├── .nvmrc ├── .clinerules ├── src ├── renderer │ ├── .gitignore │ ├── src │ │ ├── vite-env.d.ts │ │ ├── routes │ │ │ ├── omnibox │ │ │ │ ├── page.tsx │ │ │ │ └── config.tsx │ │ │ ├── main-ui │ │ │ │ ├── page.tsx │ │ │ │ └── config.tsx │ │ │ ├── popup-ui │ │ │ │ ├── page.tsx │ │ │ │ └── config.tsx │ │ │ ├── settings │ │ │ │ ├── page.tsx │ │ │ │ └── config.tsx │ │ │ ├── pdf-viewer │ │ │ │ ├── pdf-viewer │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Toolbar │ │ │ │ │ │ ├── Splitter.tsx │ │ │ │ │ │ └── DocumentInfo.tsx │ │ │ │ │ ├── Thumbsbar │ │ │ │ │ │ └── Attachments.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── config.tsx │ │ │ ├── new-tab │ │ │ │ ├── page.tsx │ │ │ │ └── config.tsx │ │ │ ├── error │ │ │ │ └── config.tsx │ │ │ ├── omnibox-debug │ │ │ │ └── config.tsx │ │ │ ├── about │ │ │ │ └── config.tsx │ │ │ ├── games │ │ │ │ └── config.tsx │ │ │ ├── onboarding │ │ │ │ ├── config.tsx │ │ │ │ └── page.tsx │ │ │ └── extensions │ │ │ │ └── config.tsx │ │ ├── types │ │ │ └── routes.ts │ │ ├── components │ │ │ ├── react-icons │ │ │ │ ├── README.md │ │ │ │ └── iconContext.ts │ │ │ ├── providers │ │ │ │ └── nuqs-provider.tsx │ │ │ ├── ui │ │ │ │ ├── skeleton.tsx │ │ │ │ ├── label.tsx │ │ │ │ ├── progress.tsx │ │ │ │ ├── separator.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── switch.tsx │ │ │ │ ├── radio-group.tsx │ │ │ │ ├── badge.tsx │ │ │ │ ├── scroll-area.tsx │ │ │ │ └── popover.tsx │ │ │ ├── settings │ │ │ │ ├── settings-titlebar.tsx │ │ │ │ ├── sections │ │ │ │ │ ├── general │ │ │ │ │ │ └── section.tsx │ │ │ │ │ ├── spaces │ │ │ │ │ │ ├── theme-editors │ │ │ │ │ │ │ └── space-icon.tsx │ │ │ │ │ │ └── space-card.tsx │ │ │ │ │ ├── shortcuts │ │ │ │ │ │ └── components │ │ │ │ │ │ │ ├── search-header.tsx │ │ │ │ │ │ │ └── reset-dialog.tsx │ │ │ │ │ └── about │ │ │ │ │ │ └── section.tsx │ │ │ │ └── settings-sidebar.tsx │ │ │ ├── onboarding │ │ │ │ ├── stages │ │ │ │ │ └── onboarding-drag-disabler.tsx │ │ │ │ ├── screen.tsx │ │ │ │ └── main.tsx │ │ │ ├── logic │ │ │ │ └── tab-disabler.tsx │ │ │ ├── analytics │ │ │ │ └── umami.tsx │ │ │ ├── browser-ui │ │ │ │ └── sidebar │ │ │ │ │ ├── content │ │ │ │ │ ├── space-title.tsx │ │ │ │ │ └── new-tab-button.tsx │ │ │ │ │ └── hover-detector.tsx │ │ │ └── main │ │ │ │ └── website-favicon.tsx │ │ ├── hooks │ │ │ ├── use-force-update.tsx │ │ │ ├── use-debounce.tsx │ │ │ └── use-mobile.ts │ │ └── lib │ │ │ ├── omnibox │ │ │ ├── data-providers │ │ │ │ ├── open-tabs.ts │ │ │ │ ├── string-similarity.ts │ │ │ │ └── history.ts │ │ │ ├── base-provider.tsx │ │ │ ├── types.ts │ │ │ └── autocomplete-result.ts │ │ │ ├── merge-refs.ts │ │ │ ├── merge-button-refs.ts │ │ │ ├── search.ts │ │ │ └── phosphor-icons.tsx │ └── public │ │ ├── assets │ │ ├── icon.png │ │ └── favicon.ico │ │ ├── chrome-dino-game │ │ ├── favicon.ico │ │ ├── icons │ │ │ ├── icon-114.png │ │ │ ├── icon-128.png │ │ │ ├── icon-16.png │ │ │ ├── icon-256.png │ │ │ └── icon-32.png │ │ ├── images │ │ │ ├── default_100_percent │ │ │ │ └── offline │ │ │ │ │ ├── 100-disabled.png │ │ │ │ │ ├── 100-error-offline.png │ │ │ │ │ ├── 100-offline-sprite.png │ │ │ │ │ ├── 100-olympics-surfing-sprite.png │ │ │ │ │ ├── 100-olympic-firemedal-sprite.png │ │ │ │ │ ├── 100-olympics-equestrian-sprite.png │ │ │ │ │ ├── 100-olympics-gymnastics-sprite.png │ │ │ │ │ ├── 100-olympics-hurdling-sprite.png │ │ │ │ │ └── 100-olympics-swimming-sprite.png │ │ │ └── default_200_percent │ │ │ │ └── offline │ │ │ │ ├── 200-disabled.png │ │ │ │ ├── 200-error-offline.png │ │ │ │ ├── 200-offline-sprite.png │ │ │ │ ├── 200-olympics-surfing-sprite.png │ │ │ │ ├── 200-olympic-firemedal-sprite.png │ │ │ │ ├── 200-olympics-equestrian-sprite.png │ │ │ │ ├── 200-olympics-gymnastics-sprite.png │ │ │ │ ├── 200-olympics-hurdling-sprite.png │ │ │ │ └── 200-olympics-swimming-sprite.png │ │ ├── scripts │ │ │ ├── strings.js │ │ │ └── neterror.slim.js │ │ └── appmanifest.json │ │ ├── edge-surf-game-v2 │ │ ├── favicon.png │ │ ├── icons │ │ │ ├── icon-114.png │ │ │ ├── icon-128.png │ │ │ ├── icon-16.png │ │ │ ├── icon-256.png │ │ │ └── icon-32.png │ │ ├── js │ │ │ └── assert.js │ │ ├── surf-iframe.html │ │ └── index.html │ │ └── edge-surf-game-v1 │ │ └── resources │ │ ├── ski │ │ ├── bg.png │ │ ├── objects.png │ │ └── player.png │ │ ├── surf │ │ ├── bg.png │ │ ├── player.png │ │ └── objects.png │ │ ├── icons │ │ ├── favicon.png │ │ ├── apple-icon.png │ │ ├── ms-icon-70x70.png │ │ ├── ms-icon-144x144.png │ │ ├── ms-icon-150x150.png │ │ ├── ms-icon-310x310.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ └── apple-icon-precomposed.png │ │ └── js │ │ ├── surf-error-reporting.js │ │ └── base-error-reporting.js ├── main │ ├── controllers │ │ ├── sessions-controller │ │ │ ├── protocols │ │ │ │ ├── types.ts │ │ │ │ ├── static-domains │ │ │ │ │ └── types.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── _protocols │ │ │ │ │ ├── flow │ │ │ │ │ │ ├── pdf-cache.ts │ │ │ │ │ │ ├── favicon.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── assets.ts │ │ │ │ │ │ └── extension-icon.ts │ │ │ │ │ ├── flow-external │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── flow-internal │ │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── preload-scripts │ │ │ │ └── index.ts │ │ │ ├── intercept-rules │ │ │ │ ├── index.ts │ │ │ │ ├── user-agent-transformer.ts │ │ │ │ └── cors-bypass-custom-protocols.ts │ │ │ ├── README.md │ │ │ └── default-session │ │ │ │ └── index.ts │ │ ├── quit-controller │ │ │ ├── handlers │ │ │ │ ├── can-quit.ts │ │ │ │ └── before-quit.ts │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── app-menu-controller │ │ │ ├── menu │ │ │ │ ├── items │ │ │ │ │ ├── window.ts │ │ │ │ │ ├── edit.ts │ │ │ │ │ ├── file.ts │ │ │ │ │ ├── app.ts │ │ │ │ │ └── archive.ts │ │ │ │ └── helpers.ts │ │ │ └── index.ts │ │ ├── tabs-controller │ │ │ └── tab-groups │ │ │ │ ├── split.ts │ │ │ │ └── glance.ts │ │ ├── windows-controller │ │ │ ├── types │ │ │ │ ├── extension-popup.ts │ │ │ │ ├── index.ts │ │ │ │ ├── onboarding.ts │ │ │ │ └── settings.ts │ │ │ ├── README.md │ │ │ ├── interfaces │ │ │ │ ├── settings.ts │ │ │ │ └── onboarding.ts │ │ │ └── utils │ │ │ │ └── close-preventer.ts │ │ ├── posthog-controller │ │ │ ├── posthog-error-capture-sdk │ │ │ │ ├── README.md │ │ │ │ ├── reduceable-cache.ts │ │ │ │ └── type-checking.ts │ │ │ └── identify.ts │ │ ├── index.ts │ │ └── default-browser-controller │ │ │ └── index.ts │ ├── modules │ │ ├── types.ts │ │ ├── electron-components.ts │ │ ├── pdf-cache.ts │ │ ├── paths.ts │ │ ├── flags.ts │ │ ├── user-agent.ts │ │ ├── logs.ts │ │ ├── output.ts │ │ └── extensions │ │ │ └── main.ts │ ├── ipc │ │ ├── app │ │ │ ├── onboarding.ts │ │ │ ├── actions.ts │ │ │ ├── open-external.ts │ │ │ ├── icons.ts │ │ │ ├── app.ts │ │ │ ├── shortcuts.ts │ │ │ ├── updates.ts │ │ │ └── new-tab.ts │ │ ├── browser │ │ │ ├── browser.ts │ │ │ └── page.ts │ │ ├── index.ts │ │ ├── window │ │ │ ├── settings.ts │ │ │ └── omnibox.ts │ │ ├── session │ │ │ └── profiles.ts │ │ └── README.md │ ├── app │ │ ├── instance.ts │ │ ├── onboarding.ts │ │ ├── lifecycle.ts │ │ ├── platform.ts │ │ └── urls.ts │ ├── browser.ts │ ├── saving │ │ └── onboarding.ts │ └── index.ts └── shared │ ├── flow │ ├── interfaces │ │ ├── browser │ │ │ ├── newTab.ts │ │ │ ├── omnibox.ts │ │ │ ├── page.ts │ │ │ ├── browser.ts │ │ │ └── navigation.ts │ │ ├── settings │ │ │ ├── onboarding.ts │ │ │ ├── openExternal.ts │ │ │ ├── icons.ts │ │ │ └── settings.ts │ │ ├── app │ │ │ ├── windows.ts │ │ │ ├── actions.ts │ │ │ ├── shortcuts.ts │ │ │ ├── app.ts │ │ │ ├── extensions.ts │ │ │ └── updates.ts │ │ └── sessions │ │ │ ├── profiles.ts │ │ │ └── spaces.ts │ └── types.ts │ └── types │ ├── updates.ts │ ├── shortcuts.ts │ ├── extensions.ts │ ├── settings.ts │ └── tabs.ts ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── .gitignore ├── .github ├── CODEOWNERS └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .prettierrc ├── assets └── public │ └── icons │ ├── 3d.png │ ├── aquatic.png │ ├── default.png │ ├── digital.png │ ├── dynamic.png │ ├── glowy.png │ ├── nature.png │ ├── retro.png │ ├── summer.png │ ├── vibrant.png │ ├── darkness.png │ ├── galactic.png │ ├── futuristic.png │ └── minimal_flat.png ├── .prettierignore ├── docs ├── assets │ └── screenshots │ │ ├── beta-newtab-1.png │ │ ├── beta-browser-1.png │ │ ├── beta-command-1.png │ │ ├── beta-settings-1.png │ │ └── beta-onboarding-1.png ├── references │ ├── tabs.md │ ├── window-border.md │ ├── view-indexes.md │ ├── logs.md │ └── omnibox-scores.md ├── api │ └── extensions │ │ └── index.md └── contributing │ ├── hot-reloading.md │ └── updating-electron.md ├── scripts ├── temp-change-name │ ├── revert.ts │ ├── set.ts │ └── module.ts ├── frontend-routes-generator │ ├── common.ts │ └── pruner.ts └── electron-upgrader │ ├── current.ts │ └── next.ts ├── tsconfig.json ├── .editorconfig ├── tsconfig.scripts.json ├── tsconfig.node.json ├── tsconfig.web.json ├── components.json ├── eslint.config.mjs └── electron.vite.config.ts /.nvmrc: -------------------------------------------------------------------------------- 1 | v22 2 | -------------------------------------------------------------------------------- /.clinerules: -------------------------------------------------------------------------------- 1 | .cursor/rules -------------------------------------------------------------------------------- /src/renderer/.gitignore: -------------------------------------------------------------------------------- 1 | route-*.html 2 | src/routes/*/main.tsx -------------------------------------------------------------------------------- /src/renderer/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .DS_Store 5 | .eslintcache 6 | *.log* 7 | .env -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | ## CORE TEAM ## 2 | # The creator and maintainer of the project. 3 | * @iamEvanYT -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": false, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /assets/public/icons/3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/3d.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | bun.lock 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | src/renderer/public/* -------------------------------------------------------------------------------- /assets/public/icons/aquatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/aquatic.png -------------------------------------------------------------------------------- /assets/public/icons/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/default.png -------------------------------------------------------------------------------- /assets/public/icons/digital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/digital.png -------------------------------------------------------------------------------- /assets/public/icons/dynamic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/dynamic.png -------------------------------------------------------------------------------- /assets/public/icons/glowy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/glowy.png -------------------------------------------------------------------------------- /assets/public/icons/nature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/nature.png -------------------------------------------------------------------------------- /assets/public/icons/retro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/retro.png -------------------------------------------------------------------------------- /assets/public/icons/summer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/summer.png -------------------------------------------------------------------------------- /assets/public/icons/vibrant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/vibrant.png -------------------------------------------------------------------------------- /assets/public/icons/darkness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/darkness.png -------------------------------------------------------------------------------- /assets/public/icons/galactic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/galactic.png -------------------------------------------------------------------------------- /assets/public/icons/futuristic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/futuristic.png -------------------------------------------------------------------------------- /assets/public/icons/minimal_flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/assets/public/icons/minimal_flat.png -------------------------------------------------------------------------------- /src/renderer/public/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/assets/icon.png -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/protocols/types.ts: -------------------------------------------------------------------------------- 1 | export type CustomProtocol = "flow" | "flow-internal" | "flow-external"; 2 | -------------------------------------------------------------------------------- /src/renderer/public/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/screenshots/beta-newtab-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/docs/assets/screenshots/beta-newtab-1.png -------------------------------------------------------------------------------- /src/main/modules/types.ts: -------------------------------------------------------------------------------- 1 | export type MinimalEvent = { 2 | preventDefault: () => void; 3 | readonly defaultPrevented: boolean; 4 | }; 5 | -------------------------------------------------------------------------------- /docs/assets/screenshots/beta-browser-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/docs/assets/screenshots/beta-browser-1.png -------------------------------------------------------------------------------- /docs/assets/screenshots/beta-command-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/docs/assets/screenshots/beta-command-1.png -------------------------------------------------------------------------------- /docs/assets/screenshots/beta-settings-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/docs/assets/screenshots/beta-settings-1.png -------------------------------------------------------------------------------- /docs/references/tabs.md: -------------------------------------------------------------------------------- 1 | ## Active Tab Modes 2 | 3 | - **Standard**: 1 standard 4 | - **Glance**: 1 Front & 1 Back 5 | - **Split**: Split Tabs 6 | -------------------------------------------------------------------------------- /docs/assets/screenshots/beta-onboarding-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/docs/assets/screenshots/beta-onboarding-1.png -------------------------------------------------------------------------------- /docs/references/window-border.md: -------------------------------------------------------------------------------- 1 | # Window Border Radius 2 | 3 | - 4px 4 | - 8px in setBorderRadius() 5 | - 8px padding in tab (browser-ui/main.tsx) 6 | -------------------------------------------------------------------------------- /scripts/temp-change-name/revert.ts: -------------------------------------------------------------------------------- 1 | import { revertName } from "./module"; 2 | 3 | // Revert the app name using the module function 4 | revertName(); 5 | -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/favicon.ico -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v2/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v2/favicon.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/icons/icon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/icons/icon-114.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/icons/icon-128.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/icons/icon-16.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/icons/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/icons/icon-256.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/icons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/icons/icon-32.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v2/icons/icon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v2/icons/icon-114.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v2/icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v2/icons/icon-128.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v2/icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v2/icons/icon-16.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v2/icons/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v2/icons/icon-256.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v2/icons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v2/icons/icon-32.png -------------------------------------------------------------------------------- /src/main/controllers/quit-controller/handlers/can-quit.ts: -------------------------------------------------------------------------------- 1 | // Insert Logic here to check if the app can quit 2 | export function canQuit(): boolean { 3 | return true; 4 | } 5 | -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/ski/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/ski/bg.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }, { "path": "./tsconfig.scripts.json" }] 4 | } 5 | -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/surf/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/surf/bg.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/ski/objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/ski/objects.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/ski/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/ski/player.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/surf/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/surf/player.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/favicon.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/surf/objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/surf/objects.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon.png -------------------------------------------------------------------------------- /src/renderer/src/routes/omnibox/page.tsx: -------------------------------------------------------------------------------- 1 | import { OmniboxMain } from "@/components/omnibox/main"; 2 | 3 | function Page() { 4 | return ; 5 | } 6 | 7 | export default Page; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-70x70.png -------------------------------------------------------------------------------- /src/renderer/src/types/routes.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, ReactNode } from "react"; 2 | 3 | export interface RouteConfigType { 4 | Providers: FunctionComponent<{ children: ReactNode }>; 5 | } 6 | -------------------------------------------------------------------------------- /docs/references/view-indexes.md: -------------------------------------------------------------------------------- 1 | # View Indexes 2 | 3 | - 999 - Omnibox 4 | - 4 - Browser Overlay Components 5 | - 3 - Standard Browser Components 6 | - 2 - Tabs 7 | - 1 - UNUSED 8 | - 0 - Glance Back Tab 9 | -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-150x150.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/ms-icon-310x310.png -------------------------------------------------------------------------------- /src/renderer/src/routes/main-ui/page.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserUI } from "@/components/browser-ui/main"; 2 | 3 | function Page() { 4 | return ; 5 | } 6 | 7 | export default Page; 8 | -------------------------------------------------------------------------------- /src/renderer/src/routes/popup-ui/page.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserUI } from "@/components/browser-ui/main"; 2 | 3 | function Page() { 4 | return ; 5 | } 6 | 7 | export default Page; 8 | -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-36x36.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-48x48.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-72x72.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-96x96.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-114x114.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-120x120.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-144x144.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-180x180.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-57x57.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-60x60.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-72x72.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-76x76.png -------------------------------------------------------------------------------- /src/renderer/src/components/react-icons/README.md: -------------------------------------------------------------------------------- 1 | This is a stripped down version of the `react-icons` library. 2 | I used this instead as the `react-icons` library is 80MB in size and I only need a few icons. 3 | -------------------------------------------------------------------------------- /src/renderer/src/routes/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { SettingsLayout } from "@/components/settings/settings-layout"; 2 | 3 | function Page() { 4 | return ; 5 | } 6 | 7 | export default Page; 8 | -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-144x144.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/android-icon-192x192.png -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/edge-surf-game-v1/resources/icons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /src/renderer/src/hooks/use-force-update.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export function useForceUpdate() { 4 | const [, setState] = useState(0); 5 | return () => setState((state) => state + 1); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/controllers/app-menu-controller/menu/items/window.ts: -------------------------------------------------------------------------------- 1 | import { MenuItemConstructorOptions } from "electron"; 2 | 3 | export const createWindowMenu = (): MenuItemConstructorOptions => ({ 4 | role: "windowMenu" 5 | }); 6 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/browser/newTab.ts: -------------------------------------------------------------------------------- 1 | export type NewTabMode = "omnibox" | "tab"; 2 | 3 | // API // 4 | export interface FlowNewTabAPI { 5 | /** 6 | * Opens a new tab 7 | */ 8 | open: () => void; 9 | } 10 | -------------------------------------------------------------------------------- /docs/references/logs.md: -------------------------------------------------------------------------------- 1 | # Logs from Flow 2 | 3 | Logs can be found in these directories: 4 | 5 | - on Linux: `~/.config/Flow/logs/` 6 | - on macOS: `~/Library/Logs/Flow/` 7 | - on Windows: `%USERPROFILE%\AppData\Roaming\Flow\logs\` 8 | -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-disabled.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-disabled.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-error-offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-error-offline.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-error-offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-error-offline.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-offline-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-offline-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-offline-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-offline-sprite.png -------------------------------------------------------------------------------- /src/main/controllers/tabs-controller/tab-groups/split.ts: -------------------------------------------------------------------------------- 1 | import { BaseTabGroup } from "./index"; 2 | 3 | export class SplitTabGroup extends BaseTabGroup { 4 | public mode: "split" = "split" as const; 5 | 6 | // TODO: Implement split tab group layout 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-surfing-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-surfing-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-surfing-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-surfing-sprite.png -------------------------------------------------------------------------------- /src/shared/types/updates.ts: -------------------------------------------------------------------------------- 1 | import type { UpdateInfo, ProgressInfo } from "electron-updater"; 2 | 3 | export interface UpdateStatus { 4 | availableUpdate: UpdateInfo | null; 5 | downloadProgress: ProgressInfo | null; 6 | updateDownloaded: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympic-firemedal-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympic-firemedal-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-equestrian-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-equestrian-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-gymnastics-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-gymnastics-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-hurdling-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-hurdling-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-swimming-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_100_percent/offline/100-olympics-swimming-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympic-firemedal-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympic-firemedal-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-equestrian-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-equestrian-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-gymnastics-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-gymnastics-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-hurdling-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-hurdling-sprite.png -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-swimming-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiboxLabs/flow-browser/HEAD/src/renderer/public/chrome-dino-game/images/default_200_percent/offline/200-olympics-swimming-sprite.png -------------------------------------------------------------------------------- /src/renderer/src/routes/pdf-viewer/pdf-viewer/README.md: -------------------------------------------------------------------------------- 1 | # PDF Viewer 2 | 3 | This PDF viewer is taken from [pdfslick examples](https://github.com/pdfslick/pdfslick/tree/35535e16991d2ca3f82673456af9a7da82d32aa8/apps/web/examples/PDFViewerApp) and modified to work with Flow. 4 | -------------------------------------------------------------------------------- /src/renderer/src/routes/pdf-viewer/pdf-viewer/Toolbar/Splitter.tsx: -------------------------------------------------------------------------------- 1 | const Splitter = () => { 2 | return ( 3 |
4 | ); 5 | }; 6 | 7 | export default Splitter; 8 | -------------------------------------------------------------------------------- /src/renderer/src/components/providers/nuqs-provider.tsx: -------------------------------------------------------------------------------- 1 | import { NuqsAdapter } from "nuqs/adapters/react"; 2 | import type { ReactNode } from "react"; 3 | 4 | export function NuqsProvider({ children }: { children: ReactNode }) { 5 | return {children}; 6 | } 7 | -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v1/resources/js/surf-error-reporting.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) Microsoft Corporation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | hookErrorReporting('webui-surf-game'); -------------------------------------------------------------------------------- /src/shared/flow/interfaces/settings/onboarding.ts: -------------------------------------------------------------------------------- 1 | // API // 2 | export interface FlowOnboardingAPI { 3 | /** 4 | * Finishes the onboarding process 5 | */ 6 | finish: () => void; 7 | 8 | /** 9 | * Resets the onboarding process 10 | */ 11 | reset: () => void; 12 | } 13 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/app/windows.ts: -------------------------------------------------------------------------------- 1 | // API // 2 | export interface FlowWindowsAPI { 3 | /** 4 | * Opens the settings window 5 | */ 6 | openSettingsWindow: () => void; 7 | 8 | /** 9 | * Closes the settings window 10 | */ 11 | closeSettingsWindow: () => void; 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return
; 5 | } 6 | 7 | export { Skeleton }; 8 | -------------------------------------------------------------------------------- /src/renderer/src/routes/new-tab/page.tsx: -------------------------------------------------------------------------------- 1 | import { NewTabPage } from "@/components/new-tab/main"; 2 | 3 | function Page() { 4 | return ; 5 | } 6 | 7 | function App() { 8 | return ( 9 | <> 10 | New Tab 11 | 12 | 13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /src/main/ipc/app/onboarding.ts: -------------------------------------------------------------------------------- 1 | import { setOnboardingCompleted, resetOnboarding } from "@/saving/onboarding"; 2 | import { ipcMain } from "electron"; 3 | 4 | ipcMain.on("onboarding:finish", () => { 5 | return setOnboardingCompleted(); 6 | }); 7 | 8 | ipcMain.on("onboarding:reset", () => { 9 | return resetOnboarding(); 10 | }); 11 | -------------------------------------------------------------------------------- /src/renderer/src/components/settings/settings-titlebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export function SettingsTitlebar() { 4 | return ( 5 |
6 | Flow Settings 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/shared/flow/types.ts: -------------------------------------------------------------------------------- 1 | export type PageBounds = { 2 | x: number; 3 | y: number; 4 | width: number; 5 | height: number; 6 | }; 7 | 8 | export type WindowState = { 9 | isMaximized: boolean; 10 | isFullscreen: boolean; 11 | }; 12 | 13 | export type IPCListener = (callback: (...data: T) => void) => () => void; 14 | -------------------------------------------------------------------------------- /src/renderer/src/lib/omnibox/data-providers/open-tabs.ts: -------------------------------------------------------------------------------- 1 | export async function getOpenTabsInSpace() { 2 | const spaceId = await flow.spaces.getUsingSpace(); 3 | if (!spaceId) return []; 4 | 5 | const tabsData = await flow.tabs.getData(); 6 | 7 | const tabs = tabsData.tabs.filter((tab) => tab.spaceId === spaceId); 8 | return tabs; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/preload-scripts/index.ts: -------------------------------------------------------------------------------- 1 | import { PATHS } from "@/modules/paths"; 2 | import type { Session } from "electron"; 3 | 4 | export function registerPreloadScripts(session: Session) { 5 | session.registerPreloadScript({ 6 | id: "flow-preload", 7 | type: "frame", 8 | filePath: PATHS.PRELOAD 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[json]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "typescript.tsdk": "node_modules/typescript/lib" 12 | } 13 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/app/actions.ts: -------------------------------------------------------------------------------- 1 | import { IPCListener } from "~/flow/types"; 2 | 3 | // API // 4 | export interface FlowActionsAPI { 5 | /** 6 | * Listen for copy link action 7 | */ 8 | onCopyLink: IPCListener<[]>; 9 | 10 | /** 11 | * Listen for generic incoming actions 12 | */ 13 | onIncomingAction: IPCListener<[string]>; 14 | } 15 | -------------------------------------------------------------------------------- /src/shared/types/shortcuts.ts: -------------------------------------------------------------------------------- 1 | export interface ShortcutAction { 2 | id: string; // e.g., "tabs.newTab", "navigation.goBack" 3 | name: string; // e.g., "Open New Tab", "Go Back" 4 | shortcut: string; // e.g., "CommandOrControl+T", "Alt+Left" 5 | category: string; // e.g., "Tabs", "Navigation" 6 | originalShortcut?: string; // To store the initial default shortcut 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/src/routes/error/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/omnibox/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/settings/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/omnibox-debug/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /tsconfig.scripts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", 3 | "include": ["electron.vite.config.*", "scripts/**/*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "moduleResolution": "bundler", 7 | "types": ["electron-vite/node"], 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": ["scripts/*"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/routes/main-ui/config.tsx: -------------------------------------------------------------------------------- 1 | import { PortalsProvider } from "@/components/portal/provider"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/new-tab/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/popup-ui/config.tsx: -------------------------------------------------------------------------------- 1 | import { PortalsProvider } from "@/components/portal/provider"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/about/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/games/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/routes/onboarding/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/main/theme"; 2 | import { RouteConfigType } from "@/types/routes"; 3 | import { ReactNode } from "react"; 4 | 5 | export const RouteConfig: RouteConfigType = { 6 | Providers: ({ children }: { children: ReactNode }) => { 7 | return {children}; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer/src/lib/omnibox/data-providers/string-similarity.ts: -------------------------------------------------------------------------------- 1 | import { stringSimilarity } from "string-similarity-js"; 2 | 3 | const SIMILARITY_THRESHOLD = 0.4; 4 | 5 | export function getStringSimilarity(str1: string, str2: string): number { 6 | const similarity = stringSimilarity(str1, str2); 7 | if (similarity >= SIMILARITY_THRESHOLD) { 8 | return similarity; 9 | } 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/browser/omnibox.ts: -------------------------------------------------------------------------------- 1 | import { PageBounds } from "~/flow/types"; 2 | 3 | type QueryParams = { [key: string]: string }; 4 | 5 | // API // 6 | export interface FlowOmniboxAPI { 7 | /** 8 | * Shows the omnibox 9 | */ 10 | show: (bounds: PageBounds | null, params: QueryParams | null) => void; 11 | 12 | /** 13 | * Hides the omnibox 14 | */ 15 | hide: () => void; 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/src/lib/merge-refs.ts: -------------------------------------------------------------------------------- 1 | export function mergeRefs(refs: Array | undefined>): React.RefCallback { 2 | return (value) => { 3 | for (const ref of refs) { 4 | if (typeof ref === "function") { 5 | ref(value); 6 | } else if (ref != null) { 7 | (ref as React.MutableRefObject).current = value; 8 | } 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/renderer/src/components/onboarding/stages/onboarding-drag-disabler.tsx: -------------------------------------------------------------------------------- 1 | export function OnboardingDragDisabler() { 2 | // Enable drag for Topbar 3 | // Disable drag for rest of the window 4 | return ( 5 |
6 |
7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/ipc/app/actions.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from "@/controllers/windows-controller/types"; 2 | import { sendMessageToListeners } from "@/ipc/listeners-manager"; 3 | 4 | export async function fireCopyLinkAction(win: BrowserWindow) { 5 | win.sendMessage("actions:on-copy-link"); 6 | } 7 | 8 | export async function fireFrontendAction(action: string) { 9 | sendMessageToListeners("actions:on-incoming", action); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/ipc/app/open-external.ts: -------------------------------------------------------------------------------- 1 | import { getAlwaysOpenExternal, unsetAlwaysOpenExternal } from "@/saving/open-external"; 2 | import { ipcMain } from "electron"; 3 | 4 | ipcMain.handle("open-external:get", () => { 5 | return getAlwaysOpenExternal(); 6 | }); 7 | 8 | ipcMain.handle("open-external:unset", (_, requestingURL: string, openingURL: string) => { 9 | return unsetAlwaysOpenExternal(requestingURL, openingURL); 10 | }); 11 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/types/extension-popup.ts: -------------------------------------------------------------------------------- 1 | import { BaseWindow } from "@/controllers/windows-controller/types/base"; 2 | import { BrowserWindow } from "electron"; 3 | 4 | export class ExtensionPopupWindow extends BaseWindow { 5 | constructor(browserWindow: BrowserWindow) { 6 | // Uses the browserWindow that was already created by the extension handler 7 | super("extension-popup", browserWindow); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/controllers/posthog-controller/posthog-error-capture-sdk/README.md: -------------------------------------------------------------------------------- 1 | # PostHog Error Capture 2 | 3 | This is taken from a directory in [PostHog/posthog-js-lite](https://github.com/PostHog/posthog-js-lite/tree/6f67f8a3e9724479011173f7b0fc0c190806da2b/posthog-node/src/extensions/error-tracking) and forked. 4 | 5 | ## Modifications 6 | 7 | - Added a fallback distinct ID to use when no distinct ID is provided. 8 | - Use fallback distinct ID for autocapture. 9 | -------------------------------------------------------------------------------- /src/renderer/src/routes/onboarding/page.tsx: -------------------------------------------------------------------------------- 1 | import { OnboardingMain } from "@/components/onboarding/main"; 2 | import { SettingsProvider } from "@/components/providers/settings-provider"; 3 | 4 | function Page() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | function App() { 13 | return ( 14 | <> 15 | 16 | 17 | ); 18 | } 19 | export default App; 20 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", 3 | "include": ["src/main/**/*", "src/preload/**/*", "src/shared/**/*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "moduleResolution": "bundler", 7 | "types": ["electron-vite/node"], 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": [ 11 | "src/main/*" 12 | ], 13 | "~/*": ["src/shared/*"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/api/extensions/index.md: -------------------------------------------------------------------------------- 1 | # Extensions API 2 | 3 | The Extensions API provides functionalities for working with browser extensions in Flow Browser. This document serves as an index to the available extension-related APIs. 4 | 5 | ## Available APIs 6 | 7 | - [Permission Warnings](./permission-warnings.md): Converts extension manifest permissions into user-facing warning messages 8 | - [Locales](./locales.md): Handles internationalization and localization for extensions 9 | -------------------------------------------------------------------------------- /src/renderer/src/components/logic/tab-disabler.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export function TabDisabler() { 4 | useEffect(() => { 5 | const onKeyDown = (e: KeyboardEvent) => { 6 | if (e.key === "Tab") { 7 | e.preventDefault(); 8 | } 9 | }; 10 | window.addEventListener("keydown", onKeyDown); 11 | return () => window.removeEventListener("keydown", onKeyDown); 12 | }, []); 13 | 14 | return <>; 15 | } 16 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/browser/page.ts: -------------------------------------------------------------------------------- 1 | import { PageBounds } from "~/flow/types"; 2 | 3 | // API // 4 | export interface FlowPageAPI { 5 | /** 6 | * Sets the bounds of the page content 7 | * Similar to setTabBounds but specifically for the page content area 8 | * This can only be called from the Browser UI 9 | * @param bounds The bounds object containing position and dimensions 10 | */ 11 | setPageBounds: (bounds: PageBounds) => void; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/types/index.ts: -------------------------------------------------------------------------------- 1 | export { BaseWindow } from "@/controllers/windows-controller/types/base"; 2 | 3 | export { SettingsWindow } from "@/controllers/windows-controller/types/settings"; 4 | export { OnboardingWindow } from "@/controllers/windows-controller/types/onboarding"; 5 | export { BrowserWindow } from "@/controllers/windows-controller/types/browser"; 6 | export { ExtensionPopupWindow } from "@/controllers/windows-controller/types/extension-popup"; 7 | -------------------------------------------------------------------------------- /src/main/modules/electron-components.ts: -------------------------------------------------------------------------------- 1 | export async function waitForElectronComponentsToBeReady() { 2 | const electron = await import("electron"); 3 | 4 | if ("components" in electron) { 5 | const { components } = electron; 6 | 7 | // @ts-ignore: This is defined in Widevine Electron, but not in the stock Electron 8 | return components 9 | .whenReady() 10 | .then(() => true) 11 | .catch(() => false); 12 | } 13 | 14 | return Promise.resolve(false); 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/src/hooks/use-debounce.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useEffect } from "react"; 3 | 4 | export function useDebounce(value: T, delay: number): T { 5 | const [debouncedValue, setDebouncedValue] = useState(value); 6 | 7 | useEffect(() => { 8 | const timer = setTimeout(() => { 9 | setDebouncedValue(value); 10 | }, delay); 11 | 12 | return () => { 13 | clearTimeout(timer); 14 | }; 15 | }, [value, delay]); 16 | 17 | return debouncedValue; 18 | } 19 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/browser/browser.ts: -------------------------------------------------------------------------------- 1 | // API // 2 | export interface FlowBrowserAPI { 3 | /** 4 | * Loads a profile 5 | * @param profileId The id of the profile to load 6 | */ 7 | loadProfile: (profileId: string) => Promise; 8 | 9 | /** 10 | * Unloads a profile 11 | * @param profileId The id of the profile to unload 12 | */ 13 | unloadProfile: (profileId: string) => Promise; 14 | 15 | /** 16 | * Creates a new window 17 | */ 18 | createWindow: () => void; 19 | } 20 | -------------------------------------------------------------------------------- /scripts/frontend-routes-generator/common.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs/promises"; 3 | 4 | // Constants // 5 | export const FRONTEND_PATH = path.resolve(__dirname, "..", "..", "src", "renderer"); 6 | export const ROUTES_PATH = path.join(FRONTEND_PATH, "src/routes"); 7 | 8 | // Helper Functions // 9 | export const getDirectories = async (source: string) => 10 | (await fs.readdir(source, { withFileTypes: true })) 11 | .filter((dirent) => dirent.isDirectory()) 12 | .map((dirent) => dirent.name); 13 | -------------------------------------------------------------------------------- /scripts/temp-change-name/set.ts: -------------------------------------------------------------------------------- 1 | import { changeName, revertName } from "./module"; 2 | 3 | const args = process.argv; 4 | 5 | // Grab Temp App Name 6 | const nameIndex = args.findIndex((val) => val === "--name"); 7 | if (nameIndex === -1) { 8 | console.error("No name provided"); 9 | process.exit(1); 10 | } 11 | 12 | const newName = args[nameIndex + 1]; 13 | 14 | // Revert the app name before setting the new name 15 | revertName(); 16 | 17 | // Change the app name using the module function 18 | changeName(newName); 19 | -------------------------------------------------------------------------------- /src/renderer/src/components/analytics/umami.tsx: -------------------------------------------------------------------------------- 1 | export const UmamiScriptLoader = () => { 2 | if (process.env.NODE_ENV !== "production") return null; 3 | 4 | // Umami Analytics 5 | // Very simple, does not collect or store personal data 6 | // Does not log what websites you visit, or anything similar 7 | return ( 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/renderer/src/hooks/use-mobile.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined); 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 12 | }; 13 | mql.addEventListener("change", onChange); 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 15 | return () => mql.removeEventListener("change", onChange); 16 | }, []); 17 | 18 | return !!isMobile; 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Label as LabelPrimitive } from "radix-ui"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | function Label({ className, ...props }: React.ComponentProps) { 7 | return ( 8 | 16 | ); 17 | } 18 | 19 | export { Label }; 20 | -------------------------------------------------------------------------------- /src/main/app/instance.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron"; 2 | import { handleOpenUrl, isValidOpenerUrl } from "@/app/urls"; 3 | import { debugPrint } from "@/modules/output"; 4 | 5 | function shouldCreateNewWindow(args: string[]): boolean { 6 | return args.includes("--new-window"); 7 | } 8 | 9 | export function setupSecondInstanceHandling() { 10 | app.on("second-instance", async (_event, commandLine) => { 11 | const url = commandLine.pop(); 12 | if (url && isValidOpenerUrl(url)) { 13 | const shouldCreate = shouldCreateNewWindow(commandLine); 14 | handleOpenUrl(shouldCreate, url); 15 | } 16 | }); 17 | 18 | debugPrint("INITIALIZATION", "second instance handler initialized"); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/protocols/utils.ts: -------------------------------------------------------------------------------- 1 | import { getPathNoStrict } from "hono/utils/url"; 2 | 3 | export interface AllowedDomains { 4 | [key: string]: true | string; 5 | } 6 | 7 | export interface ServeStaticFileOptions { 8 | overrideRouteName?: string; 9 | } 10 | 11 | export function transformPathForRequest(request: Request) { 12 | const realPath = getPathNoStrict(request); 13 | if (!realPath) { 14 | return realPath; 15 | } 16 | 17 | const url = URL.parse(request.url); 18 | // 'unknown-hostname' as default hostname to prevent weird behaviors when no hostname is present 19 | const hostname = url?.hostname ?? "unknown-hostname"; 20 | return `/${hostname}${realPath}`; 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/src/lib/merge-button-refs.ts: -------------------------------------------------------------------------------- 1 | /* 2 | This function merges multiple refs into a single ref callback. 3 | It ensures that the value is assigned to each ref, whether it's a function or a mutable ref object. 4 | This is useful when you need to merge external refs with an internal ref. 5 | */ 6 | 7 | export function mergeButtonRefs( 8 | refs: Array | undefined> 9 | ): React.RefCallback { 10 | return (value) => { 11 | for (const ref of refs) { 12 | if (typeof ref === "function") { 13 | ref(value); 14 | } else if (ref != null) { 15 | (ref as React.MutableRefObject).current = value; 16 | } 17 | } 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/app/shortcuts.ts: -------------------------------------------------------------------------------- 1 | import { IPCListener } from "~/flow/types"; 2 | import { ShortcutAction } from "~/types/shortcuts"; 3 | 4 | export type ShortcutsData = ShortcutAction[]; 5 | 6 | // API // 7 | export interface FlowShortcutsAPI { 8 | /** 9 | * Get all shortcuts 10 | */ 11 | getShortcuts: () => Promise; 12 | 13 | /** 14 | * Set a shortcut 15 | */ 16 | setShortcut: (actionId: string, shortcut: string) => Promise; 17 | 18 | /** 19 | * Reset a shortcut 20 | */ 21 | resetShortcut: (actionId: string) => Promise; 22 | 23 | /** 24 | * Listen for shortcuts updates 25 | */ 26 | onShortcutsUpdated: IPCListener<[ShortcutsData]>; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/controllers/index.ts: -------------------------------------------------------------------------------- 1 | // Required by app-menu-controller 2 | import "./windows-controller"; 3 | 4 | // Required by app-menu-controller, profiles-controller 5 | import "./spaces-controller"; 6 | 7 | // Required by spaces-controller, loaded-profiles-controller, sessions-controller 8 | import "./profiles-controller"; 9 | 10 | // Required by windows-controller 11 | import "./app-menu-controller"; 12 | 13 | // Required by app-menu-controller 14 | import "./default-browser-controller"; 15 | 16 | // No Dependents 17 | import "./posthog-controller"; 18 | import "./quit-controller"; 19 | import "./auto-update-controller"; 20 | import "./loaded-profiles-controller"; 21 | import "./tabs-controller"; 22 | import "./sessions-controller"; 23 | -------------------------------------------------------------------------------- /src/main/ipc/app/app.ts: -------------------------------------------------------------------------------- 1 | import { defaultBrowserController } from "@/controllers/default-browser-controller"; 2 | import { app, clipboard } from "electron"; 3 | import { ipcMain } from "electron"; 4 | 5 | ipcMain.handle("app:get-info", async () => { 6 | return { 7 | version: app.getVersion(), 8 | packaged: app.isPackaged 9 | }; 10 | }); 11 | 12 | ipcMain.on("app:write-text-to-clipboard", (_event, text: string) => { 13 | clipboard.writeText(text); 14 | }); 15 | 16 | ipcMain.handle("app:set-default-browser", async () => { 17 | return await defaultBrowserController.setDefaultBrowser(); 18 | }); 19 | 20 | ipcMain.handle("app:get-default-browser", async () => { 21 | return defaultBrowserController.isDefaultBrowser(); 22 | }); 23 | -------------------------------------------------------------------------------- /src/renderer/src/routes/pdf-viewer/config.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider as ThemeProviderComponent } from "@/components/main/theme"; 2 | import { NuqsProvider } from "@/components/providers/nuqs-provider"; 3 | import { RouteConfigType } from "@/types/routes"; 4 | import { Fragment, ReactNode } from "react"; 5 | 6 | // Theme makes it go all weird... 7 | const THEME_PROVIDER_ENABLED = true; 8 | 9 | const ThemeProvider = THEME_PROVIDER_ENABLED ? ThemeProviderComponent : Fragment; 10 | 11 | export const RouteConfig: RouteConfigType = { 12 | Providers: ({ children }: { children: ReactNode }) => { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] " 5 | labels: enhancement 6 | type: feature 7 | assignees: "" 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/protocols/_protocols/flow/pdf-cache.ts: -------------------------------------------------------------------------------- 1 | import { getPdfResponseFromCache, removePdfResponseFromCache } from "@/modules/pdf-cache"; 2 | import { HonoApp } from "."; 3 | 4 | export function registerPdfCacheRoutes(app: HonoApp) { 5 | app.get("/pdf-cache", async (c) => { 6 | const pdfURL = c.req.query("url"); 7 | const key = c.req.query("key"); 8 | if (!pdfURL || !key) { 9 | return c.text("Invalid request path", 400); 10 | } 11 | 12 | const pdfResponse = getPdfResponseFromCache(key); 13 | if (!pdfResponse) { 14 | // redirect to actual url 15 | return c.redirect(pdfURL); 16 | } 17 | 18 | removePdfResponseFromCache(key); 19 | return pdfResponse; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/ipc/index.ts: -------------------------------------------------------------------------------- 1 | // App APIs 2 | import "@/ipc/app/app"; 3 | import "@/ipc/app/extensions"; 4 | import "@/ipc/app/updates"; 5 | import "@/ipc/app/shortcuts"; 6 | 7 | // Browser APIs 8 | import "@/ipc/browser/browser"; 9 | import "@/ipc/browser/tabs"; 10 | import "@/ipc/browser/page"; 11 | import "@/ipc/browser/navigation"; 12 | import "@/ipc/browser/interface"; 13 | import "@/ipc/window/omnibox"; 14 | import "@/ipc/app/new-tab"; 15 | 16 | // Session APIs 17 | import "@/ipc/session/profiles"; 18 | import "@/ipc/session/spaces"; 19 | 20 | // Settings APIs 21 | import "@/ipc/window/settings"; 22 | import "@/ipc/app/icons"; 23 | import "@/ipc/app/open-external"; 24 | import "@/ipc/app/onboarding"; 25 | 26 | // Special 27 | import "@/ipc/listeners-manager"; 28 | -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/scripts/strings.js: -------------------------------------------------------------------------------- 1 | var pageData = {"altGameCommonImage1x":"images/default_100_percent/offline/100-olympic-firemedal-sprites.png","altGameCommonImage2x":"images/default_200_percent/offline/200-olympic-firemedal-sprites.png","dinoGameA11yAriaLabel":"","dinoGameA11yGameOver":"Game over, your score is $1.","dinoGameA11yHighScore":"Your highest score is $1.","dinoGameA11yJump":"Jump!","dinoGameA11ySpeedToggle":"Start slower","dinoGameA11yStartGame":"Game started.","enableAltGameMode":false,"errorCode":"","fontfamily":"'Segoe UI', Tahoma, sans-serif","fontsize":"75%","heading":{"hostName":"dino","msg":"Press space to play"},"iconClass":"icon-offline","language":"en","textdirection":"ltr","title":"chrome://dino/"}; 2 | loadTimeData.data = pageData; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "bug" 6 | type: bug 7 | assignees: "" 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Device Information:** 28 | 29 | - OS: [e.g. iOS] 30 | - App Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/protocols/_protocols/flow-external/index.ts: -------------------------------------------------------------------------------- 1 | import { transformPathForRequest } from "../../utils"; 2 | import { type Protocol } from "electron"; 3 | import { Hono } from "hono/tiny"; 4 | import { registerStaticDomainsRoutes } from "../../static-domains"; 5 | 6 | // Create Hono App 7 | const app = new Hono({ 8 | getPath: transformPathForRequest 9 | }); 10 | export type HonoApp = typeof app; 11 | 12 | // Register Routes 13 | // None currently 14 | 15 | // Catch-all Route 16 | registerStaticDomainsRoutes("flow-external", app); 17 | 18 | // Export Protocol Handler 19 | export function registerFlowExternalProtocol(protocol: Protocol) { 20 | protocol.handle("flow-external", async (request) => { 21 | return app.fetch(request); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/protocols/_protocols/flow/favicon.ts: -------------------------------------------------------------------------------- 1 | import { getFavicon, normalizeURL } from "@/modules/favicons"; 2 | import { HonoApp } from "."; 3 | import { bufferToArrayBuffer } from "@/modules/utils"; 4 | 5 | export function registerFaviconRoutes(app: HonoApp) { 6 | app.get("/favicon", async (c) => { 7 | const targetUrl = c.req.query("url"); 8 | if (!targetUrl) { 9 | return c.text("No URL provided", 400); 10 | } 11 | 12 | const normalizedTargetUrl = normalizeURL(targetUrl); 13 | const favicon = await getFavicon(normalizedTargetUrl); 14 | if (!favicon) { 15 | return c.text("No favicon found", 404); 16 | } 17 | 18 | return c.body(bufferToArrayBuffer(favicon), 200, { "Content-Type": "image/png" }); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/sessions/profiles.ts: -------------------------------------------------------------------------------- 1 | export type Profile = { 2 | id: string; 3 | name: string; 4 | }; 5 | 6 | // API // 7 | export interface FlowProfilesAPI { 8 | /** 9 | * Gets the profiles 10 | */ 11 | getProfiles: () => Promise; 12 | 13 | /** 14 | * Creates a profile 15 | */ 16 | createProfile: (profileName: string) => Promise; 17 | 18 | /** 19 | * Updates a profile 20 | */ 21 | updateProfile: (profileId: string, profileData: Partial) => Promise; 22 | 23 | /** 24 | * Deletes a profile 25 | */ 26 | deleteProfile: (profileId: string) => Promise; 27 | 28 | /** 29 | * Gets the profile id that is currently being used 30 | */ 31 | getUsingProfile: () => Promise; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/app/onboarding.ts: -------------------------------------------------------------------------------- 1 | import { debugPrint } from "@/modules/output"; 2 | import { hasCompletedOnboarding } from "@/saving/onboarding"; 3 | import { onboarding } from "@/controllers/windows-controller/interfaces/onboarding"; 4 | import { createInitialWindow } from "@/saving/tabs"; 5 | 6 | export function runOnboardingOrInitialWindow() { 7 | debugPrint("INITIALIZATION", "grabbing hasCompletedOnboarding()"); 8 | hasCompletedOnboarding().then((completed) => { 9 | debugPrint("INITIALIZATION", "grabbed hasCompletedOnboarding()", completed); 10 | if (!completed) { 11 | onboarding.show(); 12 | debugPrint("INITIALIZATION", "show onboarding window"); 13 | } else { 14 | createInitialWindow(); 15 | debugPrint("INITIALIZATION", "show browser window"); 16 | } 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import { IPCListener } from "~/flow/types"; 2 | import type { BasicSetting, BasicSettingCard } from "~/types/settings"; 3 | 4 | // API // 5 | export interface FlowSettingsAPI { 6 | /** 7 | * Gets the value of a setting 8 | */ 9 | getSetting(settingId: string): Promise; 10 | 11 | /** 12 | * Sets the value of a setting 13 | */ 14 | setSetting: (settingId: string, value: unknown) => Promise; 15 | 16 | /** 17 | * Gets the basic settings and cards 18 | */ 19 | getBasicSettings: () => Promise<{ 20 | settings: BasicSetting[]; 21 | cards: BasicSettingCard[]; 22 | }>; 23 | 24 | /** 25 | * Listens for changes to the settings */ 26 | onSettingsChanged: IPCListener<[void]>; 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/src/components/browser-ui/sidebar/content/space-title.tsx: -------------------------------------------------------------------------------- 1 | import { Space } from "~/flow/interfaces/sessions/spaces"; 2 | import { SidebarGroup, SidebarMenuButton, useSidebar } from "@/components/ui/resizable-sidebar"; 3 | import { SpaceIcon } from "@/lib/phosphor-icons"; 4 | 5 | export function SpaceTitle({ space }: { space: Space }) { 6 | const { open } = useSidebar(); 7 | 8 | if (!open) return null; 9 | 10 | return ( 11 | 12 | 13 | 14 | {space.name} 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/src/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Progress as ProgressPrimitive } from "radix-ui"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Progress({ className, value, ...props }: React.ComponentProps) { 9 | return ( 10 | 15 | 20 | 21 | ); 22 | } 23 | 24 | export { Progress }; 25 | -------------------------------------------------------------------------------- /src/renderer/src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Separator as SeparatorPrimitive } from "radix-ui"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Separator({ 9 | className, 10 | orientation = "horizontal", 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ); 26 | } 27 | 28 | export { Separator }; 29 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/protocols/_protocols/flow-internal/index.ts: -------------------------------------------------------------------------------- 1 | import { type Protocol } from "electron"; 2 | import { Hono } from "hono/tiny"; 3 | import { registerStaticDomainsRoutes } from "../../static-domains"; 4 | import { transformPathForRequest } from "../../utils"; 5 | import { registerActiveFaviconRoutes } from "./active-favicon"; 6 | 7 | // Create Hono App 8 | const app = new Hono({ 9 | getPath: transformPathForRequest 10 | }); 11 | export type HonoApp = typeof app; 12 | 13 | // Register Routes 14 | registerActiveFaviconRoutes(app); 15 | 16 | // Catch-all Route 17 | registerStaticDomainsRoutes("flow-internal", app); 18 | 19 | // Export Protocol Handler 20 | export function registerFlowInternalProtocol(protocol: Protocol) { 21 | protocol.handle("flow-internal", async (request) => { 22 | return app.fetch(request); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/shared/flow/interfaces/app/app.ts: -------------------------------------------------------------------------------- 1 | // API // 2 | export interface FlowAppAPI { 3 | /** 4 | * Gets the app info 5 | */ 6 | getAppInfo: () => Promise<{ 7 | app_version: string; 8 | build_number: string; 9 | node_version: string; 10 | chrome_version: string; 11 | electron_version: string; 12 | os: string; 13 | update_channel: "Stable" | "Beta" | "Alpha" | "Development"; 14 | }>; 15 | 16 | /** 17 | * Gets the platform of the current device 18 | */ 19 | getPlatform: () => string; 20 | 21 | /** 22 | * Writes text to the clipboard 23 | */ 24 | writeTextToClipboard: (text: string) => void; 25 | 26 | /** 27 | * Sets the default browser 28 | */ 29 | setDefaultBrowser: () => Promise; 30 | 31 | /** 32 | * Gets the default browser 33 | */ 34 | getDefaultBrowser: () => Promise; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/intercept-rules/index.ts: -------------------------------------------------------------------------------- 1 | import { setupBetterPdfViewer } from "@/controllers/sessions-controller/intercept-rules/better-pdf-viewer"; 2 | import { setupCorsBypassForCustomProtocols } from "@/controllers/sessions-controller/intercept-rules/cors-bypass-custom-protocols"; 3 | import { setupUserAgentTransformer } from "@/controllers/sessions-controller/intercept-rules/user-agent-transformer"; 4 | import type { Session } from "electron"; 5 | 6 | // Setup intercept rules for the session 7 | export function setupInterceptRules(session: Session) { 8 | // Transform the User-Agent header 9 | setupUserAgentTransformer(session); 10 | 11 | // Bypass CORS for flow and flow-internal protocols 12 | setupCorsBypassForCustomProtocols(session); 13 | 14 | // Setup redirects required for the better PDF viewer 15 | setupBetterPdfViewer(session); 16 | } 17 | -------------------------------------------------------------------------------- /src/shared/types/settings.ts: -------------------------------------------------------------------------------- 1 | // This is used to create a simple settings framework. 2 | // This will make it easier to add new settings and cards. 3 | 4 | // Setting Type: Boolean // 5 | type SettingTypeBoolean = { 6 | type: "boolean"; 7 | defaultValue: boolean; 8 | }; 9 | 10 | // Setting Type: Enum // 11 | type SettingTypeEnumOption = { 12 | id: string; 13 | name: string; 14 | }; 15 | 16 | type SettingTypeEnum = { 17 | type: "enum"; 18 | defaultValue: string; 19 | options: SettingTypeEnumOption[]; 20 | }; 21 | 22 | export type SettingType = SettingTypeBoolean | SettingTypeEnum; 23 | 24 | // Setting // 25 | export type BasicSetting = { 26 | id: string; 27 | name: string; 28 | showName?: boolean; 29 | } & SettingType; 30 | 31 | // Setting Card // 32 | export type BasicSettingCard = { 33 | title: string; 34 | subtitle: string; 35 | settings: string[]; 36 | }; 37 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/README.md: -------------------------------------------------------------------------------- 1 | # WindowsController 2 | 3 | This handles all `BrowserWindows` from `electron` and seperates them into different types of windows to easily manage them. 4 | 5 | ## Components 6 | 7 | - `./index.ts` - Main interface exposed to grab all windows (not creating new ones!) 8 | - `./type-manager.ts` - A helper class to manage and let you create new windows of a specific type 9 | - `./interfaces` - Interfaces for the windows controller for external use 10 | - `./types` - Window classes for each type of windows (and the base window class) 11 | - `./utils` - Handles the utilities for the windows 12 | 13 | ## External Usage 14 | 15 | - The only thing that should be required outside of this controller is the `index.ts` file and interfaces in the `./interfaces` directory. 16 | - All other files are internal and should not be used outside of this controller. 17 | -------------------------------------------------------------------------------- /src/renderer/src/lib/omnibox/base-provider.tsx: -------------------------------------------------------------------------------- 1 | import { OmniboxUpdateCallback } from "@/lib/omnibox/omnibox"; 2 | import { AutocompleteInput } from "@/lib/omnibox/types"; 3 | 4 | /** Interface for components that generate autocomplete suggestions. */ 5 | export interface AutocompleteProvider { 6 | name: string; 7 | /** 8 | * Starts generating suggestions for the given input. 9 | * Results are sent asynchronously via the onResults callback. 10 | */ 11 | start(input: AutocompleteInput, onResults: OmniboxUpdateCallback): void; 12 | /** 13 | * Stops any ongoing asynchronous operations for the current query. 14 | */ 15 | stop(): void; 16 | } 17 | 18 | export abstract class BaseProvider implements AutocompleteProvider { 19 | abstract name: string; 20 | 21 | abstract start(input: AutocompleteInput, onResults: OmniboxUpdateCallback): void; 22 | 23 | abstract stop(): void; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/browser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Main entrypoint after conditions met in index.ts 3 | */ 4 | 5 | // Import everything 6 | import "@/controllers"; 7 | import "@/ipc"; 8 | import "@/modules/content-blocker"; 9 | import "@/modules/extensions/main"; 10 | import { setupPlatformIntegration } from "@/app/platform"; 11 | import { processInitialUrl } from "@/app/urls"; 12 | import { setupSecondInstanceHandling } from "@/app/instance"; 13 | import { runOnboardingOrInitialWindow } from "@/app/onboarding"; 14 | import { setupAppLifecycle } from "@/app/lifecycle"; 15 | 16 | // Handle initial URL (runs asynchronously) 17 | processInitialUrl(); 18 | 19 | // Setup second instance handler 20 | setupSecondInstanceHandling(); 21 | 22 | // Setup platform specific features 23 | setupPlatformIntegration(); 24 | 25 | // Open onboarding / create initial window 26 | runOnboardingOrInitialWindow(); 27 | 28 | // App lifecycle events 29 | setupAppLifecycle(); 30 | -------------------------------------------------------------------------------- /src/main/app/lifecycle.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from "electron"; 2 | import { handleOpenUrl } from "@/app/urls"; 3 | import { hasCompletedOnboarding } from "@/saving/onboarding"; 4 | import { browserWindowsController } from "@/controllers/windows-controller/interfaces/browser"; 5 | 6 | export function setupAppLifecycle() { 7 | app.on("window-all-closed", () => { 8 | if (process.platform !== "darwin") { 9 | app.quit(); 10 | return; 11 | } 12 | 13 | hasCompletedOnboarding().then((completed) => { 14 | if (!completed) { 15 | app.quit(); 16 | } 17 | }); 18 | }); 19 | 20 | app.whenReady().then(() => { 21 | app.on("activate", () => { 22 | if (BrowserWindow.getAllWindows().length === 0) { 23 | browserWindowsController.create(); 24 | } 25 | }); 26 | }); 27 | 28 | app.on("open-url", async (_event, url) => { 29 | handleOpenUrl(false, url); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/README.md: -------------------------------------------------------------------------------- 1 | # SessionsController 2 | 3 | This handles the raw sessions from `electron` and configure them so they are useful for the browser. 4 | 5 | ## Components 6 | 7 | - `./index.ts` - Main interface exposed to grab and create sessions 8 | - `./default-session` - Handles the default session and its initialization 9 | - `./handlers` - Handles the callbacks and permissions for the sessions 10 | - `./intercept-rules` - Handles the intercept rules for the sessions 11 | - `./preload-scripts` - Handles the preload scripts for the sessions 12 | - `./protocols` - Handles all the custom protocols and let them be registered to sessions 13 | - `./web-requests` - Handles the web requests for the sessions 14 | 15 | ## External Usage 16 | 17 | - The only thing that should be required outside of this controller is the `index.ts` file. 18 | - All other files are internal and should not be used outside of this controller. 19 | -------------------------------------------------------------------------------- /src/renderer/public/chrome-dino-game/appmanifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chrome Dino", 3 | "short_name": "Chrome Dino", 4 | "description": "Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape.", 5 | "version": "2.0", 6 | "start_url": "index.html", 7 | "display": "fullscreen", 8 | "orientation": "any", 9 | "background_color": "#ffffff", 10 | "icons": [ 11 | { 12 | "src": "icons/icon-16.png", 13 | "sizes": "16x16", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "icons/icon-32.png", 18 | "sizes": "32x32", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "icons/icon-114.png", 23 | "sizes": "114x114", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "icons/icon-128.png", 28 | "sizes": "128x128", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "icons/icon-256.png", 33 | "sizes": "256x256", 34 | "type": "image/png" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /src/shared/flow/interfaces/app/extensions.ts: -------------------------------------------------------------------------------- 1 | import { IPCListener } from "~/flow/types"; 2 | import { SharedExtensionData } from "~/types/extensions"; 3 | 4 | // API // 5 | export interface FlowExtensionsAPI { 6 | /** 7 | * Get all extensions in the current profile 8 | */ 9 | getAllInCurrentProfile: () => Promise; 10 | 11 | /** 12 | * Listen for updates to the extensions in the current profile 13 | */ 14 | onUpdated: IPCListener<[string, SharedExtensionData[]]>; 15 | 16 | /** 17 | * Set the enabled state of an extension 18 | */ 19 | setExtensionEnabled: (extensionId: string, enabled: boolean) => Promise; 20 | 21 | /** 22 | * Uninstall an extension 23 | */ 24 | uninstallExtension: (extensionId: string) => Promise; 25 | 26 | /** 27 | * Set the pinned state of an extension 28 | */ 29 | setExtensionPinned: (extensionId: string, pinned: boolean) => Promise; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/controllers/app-menu-controller/menu/items/edit.ts: -------------------------------------------------------------------------------- 1 | import { MenuItemConstructorOptions } from "electron"; 2 | import { getFocusedBrowserWindow } from "../helpers"; 3 | import { fireCopyLinkAction } from "@/ipc/app/actions"; 4 | import { getCurrentShortcut } from "@/modules/shortcuts"; 5 | 6 | export const createEditMenu = (): MenuItemConstructorOptions => ({ 7 | label: "Edit", 8 | submenu: [ 9 | { role: "undo" }, 10 | { role: "redo" }, 11 | { type: "separator" }, 12 | { role: "cut" }, 13 | { role: "copy" }, 14 | { 15 | label: "Copy URL", 16 | accelerator: getCurrentShortcut("tab.copyUrl"), 17 | click: () => { 18 | const window = getFocusedBrowserWindow(); 19 | if (!window) return; 20 | return fireCopyLinkAction(window); 21 | } 22 | }, 23 | { role: "paste" }, 24 | { role: "pasteAndMatchStyle" }, 25 | { role: "delete" }, 26 | { role: "selectAll" } 27 | ] 28 | }); 29 | -------------------------------------------------------------------------------- /src/main/ipc/app/shortcuts.ts: -------------------------------------------------------------------------------- 1 | import { sendMessageToListeners } from "@/ipc/listeners-manager"; 2 | import { getShortcuts } from "@/modules/shortcuts"; 3 | import { resetModifiedShortcut, shortcutsEmitter, updateModifiedShortcut } from "@/saving/shortcuts"; 4 | import { ipcMain } from "electron"; 5 | 6 | ipcMain.handle("shortcuts:get-all", () => { 7 | const shortcuts = getShortcuts(); 8 | return shortcuts; 9 | }); 10 | 11 | ipcMain.handle("shortcuts:set", (_event, actionId: string, shortcut: string) => { 12 | const success = updateModifiedShortcut(actionId, { 13 | newShortcut: shortcut 14 | }); 15 | return success; 16 | }); 17 | 18 | ipcMain.handle("shortcuts:reset", (_event, actionId: string) => { 19 | const success = resetModifiedShortcut(actionId); 20 | return success; 21 | }); 22 | 23 | shortcutsEmitter.on("shortcuts-changed", () => { 24 | const shortcuts = getShortcuts(); 25 | sendMessageToListeners("shortcuts:on-changed", shortcuts); 26 | }); 27 | -------------------------------------------------------------------------------- /scripts/electron-upgrader/current.ts: -------------------------------------------------------------------------------- 1 | import { findLatestCurrentMajorVersion, getCommitHashForTag } from "./_modules/github"; 2 | import { updateBunLock, updatePackageJson } from "./_modules/updater"; 3 | 4 | // GRAB RELEASE FROM GITHUB // 5 | const latestCurrentVersion = await findLatestCurrentMajorVersion(); 6 | 7 | if (!latestCurrentVersion) { 8 | throw new Error("No version found in current major version"); 9 | } 10 | 11 | const commitHash = await getCommitHashForTag(latestCurrentVersion); 12 | 13 | if (!commitHash) { 14 | throw new Error("No commit hash found"); 15 | } 16 | 17 | console.log(`Latest version in current major version: ${latestCurrentVersion}`); 18 | console.log(`Commit hash: ${commitHash}`); 19 | 20 | // UPDATE PACKAGE.JSON // 21 | updatePackageJson(latestCurrentVersion); 22 | 23 | console.log("package.json updated!"); 24 | 25 | // UPDATE BUN.LOCK // 26 | updateBunLock(latestCurrentVersion, commitHash); 27 | 28 | console.log("bun.lock updated!"); 29 | -------------------------------------------------------------------------------- /scripts/frontend-routes-generator/pruner.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { FRONTEND_PATH, ROUTES_PATH, getDirectories } from "./common"; 3 | import fs from "fs/promises"; 4 | 5 | const emptyFn = () => {}; 6 | 7 | async function pruneRoutes() { 8 | // Grab all the routes 9 | const routes = await getDirectories(ROUTES_PATH); 10 | 11 | for (const route of routes) { 12 | // Remove all the route-*.html files 13 | const htmlPath = path.join(FRONTEND_PATH, `route-${route}.html`); 14 | await fs.rm(htmlPath).catch(emptyFn); 15 | 16 | // Remove all the main.tsx files 17 | const entrypointPath = path.join(ROUTES_PATH, route, "main.tsx"); 18 | await fs.rm(entrypointPath).catch(emptyFn); 19 | } 20 | } 21 | 22 | pruneRoutes() 23 | .then(() => { 24 | console.log("Routes pruned successfully"); 25 | process.exit(0); 26 | }) 27 | .catch((err) => { 28 | console.error("Failed to prune routes:", err); 29 | process.exit(1); 30 | }); 31 | -------------------------------------------------------------------------------- /src/renderer/src/components/settings/sections/spaces/theme-editors/space-icon.tsx: -------------------------------------------------------------------------------- 1 | import { Space } from "~/flow/interfaces/sessions/spaces"; 2 | import { SpaceIconPicker } from "../icon-picker"; 3 | import { motion } from "motion/react"; 4 | 5 | type SpaceIconEditorProps = { 6 | editedSpace: Space; 7 | updateEditedSpace: (updates: Partial) => void; 8 | }; 9 | 10 | export function SpaceIconEditor({ editedSpace, updateEditedSpace }: SpaceIconEditorProps) { 11 | return ( 12 | 18 |

Space Icon

19 | updateEditedSpace({ icon: iconId })} 22 | /> 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/controllers/app-menu-controller/menu/items/file.ts: -------------------------------------------------------------------------------- 1 | import { MenuItemConstructorOptions } from "electron"; 2 | import { getFocusedBrowserWindow } from "../helpers"; 3 | import { openNewTab } from "@/ipc/app/new-tab"; 4 | import { getCurrentShortcut } from "@/modules/shortcuts"; 5 | import { browserWindowsController } from "@/controllers/windows-controller/interfaces/browser"; 6 | 7 | export const createFileMenu = (): MenuItemConstructorOptions => ({ 8 | label: "File", 9 | submenu: [ 10 | { 11 | label: "New Tab", 12 | accelerator: getCurrentShortcut("tabs.new"), 13 | click: () => { 14 | const window = getFocusedBrowserWindow(); 15 | if (!window) return; 16 | return openNewTab(window); 17 | } 18 | }, 19 | { 20 | label: "New Window", 21 | accelerator: getCurrentShortcut("browser.newWindow"), 22 | click: () => { 23 | browserWindowsController.create(); 24 | } 25 | } 26 | ] 27 | }); 28 | -------------------------------------------------------------------------------- /src/main/saving/onboarding.ts: -------------------------------------------------------------------------------- 1 | import { browserWindowsController } from "@/controllers/windows-controller/interfaces/browser"; 2 | import { onboarding } from "@/controllers/windows-controller/interfaces/onboarding"; 3 | import { SettingsDataStore } from "@/saving/settings"; 4 | import { app } from "electron"; 5 | 6 | const ONBOARDING_KEY = "onboarding_version_completed"; 7 | const ONBOARDING_VERSION = "v0"; 8 | 9 | export async function hasCompletedOnboarding() { 10 | const onboardingData = await SettingsDataStore.get(ONBOARDING_KEY); 11 | return onboardingData === ONBOARDING_VERSION; 12 | } 13 | 14 | export async function setOnboardingCompleted() { 15 | await SettingsDataStore.set(ONBOARDING_KEY, ONBOARDING_VERSION); 16 | onboarding.hide(); 17 | 18 | if (browserWindowsController.getWindows().length === 0) { 19 | browserWindowsController.create(); 20 | } 21 | } 22 | 23 | export async function resetOnboarding() { 24 | await SettingsDataStore.remove(ONBOARDING_KEY); 25 | app.quit(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/default-session/index.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from "@/modules/utils"; 2 | import { registerProtocolsWithSession } from "../protocols"; 3 | import { app, session } from "electron"; 4 | import { setupInterceptRules } from "@/controllers/sessions-controller/intercept-rules"; 5 | import { registerPreloadScripts } from "@/controllers/sessions-controller/preload-scripts"; 6 | 7 | function initializeDefaultSession() { 8 | const defaultSession = session.defaultSession; 9 | 10 | registerProtocolsWithSession(defaultSession, ["flow", "flow-internal", "flow-external"]); 11 | 12 | setupInterceptRules(defaultSession); 13 | registerPreloadScripts(defaultSession); 14 | } 15 | 16 | export let isDefaultSessionReady = false; 17 | 18 | export const defaultSessionReady = app.whenReady().then(async () => { 19 | initializeDefaultSession(); 20 | 21 | // wait for 50 ms before returning 22 | await sleep(50); 23 | 24 | // Set the flag to true 25 | isDefaultSessionReady = true; 26 | }); 27 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/types/onboarding.ts: -------------------------------------------------------------------------------- 1 | import { BaseWindow } from "@/controllers/windows-controller/types/base"; 2 | import { BrowserWindow, nativeTheme } from "electron"; 3 | 4 | export class OnboardingWindow extends BaseWindow { 5 | constructor() { 6 | const browserWindow = new BrowserWindow({ 7 | width: 1000, 8 | height: 700, 9 | 10 | resizable: false, 11 | center: true, 12 | show: true, 13 | frame: false, 14 | roundedCorners: true, 15 | 16 | titleBarStyle: "hidden", 17 | titleBarOverlay: { 18 | height: 20, 19 | symbolColor: nativeTheme.shouldUseDarkColors ? "white" : "black", 20 | color: "rgba(0,0,0,0)" 21 | } 22 | }); 23 | browserWindow.loadURL("flow-internal://onboarding/"); 24 | 25 | // Use settings.hide's behavior instead of the default one 26 | browserWindow.on("close", () => { 27 | browserWindow.hide(); 28 | }); 29 | 30 | super("onboarding", browserWindow); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { debugPrint } from "@/modules/output"; 2 | import { app } from "electron"; 3 | 4 | function printHeader() { 5 | if (!app.isPackaged) { 6 | console.log("\n".repeat(75)); 7 | } 8 | 9 | console.log("\x1b[34m%s\x1b[0m", "--- Flow Browser ---"); 10 | 11 | if (app.isPackaged) { 12 | console.log("\x1b[32m%s\x1b[0m", `Production Build (${app.getVersion()})`); 13 | } else { 14 | console.log("\x1b[31m%s\x1b[0m", `Development Build (${app.getVersion()})`); 15 | } 16 | 17 | console.log(""); 18 | } 19 | 20 | function initializeApp() { 21 | const gotTheLock = app.requestSingleInstanceLock(); 22 | debugPrint("INITIALIZATION", "gotTheLock", gotTheLock); 23 | 24 | if (!gotTheLock) { 25 | return false; 26 | } 27 | 28 | // Print header 29 | printHeader(); 30 | 31 | // Import everything 32 | import("@/browser"); 33 | 34 | return true; 35 | } 36 | 37 | // Start the application 38 | const initialized = initializeApp(); 39 | if (!initialized) { 40 | app.quit(); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/controllers/posthog-controller/identify.ts: -------------------------------------------------------------------------------- 1 | import { SettingsDataStore } from "@/saving/settings"; 2 | 3 | const POSTHOG_IDENTIFIER_STORE_KEY = "posthog-anon-id"; 4 | 5 | let posthogIdentifierPromise: Promise | undefined; 6 | 7 | /** 8 | * Get the PostHog identifier. 9 | * This should only be used internally by the Posthog controller. 10 | * @returns The PostHog identifier. 11 | */ 12 | export async function _getPosthogIdentifier(): Promise { 13 | if (!posthogIdentifierPromise) { 14 | posthogIdentifierPromise = cachePosthogIdentifier(); 15 | } 16 | return await posthogIdentifierPromise; 17 | } 18 | 19 | async function cachePosthogIdentifier(): Promise { 20 | const existingAnonId = await SettingsDataStore.get(POSTHOG_IDENTIFIER_STORE_KEY); 21 | if (!existingAnonId) { 22 | const newAnonUserId = crypto.randomUUID(); 23 | await SettingsDataStore.set(POSTHOG_IDENTIFIER_STORE_KEY, newAnonUserId); 24 | return newAnonUserId; 25 | } 26 | return existingAnonId; 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/src/components/browser-ui/sidebar/hover-detector.tsx: -------------------------------------------------------------------------------- 1 | import { SidebarSide } from "@/components/browser-ui/main"; 2 | import { cn } from "@/lib/utils"; 3 | import { useCallback, useEffect, useRef } from "react"; 4 | 5 | export function SidebarHoverDetector({ side, started }: { side: SidebarSide; started: () => void }) { 6 | const ref = useRef(null); 7 | 8 | const onMouseEnter = useCallback(() => { 9 | started(); 10 | }, [started]); 11 | 12 | useEffect(() => { 13 | const component = ref.current; 14 | 15 | if (component) { 16 | component.addEventListener("mouseenter", onMouseEnter); 17 | } 18 | 19 | return () => { 20 | if (component) { 21 | component.removeEventListener("mouseenter", onMouseEnter); 22 | } 23 | }; 24 | }, [onMouseEnter]); 25 | 26 | return ( 27 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ); 19 | } 20 | 21 | export { Input }; 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" 12 | }, 13 | "runtimeArgs": ["--sourcemap"], 14 | "env": { 15 | "REMOTE_DEBUGGING_PORT": "9222" 16 | } 17 | }, 18 | { 19 | "name": "Debug Renderer Process", 20 | "port": 9222, 21 | "request": "attach", 22 | "type": "chrome", 23 | "webRoot": "${workspaceFolder}/src/renderer", 24 | "timeout": 60000, 25 | "presentation": { 26 | "hidden": true 27 | } 28 | } 29 | ], 30 | "compounds": [ 31 | { 32 | "name": "Debug All", 33 | "configurations": ["Debug Main Process", "Debug Renderer Process"], 34 | "presentation": { 35 | "order": 1 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/protocols/_protocols/flow/index.ts: -------------------------------------------------------------------------------- 1 | import { registerFaviconRoutes } from "./favicon"; 2 | import { registerAssetsRoutes } from "./assets"; 3 | import { registerExtensionIconRoutes } from "./extension-icon"; 4 | import { registerPdfCacheRoutes } from "./pdf-cache"; 5 | import { transformPathForRequest } from "../../utils"; 6 | import { type Protocol } from "electron"; 7 | import { Hono } from "hono/tiny"; 8 | import { registerStaticDomainsRoutes } from "../../static-domains"; 9 | 10 | // Create Hono App 11 | const app = new Hono({ 12 | getPath: transformPathForRequest 13 | }); 14 | export type HonoApp = typeof app; 15 | 16 | // Register Routes 17 | registerFaviconRoutes(app); 18 | registerAssetsRoutes(app); 19 | registerExtensionIconRoutes(app); 20 | registerPdfCacheRoutes(app); 21 | 22 | // Catch-all Route 23 | registerStaticDomainsRoutes("flow", app); 24 | 25 | // Export Protocol Handler 26 | export function registerFlowProtocol(protocol: Protocol) { 27 | protocol.handle("flow", async (request) => { 28 | return app.fetch(request); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/ipc/window/settings.ts: -------------------------------------------------------------------------------- 1 | import { sendMessageToListeners } from "@/ipc/listeners-manager"; 2 | import { BasicSettings, BasicSettingCards } from "@/modules/basic-settings"; 3 | import { getSettingValueById, setSettingValueById } from "@/saving/settings"; 4 | import { settings } from "@/controllers/windows-controller/interfaces/settings"; 5 | import { ipcMain } from "electron"; 6 | 7 | ipcMain.on("settings:open", () => { 8 | settings.show(); 9 | }); 10 | 11 | ipcMain.on("settings:close", () => { 12 | settings.hide(); 13 | }); 14 | 15 | ipcMain.handle("settings:get-setting", (_event, settingId: string) => { 16 | return getSettingValueById(settingId); 17 | }); 18 | 19 | ipcMain.handle("settings:set-setting", (_event, settingId: string, value: unknown) => { 20 | return setSettingValueById(settingId, value); 21 | }); 22 | 23 | ipcMain.handle("settings:get-basic-settings", () => { 24 | return { 25 | settings: BasicSettings, 26 | cards: BasicSettingCards 27 | }; 28 | }); 29 | 30 | export function fireOnSettingsChanged() { 31 | sendMessageToListeners("settings:on-changed"); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/types/settings.ts: -------------------------------------------------------------------------------- 1 | import { BaseWindow } from "@/controllers/windows-controller/types/base"; 2 | import { BrowserWindow, nativeTheme } from "electron"; 3 | 4 | export class SettingsWindow extends BaseWindow { 5 | constructor() { 6 | const browserWindow = new BrowserWindow({ 7 | width: 800, 8 | minWidth: 800, 9 | height: 600, 10 | minHeight: 600, 11 | 12 | center: true, 13 | show: false, 14 | frame: false, 15 | roundedCorners: true, 16 | 17 | titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "hidden", 18 | titleBarOverlay: { 19 | height: 40, 20 | symbolColor: nativeTheme.shouldUseDarkColors ? "white" : "black", 21 | color: "rgba(0,0,0,0)" 22 | } 23 | }); 24 | browserWindow.loadURL("flow-internal://settings/"); 25 | 26 | // Use settings.hide's behavior instead of the default one 27 | browserWindow.on("close", () => { 28 | browserWindow.hide(); 29 | }); 30 | 31 | super("settings", browserWindow, { deferShowUntilAfterLoad: true }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/renderer/src/lib/search.ts: -------------------------------------------------------------------------------- 1 | export function createSearchUrl(query: string) { 2 | return `https://www.google.com/search?q=${encodeURIComponent(query)}`; 3 | } 4 | 5 | type SearchSuggestions = string[]; 6 | 7 | interface GoogleSuggestResponse { 8 | 0: string; // Original query 9 | 1: string[]; // Suggested queries 10 | 2: string[]; // Description/unused array 11 | 3: unknown[]; // Unknown/unused array 12 | 4: { 13 | // Metadata 14 | "google:clientdata": { 15 | bpc: boolean; 16 | tlw: boolean; 17 | }; 18 | "google:suggestrelevance": number[]; 19 | "google:suggestsubtypes": number[][]; 20 | "google:suggesttype": string[]; 21 | "google:verbatimrelevance": number; 22 | }; 23 | } 24 | 25 | export async function getSearchSuggestions(query: string, signal?: AbortSignal): Promise { 26 | const baseURL = `https://suggestqueries.google.com/complete/search?client=chrome&q=${encodeURIComponent(query)}`; 27 | const response = await fetch(baseURL, { signal }); 28 | const data = (await response.json()) as GoogleSuggestResponse; 29 | return data[1]; 30 | } 31 | -------------------------------------------------------------------------------- /docs/contributing/hot-reloading.md: -------------------------------------------------------------------------------- 1 | # Hot Reloading 2 | 3 | This guide explains how to set up hot reloading for Flow Browser development. 4 | 5 | ## Prerequisites 6 | 7 | - Ensure you have installed all the dependencies as mentioned in the main [CONTRIBUTING.md](../../CONTRIBUTING.md). 8 | - Make sure port 5173 is available on your system. 9 | 10 | ## Setting Up Hot Reloading 11 | 12 | Hot reloading allows you to see your changes in real-time without manually restarting the application. To enable hot reloading: 13 | 14 | 1. Make sure port 5173 is empty and ready. 15 | 16 | 2. Run the development server in a separate terminal: 17 | 18 | ```bash 19 | bun run dev:server 20 | ``` 21 | 22 | 3. In another terminal, start Flow Browser: 23 | ```bash 24 | bun run start 25 | ``` 26 | 27 | ## Troubleshooting 28 | 29 | - If you encounter errors about port 5173 being in use, find and terminate the process using that port. 30 | - For macOS/Linux: 31 | ```bash 32 | lsof -i :5173 33 | kill -9 34 | ``` 35 | - For Windows: 36 | ```cmd 37 | netstat -ano | findstr :5173 38 | taskkill /PID /F 39 | ``` 40 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import tseslint from "@electron-toolkit/eslint-config-ts"; 2 | import eslintPluginReact from "eslint-plugin-react"; 3 | import eslintPluginReactHooks from "eslint-plugin-react-hooks"; 4 | import eslintPluginReactRefresh from "eslint-plugin-react-refresh"; 5 | 6 | export default tseslint.config( 7 | { ignores: ["**/node_modules", "**/dist", "**/out", "**/public"] }, 8 | tseslint.configs.recommended, 9 | eslintPluginReact.configs.flat.recommended, 10 | eslintPluginReact.configs.flat["jsx-runtime"], 11 | { 12 | settings: { 13 | react: { 14 | version: "detect" 15 | } 16 | } 17 | }, 18 | { 19 | files: ["**/*.{ts,tsx}"], 20 | plugins: { 21 | "react-hooks": eslintPluginReactHooks, 22 | "react-refresh": eslintPluginReactRefresh 23 | }, 24 | rules: { 25 | ...eslintPluginReactHooks.configs.recommended.rules, 26 | ...eslintPluginReactRefresh.configs.vite.rules 27 | } 28 | }, 29 | { 30 | rules: { 31 | "@typescript-eslint/explicit-function-return-type": "off", 32 | "react-refresh/only-export-components": "off" 33 | } 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /src/renderer/public/edge-surf-game-v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Edge Surf v2 14 | 15 | 16 | 17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /scripts/electron-upgrader/next.ts: -------------------------------------------------------------------------------- 1 | import { findLatestNextMajorVersion, getCommitHashForTag } from "./_modules/github"; 2 | import { incrementElectronUpdaterVersionConfiguration, updateBunLock, updatePackageJson } from "./_modules/updater"; 3 | 4 | // GRAB RELEASE FROM GITHUB // 5 | const latestNextVersion = await findLatestNextMajorVersion(); 6 | 7 | if (!latestNextVersion) { 8 | throw new Error("No version found in next major version"); 9 | } 10 | 11 | const commitHash = await getCommitHashForTag(latestNextVersion); 12 | 13 | if (!commitHash) { 14 | throw new Error("No commit hash found"); 15 | } 16 | 17 | console.log(`Latest version in next major version: ${latestNextVersion}`); 18 | console.log(`Commit hash: ${commitHash}`); 19 | 20 | // UPDATE PACKAGE.JSON // 21 | updatePackageJson(latestNextVersion); 22 | 23 | console.log("package.json updated!"); 24 | 25 | // UPDATE BUN.LOCK // 26 | updateBunLock(latestNextVersion, commitHash); 27 | 28 | console.log("bun.lock updated!"); 29 | 30 | // UPDATE ELECTRON UPDATER VERSION CONFIGURATION // 31 | incrementElectronUpdaterVersionConfiguration(); 32 | 33 | console.log("Updater Version Configuration updated!"); 34 | -------------------------------------------------------------------------------- /src/renderer/src/lib/omnibox/types.ts: -------------------------------------------------------------------------------- 1 | /** Represents the input state for an autocomplete query. */ 2 | export interface AutocompleteInput { 3 | text: string; // The text entered by the user 4 | currentURL?: string; // The URL of the current page (context) 5 | type: "focus" | "keystroke"; // Why the query is being run 6 | preventInlineAutocomplete?: boolean; // Hint to providers 7 | } 8 | 9 | /** Represents a single autocomplete suggestion. */ 10 | export interface AutocompleteMatch { 11 | providerName: string; // Name of the provider that generated this match 12 | relevance: number; // Score indicating importance (higher is better) 13 | contents: string; // Text displayed in the main line of the suggestion 14 | description?: string; // Text displayed in the second line (optional) 15 | destinationUrl: string; // The URL to navigate to or the search query URL 16 | type: "history-url" | "zero-suggest" | "verbatim" | "url-what-you-typed" | "search-query" | "open-tab" | "pedal"; 17 | isDefault?: boolean; // Hint if this could be the default action on Enter 18 | inlineCompletion?: string; // Text suggested for inline completion in the omnibox 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/src/lib/phosphor-icons.tsx: -------------------------------------------------------------------------------- 1 | import { IconEntry, icons } from "@phosphor-icons/core"; 2 | import * as IconComponents from "@phosphor-icons/react"; 3 | import type { IconProps } from "@phosphor-icons/react"; 4 | import { ComponentProps } from "react"; 5 | 6 | export const PhosphorIcons = icons as unknown as IconEntry[]; 7 | 8 | type IconId = keyof typeof IconComponents; 9 | 10 | export function PhosphorIcon({ 11 | id, 12 | fallbackId, 13 | ...props 14 | }: { id: IconId | (string & Record); fallbackId?: string } & IconProps) { 15 | const Icon = IconComponents[id as IconId]; 16 | 17 | // Check if Icon is a function (component type) 18 | if ((Icon && typeof Icon === "function") || typeof Icon === "object") { 19 | const IconComponent = Icon as React.ComponentType; 20 | return ; 21 | } 22 | 23 | if (fallbackId && fallbackId !== id) { 24 | return ; 25 | } 26 | 27 | return null; 28 | } 29 | 30 | export function SpaceIcon({ ...props }: ComponentProps) { 31 | return ; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/interfaces/settings.ts: -------------------------------------------------------------------------------- 1 | // This is for other controllers to interface with the settings window 2 | import { windowsController } from "@/controllers/windows-controller"; 3 | 4 | const settingsWindowManager = () => windowsController.settings; 5 | 6 | export const settings = { 7 | show: async () => { 8 | const window = settingsWindowManager().getSingletonWindow(); 9 | await window.show(); 10 | }, 11 | hide: () => { 12 | const window = settingsWindowManager().getSingletonWindow(); 13 | window.destroy(); 14 | }, 15 | isVisible: () => { 16 | const window = settingsWindowManager().getExistingSingletonWindow(); 17 | if (!window) return false; 18 | return window.isVisible(); 19 | }, 20 | toggle: () => { 21 | if (settings.isVisible()) { 22 | settings.hide(); 23 | } else { 24 | settings.show(); 25 | } 26 | }, 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 29 | sendMessage: (channel: string, ...args: any[]) => { 30 | const window = settingsWindowManager().getExistingSingletonWindow(); 31 | if (!window) return; 32 | window.sendMessage(channel, ...args); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/interfaces/onboarding.ts: -------------------------------------------------------------------------------- 1 | // This is for other controllers to interface with the onboarding window 2 | import { windowsController } from "@/controllers/windows-controller"; 3 | 4 | const onboardingWindowManager = () => windowsController.onboarding; 5 | 6 | export const onboarding = { 7 | show: async () => { 8 | const window = onboardingWindowManager().getSingletonWindow(); 9 | await window.show(); 10 | }, 11 | hide: () => { 12 | const window = onboardingWindowManager().getSingletonWindow(); 13 | window.destroy(); 14 | }, 15 | isVisible: () => { 16 | const window = onboardingWindowManager().getExistingSingletonWindow(); 17 | if (!window) return false; 18 | return window.isVisible(); 19 | }, 20 | toggle: () => { 21 | if (onboarding.isVisible()) { 22 | onboarding.hide(); 23 | } else { 24 | onboarding.show(); 25 | } 26 | }, 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 29 | sendMessage: (channel: string, ...args: any[]) => { 30 | const window = onboardingWindowManager().getExistingSingletonWindow(); 31 | if (!window) return; 32 | window.sendMessage(channel, ...args); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/main/controllers/sessions-controller/intercept-rules/user-agent-transformer.ts: -------------------------------------------------------------------------------- 1 | import { transformUserAgentHeader } from "@/modules/user-agent"; 2 | import { createBetterWebRequest } from "@/controllers/sessions-controller/web-requests"; 3 | import type { Session } from "electron"; 4 | 5 | export function setupUserAgentTransformer(session: Session) { 6 | const webRequest = createBetterWebRequest(session.webRequest, "user-agent-transformer"); 7 | 8 | webRequest.onBeforeSendHeaders((details, callback) => { 9 | let updated = false; 10 | 11 | const url = URL.parse(details.url); 12 | 13 | const requestHeaders = details.requestHeaders; 14 | const newHeaders = { ...requestHeaders }; 15 | for (const header of Object.keys(requestHeaders)) { 16 | if (header.toLowerCase() == "user-agent") { 17 | const oldValue = requestHeaders[header]; 18 | const newValue = transformUserAgentHeader(oldValue, url); 19 | if (oldValue !== newValue) { 20 | newHeaders[header] = newValue; 21 | updated = true; 22 | } 23 | } 24 | } 25 | 26 | if (updated) { 27 | callback({ requestHeaders: newHeaders }); 28 | } else { 29 | callback({}); 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/controllers/app-menu-controller/menu/items/app.ts: -------------------------------------------------------------------------------- 1 | import { MenuItemConstructorOptions } from "electron"; 2 | import { settings } from "@/controllers/windows-controller/interfaces/settings"; 3 | import { getCurrentShortcut } from "@/modules/shortcuts"; 4 | import { defaultBrowserController } from "@/controllers/default-browser-controller"; 5 | 6 | export const createAppMenu = (): MenuItemConstructorOptions => ({ 7 | role: "appMenu", 8 | submenu: [ 9 | { role: "about" }, 10 | { type: "separator" }, 11 | { 12 | label: "Settings", 13 | accelerator: getCurrentShortcut("browser.openSettings"), 14 | click: () => { 15 | settings.show(); 16 | } 17 | }, 18 | { 19 | type: "checkbox", 20 | label: "Set as Default Browser", 21 | click: () => { 22 | defaultBrowserController.setDefaultBrowser(); 23 | }, 24 | checked: defaultBrowserController.isDefaultBrowser(), 25 | enabled: !defaultBrowserController.isDefaultBrowser() 26 | }, 27 | { role: "services" }, 28 | { type: "separator" }, 29 | { role: "hide" }, 30 | { role: "hideOthers" }, 31 | { role: "showAllTabs" }, 32 | { type: "separator" }, 33 | { role: "quit" } 34 | ] 35 | }); 36 | -------------------------------------------------------------------------------- /src/main/controllers/windows-controller/utils/close-preventer.ts: -------------------------------------------------------------------------------- 1 | // Prevent Ctrl+W on Windows from closing the entire windows 2 | // So we can process it ourselves by closing the focused tab. 3 | 4 | import { app, type WebContents, webContents } from "electron"; 5 | import { menuCloseTab } from "@/controllers/app-menu-controller/menu/items/view"; 6 | 7 | const enabled = process.platform === "win32"; 8 | 9 | const registeredWebContentIds: Set = new Set(); 10 | 11 | function newWebContents(webContents: WebContents) { 12 | if (!enabled) return; 13 | 14 | if (registeredWebContentIds.has(webContents.id)) return; 15 | registeredWebContentIds.add(webContents.id); 16 | 17 | webContents.on("before-input-event", (event, input) => { 18 | if (input.key === "w" && input.control) { 19 | event.preventDefault(); 20 | 21 | if (input.type === "keyDown") { 22 | menuCloseTab(); 23 | } 24 | } 25 | }); 26 | } 27 | 28 | function scan() { 29 | webContents.getAllWebContents().forEach((webContents) => { 30 | newWebContents(webContents); 31 | }); 32 | } 33 | 34 | if (enabled) { 35 | scan(); 36 | app.on("web-contents-created", (_event, webContents) => { 37 | newWebContents(webContents); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/renderer/src/components/main/website-favicon.tsx: -------------------------------------------------------------------------------- 1 | import { GlobeIcon } from "lucide-react"; 2 | import { useState } from "react"; 3 | 4 | export function WebsiteFavicon({ url, favicon, className }: { url: string; favicon?: string; className?: string }) { 5 | const [useFlowUtility, setUseFlowUtility] = useState(true); 6 | const [useCustomFavicon, setUseCustomFavicon] = useState(false); 7 | 8 | if (useFlowUtility) { 9 | const srcUrl = new URL("flow://favicon"); 10 | srcUrl.searchParams.set("url", url); 11 | return ( 12 | Favicon { 17 | setUseFlowUtility(false); 18 | if (favicon) { 19 | setUseCustomFavicon(true); 20 | } 21 | }} 22 | /> 23 | ); 24 | } 25 | 26 | if (useCustomFavicon && favicon) { 27 | return ( 28 | Favicon setUseCustomFavicon(false)} 33 | crossOrigin="anonymous" 34 | referrerPolicy="no-referrer" 35 | /> 36 | ); 37 | } 38 | 39 | return ; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/controllers/quit-controller/handlers/before-quit.ts: -------------------------------------------------------------------------------- 1 | import { loadedProfilesController } from "@/controllers/loaded-profiles-controller"; 2 | import { sleep } from "@/modules/utils"; 3 | 4 | async function flushSessionsData() { 5 | const promises: Promise[] = []; 6 | 7 | const loadedProfileSessions = loadedProfilesController.loadedProfileSessions; 8 | 9 | for (const session of loadedProfileSessions) { 10 | // Flush storage data 11 | session.flushStorageData(); 12 | 13 | // Flush cookies 14 | const cookies = session.cookies; 15 | promises.push(cookies.flushStore()); 16 | } 17 | 18 | console.log("Flushed data for", loadedProfileSessions.size, "sessions"); 19 | 20 | await Promise.all(promises); 21 | await sleep(50); 22 | 23 | return true; 24 | } 25 | 26 | // Insert Logic here to handle before the app quits 27 | // If the handler returns true, the app will quit normally 28 | // If the handler returns false, the quit will be cancelled 29 | export function beforeQuit(): boolean | Promise { 30 | const flushSessionsDataPromise = flushSessionsData() 31 | .then(() => true) 32 | .catch(() => true); 33 | 34 | return Promise.all([flushSessionsDataPromise]).then((results) => { 35 | return results.every((result) => result); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /docs/references/omnibox-scores.md: -------------------------------------------------------------------------------- 1 | ## Omnibox Scores 2 | 3 | This document outlines the approximate relevance score ranges used by different omnibox providers. Higher scores appear higher in the suggestion list. 4 | 5 | - **~1100 - 1500:** Open Tab (Switch) - _Encourages switching to existing tabs. Score scales with title/URL similarity._ 6 | - **1400 - 1450+:** History URL (Exact Match) - _Very high score for exact URL matches in history._ 7 | - **1300:** Verbatim Search / Typed URL - _Fixed score for the direct "Search for..." or typed URL entry._ 8 | - **1100 - 1200:** Pedal - _Browser actions triggered by keywords (e.g., "settings"). Score scales with trigger similarity._ 9 | - **~900 - 1450:** History URL (Similarity/Prefix Match) - _Score based on URL/title similarity, visit/typed counts, and prefix matching._ 10 | - **~600 - 1000:** Search Suggestion - _Fetched suggestions, ranked by provider order and similarity to input._ 11 | - **350 - 800:** Zero Suggest (Recent Tabs) - _Suggestions shown before typing, ranked by tab recency._ 12 | - **500 - 700:** Zero Suggest (Most Visited History) - _Suggestions shown before typing, ranked by visit count._ 13 | 14 | _Note: Ranges starting with '~' indicate approximate values as the final score can depend dynamically on factors like string similarity or history counts._ 15 | -------------------------------------------------------------------------------- /src/main/modules/flags.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron"; 2 | import { DEBUG_AREA } from "./output"; 3 | 4 | type Flags = { 5 | SCRUBBED_USER_AGENT: boolean; 6 | ERROR_PAGE_LOAD_MODE: "replace" | "load"; 7 | SHOW_DEBUG_PRINTS: boolean; 8 | SHOW_DEBUG_ERRORS: boolean | DEBUG_AREA[]; 9 | DEBUG_DISABLE_TAB_VIEW: boolean; 10 | DEBUG_HOT_RELOAD_FRONTEND: boolean; 11 | SHOW_DEBUG_DEVTOOLS: boolean; 12 | GLANCE_ENABLED: boolean; 13 | FAVICONS_REMOVE_PATH: boolean; 14 | }; 15 | 16 | export const FLAGS: Flags = { 17 | // Transform the user agent 18 | SCRUBBED_USER_AGENT: true, 19 | 20 | // Replace - Use window.location.replace to load the error page. 21 | // Load - Add the page to the history stack by loading it normally. 22 | ERROR_PAGE_LOAD_MODE: "replace", 23 | 24 | // Debug: Prints & Errors 25 | SHOW_DEBUG_PRINTS: true, 26 | SHOW_DEBUG_ERRORS: true, 27 | SHOW_DEBUG_DEVTOOLS: !app.isPackaged, 28 | 29 | // Debug: Disable the tab view 30 | DEBUG_DISABLE_TAB_VIEW: false, 31 | 32 | // Debug: Enable the hot reload feature for frontend (Experimental / Unstable) 33 | DEBUG_HOT_RELOAD_FRONTEND: true, 34 | 35 | // Glance: Enable the glance feature 36 | GLANCE_ENABLED: false, 37 | 38 | // Favicons: Remove the path from the favicon URL 39 | FAVICONS_REMOVE_PATH: true 40 | }; 41 | -------------------------------------------------------------------------------- /src/shared/types/tabs.ts: -------------------------------------------------------------------------------- 1 | export type TabGroupMode = "normal" | "glance" | "split"; 2 | 3 | export type NavigationEntry = { 4 | title: string; 5 | url: string; 6 | }; 7 | 8 | export type TabData = { 9 | id: number; 10 | uniqueId: string; 11 | createdAt: number; 12 | lastActiveAt: number; 13 | position: number; 14 | 15 | profileId: string; 16 | spaceId: string; 17 | windowId: number; 18 | 19 | title: string; 20 | url: string; 21 | isLoading: boolean; 22 | audible: boolean; 23 | muted: boolean; 24 | fullScreen: boolean; 25 | isPictureInPicture: boolean; 26 | faviconURL: string | null; 27 | asleep: boolean; 28 | 29 | navHistory: NavigationEntry[]; 30 | navHistoryIndex: number; 31 | }; 32 | 33 | export type TabGroupData = { 34 | id: number; 35 | mode: TabGroupMode; 36 | profileId: string; 37 | spaceId: string; 38 | tabIds: number[]; 39 | glanceFrontTabId?: number; 40 | position: number; 41 | }; 42 | 43 | export type WindowFocusedTabIds = { 44 | [spaceId: string]: number; 45 | }; 46 | 47 | export type WindowActiveTabIds = { 48 | [spaceId: string]: number[]; 49 | }; 50 | 51 | export type WindowTabsData = { 52 | tabs: TabData[]; 53 | tabGroups: TabGroupData[]; 54 | focusedTabIds: WindowFocusedTabIds; 55 | activeTabIds: WindowActiveTabIds; 56 | }; 57 | -------------------------------------------------------------------------------- /src/renderer/src/components/settings/settings-sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "motion/react"; 4 | import { LucideIcon } from "lucide-react"; 5 | 6 | interface Section { 7 | id: string; 8 | label: string; 9 | icon: React.ReactElement; 10 | } 11 | 12 | interface SettingsSidebarProps { 13 | activeSection: string; 14 | setActiveSection: (section: string) => void; 15 | sections: Section[]; 16 | } 17 | 18 | export function SettingsSidebar({ activeSection, setActiveSection, sections }: SettingsSidebarProps) { 19 | return ( 20 |
21 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/renderer/src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Switch as SwitchPrimitive } from "radix-ui"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Switch({ className, ...props }: React.ComponentProps) { 9 | return ( 10 | 18 | 24 | 25 | ); 26 | } 27 | 28 | export { Switch }; 29 | -------------------------------------------------------------------------------- /src/renderer/src/components/browser-ui/sidebar/content/new-tab-button.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "motion/react"; 2 | import { useState } from "react"; 3 | import { SidebarMenuButton } from "@/components/ui/resizable-sidebar"; 4 | import { PlusIcon } from "lucide-react"; 5 | import { SIDEBAR_HOVER_COLOR } from "@/components/browser-ui/browser-sidebar"; 6 | import { cn } from "@/lib/utils"; 7 | 8 | const MotionSidebarMenuButton = motion(SidebarMenuButton); 9 | 10 | export function NewTabButton() { 11 | const [isPressed, setIsPressed] = useState(false); 12 | 13 | const handleMouseDown = () => { 14 | setIsPressed(true); 15 | }; 16 | 17 | const handleNewTab = () => { 18 | flow.newTab.open(); 19 | }; 20 | 21 | return ( 22 | setIsPressed(false)} 29 | transition={{ 30 | scale: { type: "spring", stiffness: 600, damping: 20 } 31 | }} 32 | className={cn(SIDEBAR_HOVER_COLOR, "text-black/50 dark:text-muted-foreground")} 33 | > 34 | 35 | New Tab 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/src/components/onboarding/screen.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function OnboardingScreen({ children }: { children?: React.ReactNode }) { 4 | return ( 5 |
6 | {/* Set document title */} 7 | Onboarding | Flow Browser 8 | 9 | {/* Static gradient orbs */} 10 |