From 95aa86cdf0ed468b0aac54b0be2b0e1690fbc2c9 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Wed, 12 Apr 2023 22:11:47 +0800 Subject: [PATCH] fix: ws prefix url in electron (#1896) --- .github/workflows/build.yml | 45 ----- .../layers/main/src/app-state/google-auth.ts | 2 +- apps/electron/layers/main/src/main-window.ts | 1 + apps/electron/layers/main/src/protocol.ts | 13 +- apps/web/next.config.mjs | 2 +- apps/web/src/blocksuite/providers/index.ts | 6 +- apps/web/src/plugins/affine/index.tsx | 3 +- apps/web/src/shared/apis.ts | 22 +-- apps/web/src/utils/index.ts | 1 - .../__tests__/is-valid-ip-address.spec.ts | 0 packages/env/src/api.ts | 28 +++ packages/env/src/config.ts | 174 +++++++++++++++++ packages/env/src/index.ts | 176 +----------------- .../env/src}/is-valid-ip-address.ts | 0 packages/workspace/src/affine/login.ts | 11 +- packages/workspace/src/affine/sync.ts | 7 +- packages/workspace/src/providers/index.ts | 7 +- 17 files changed, 237 insertions(+), 261 deletions(-) rename {apps/web/src/utils => packages/env/src}/__tests__/is-valid-ip-address.spec.ts (100%) create mode 100644 packages/env/src/api.ts create mode 100644 packages/env/src/config.ts rename {apps/web/src/utils => packages/env/src}/is-valid-ip-address.ts (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b15ee62efe..97bc6f079e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,51 +81,6 @@ jobs: path: ./apps/web/.next if-no-files-found: error - build-macos: - name: Build macOS desktop app - runs-on: macos-latest - environment: development - needs: - - build - - install-all - strategy: - matrix: - arch: [arm64] - steps: - - uses: actions/checkout@v3 - - name: Setup Node.js - uses: ./.github/actions/setup-node - with: - electron-workspace-install: true - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: next-js - path: ./apps/web/.next - - - name: install Rust stable - uses: dtolnay/rust-toolchain@stable - - - name: add arm64 target - if: matrix.arch == 'arm64' - run: rustup target add aarch64-apple-darwin - - - name: Rust cache - uses: swatinem/rust-cache@v2 - with: - workspaces: './packages/octobase-node -> target' - - - name: make build - run: yarn make-macos-${{ matrix.arch }} - working-directory: apps/electron - - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - name: affine-darwin-${{ matrix.arch }}-builds - path: builds - storybook-test: name: Storybook Test runs-on: ubuntu-latest diff --git a/apps/electron/layers/main/src/app-state/google-auth.ts b/apps/electron/layers/main/src/app-state/google-auth.ts index c04fcb3a23..b00d8d9afa 100644 --- a/apps/electron/layers/main/src/app-state/google-auth.ts +++ b/apps/electron/layers/main/src/app-state/google-auth.ts @@ -3,7 +3,7 @@ import { fetch, ProxyAgent } from 'undici'; const redirectUri = 'https://affine.pro/client/auth-callback'; -export const oauthEndpoint = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.AFFINE_GOOGLE_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&scope=openid https://www.googleapis.com/auth/userinfo.email profile&access_type=offline`; +export const oauthEndpoint = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.AFFINE_GOOGLE_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&scope=openid https://www.googleapis.com/auth/userinfo.email profile&access_type=offline&customParameters={"prompt":"select_account"}`; const tokenEndpoint = 'https://oauth2.googleapis.com/token'; diff --git a/apps/electron/layers/main/src/main-window.ts b/apps/electron/layers/main/src/main-window.ts index 58544abdb3..7e39cc325c 100644 --- a/apps/electron/layers/main/src/main-window.ts +++ b/apps/electron/layers/main/src/main-window.ts @@ -88,5 +88,6 @@ export async function restoreOrCreateWindow() { } browserWindow.focus(); + return browserWindow; } diff --git a/apps/electron/layers/main/src/protocol.ts b/apps/electron/layers/main/src/protocol.ts index fd441d9b18..583fc59723 100644 --- a/apps/electron/layers/main/src/protocol.ts +++ b/apps/electron/layers/main/src/protocol.ts @@ -1,4 +1,4 @@ -import { protocol } from 'electron'; +import { protocol, session } from 'electron'; import { join } from 'path'; export function registerProtocol() { @@ -20,4 +20,15 @@ export function registerProtocol() { } }); } + + session.defaultSession.webRequest.onHeadersReceived( + (responseDetails, callback) => { + const { responseHeaders, url } = responseDetails; + if (responseHeaders) { + responseHeaders['Access-Control-Allow-Origin'] = ['*']; + } + + callback({ responseHeaders }); + } + ); } diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index e4ed29dfc8..9500abbb18 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -26,7 +26,7 @@ const profileTarget = { dev: '100.84.105.99:11001', test: '100.84.105.99:11001', stage: '', - prod: 'http://app.affine.pro', + prod: 'https://app.affine.pro', local: '127.0.0.1:3000', }; diff --git a/apps/web/src/blocksuite/providers/index.ts b/apps/web/src/blocksuite/providers/index.ts index 75964a8c49..e0f3c21c03 100644 --- a/apps/web/src/blocksuite/providers/index.ts +++ b/apps/web/src/blocksuite/providers/index.ts @@ -1,3 +1,4 @@ +import { websocketPrefixUrl } from '@affine/env'; import { KeckProvider } from '@affine/workspace/affine/keck'; import { getLoginStorage } from '@affine/workspace/affine/login'; import type { AffineWebSocketProvider } from '@affine/workspace/type'; @@ -19,11 +20,8 @@ const createAffineWebSocketProvider = ( webSocketProvider = null; }, connect: () => { - const wsUrl = `${ - window.location.protocol === 'https:' ? 'wss' : 'ws' - }://${window.location.host}/api/sync/`; webSocketProvider = new KeckProvider( - wsUrl, + websocketPrefixUrl + '/api/sync/', blockSuiteWorkspace.id, blockSuiteWorkspace.doc, { diff --git a/apps/web/src/plugins/affine/index.tsx b/apps/web/src/plugins/affine/index.tsx index ee2944517e..5f661e0b55 100644 --- a/apps/web/src/plugins/affine/index.tsx +++ b/apps/web/src/plugins/affine/index.tsx @@ -1,3 +1,4 @@ +import { prefixUrl } from '@affine/env'; import { currentAffineUserAtom } from '@affine/workspace/affine/atom'; import { clearLoginStorage, @@ -24,7 +25,7 @@ import { PageDetailEditor } from '../../components/page-detail-editor'; import { useAffineRefreshAuthToken } from '../../hooks/affine/use-affine-refresh-auth-token'; import { AffineSWRConfigProvider } from '../../providers/AffineSWRConfigProvider'; import { BlockSuiteWorkspace } from '../../shared'; -import { affineApis, prefixUrl } from '../../shared/apis'; +import { affineApis } from '../../shared/apis'; import { initPage, toast } from '../../utils'; import type { WorkspacePlugin } from '..'; import { QueryKey } from './fetcher'; diff --git a/apps/web/src/shared/apis.ts b/apps/web/src/shared/apis.ts index 5ac83d7a56..90fafcba2b 100644 --- a/apps/web/src/shared/apis.ts +++ b/apps/web/src/shared/apis.ts @@ -1,5 +1,5 @@ import { DebugLogger } from '@affine/debug'; -import { config } from '@affine/env'; +import { prefixUrl } from '@affine/env'; import { createUserApis, createWorkspaceApis, @@ -9,26 +9,6 @@ import type { LoginResponse } from '@affine/workspace/affine/login'; import { parseIdToken, setLoginStorage } from '@affine/workspace/affine/login'; import { jotaiStore } from '@affine/workspace/atom'; -import { isValidIPAddress } from '../utils'; - -let prefixUrl = '/'; -if (typeof window === 'undefined' || environment.isDesktop) { - // SSR or Desktop - const serverAPI = config.serverAPI; - if (isValidIPAddress(serverAPI.split(':')[0])) { - // This is for Server side rendering support - prefixUrl = new URL('http://' + config.serverAPI + '/').origin; - } else { - prefixUrl = serverAPI; - } - prefixUrl = prefixUrl.endsWith('/') ? prefixUrl : prefixUrl + '/'; -} else { - const params = new URLSearchParams(window.location.search); - params.get('prefixUrl') && (prefixUrl = params.get('prefixUrl') as string); -} - -export { prefixUrl }; - const affineApis = {} as ReturnType & ReturnType; Object.assign(affineApis, createUserApis(prefixUrl)); diff --git a/apps/web/src/utils/index.ts b/apps/web/src/utils/index.ts index 4ece3a7cce..cb84088028 100644 --- a/apps/web/src/utils/index.ts +++ b/apps/web/src/utils/index.ts @@ -1,5 +1,4 @@ export * from './blocksuite'; export * from './create-emotion-cache'; -export * from './is-valid-ip-address'; export * from './string2color'; export * from './toast'; diff --git a/apps/web/src/utils/__tests__/is-valid-ip-address.spec.ts b/packages/env/src/__tests__/is-valid-ip-address.spec.ts similarity index 100% rename from apps/web/src/utils/__tests__/is-valid-ip-address.spec.ts rename to packages/env/src/__tests__/is-valid-ip-address.spec.ts diff --git a/packages/env/src/api.ts b/packages/env/src/api.ts new file mode 100644 index 0000000000..ff4c4f6bef --- /dev/null +++ b/packages/env/src/api.ts @@ -0,0 +1,28 @@ +import { config, getEnvironment } from './config'; +import { isValidIPAddress } from './is-valid-ip-address'; + +let prefixUrl = '/'; +if (typeof window === 'undefined' || getEnvironment().isDesktop) { + // SSR or Desktop + const serverAPI = config.serverAPI; + if (isValidIPAddress(serverAPI.split(':')[0])) { + // This is for Server side rendering support + prefixUrl = new URL('http://' + config.serverAPI + '/').origin; + } else { + prefixUrl = serverAPI; + } + prefixUrl = prefixUrl.endsWith('/') ? prefixUrl : prefixUrl + '/'; +} else { + const params = new URLSearchParams(window.location.search); + if (params.get('prefixUrl')) { + prefixUrl = params.get('prefixUrl') as string; + } else { + prefixUrl = window.location.origin + '/'; + } +} + +const apiUrl = new URL(prefixUrl); +const wsProtocol = apiUrl.protocol === 'https:' ? 'wss' : 'ws'; +const websocketPrefixUrl = `${wsProtocol}://${apiUrl.host}`; + +export { prefixUrl, websocketPrefixUrl }; diff --git a/packages/env/src/config.ts b/packages/env/src/config.ts new file mode 100644 index 0000000000..eeea0f2c7f --- /dev/null +++ b/packages/env/src/config.ts @@ -0,0 +1,174 @@ +import { assertEquals } from '@blocksuite/global/utils'; +import getConfig from 'next/config'; +import { z } from 'zod'; + +import { getUaHelper } from './ua-helper'; + +export const publicRuntimeConfigSchema = z.object({ + PROJECT_NAME: z.string(), + BUILD_DATE: z.string(), + gitVersion: z.string(), + hash: z.string(), + serverAPI: z.string(), + editorVersion: z.string(), + enableIndexedDBProvider: z.boolean(), + enableBroadCastChannelProvider: z.boolean(), + prefetchWorkspace: z.boolean(), + enableDebugPage: z.boolean(), + // expose internal api to globalThis, **development only** + exposeInternal: z.boolean(), + enableSubpage: z.boolean(), + enableChangeLog: z.boolean(), +}); + +export type PublicRuntimeConfig = z.infer; + +const { publicRuntimeConfig: config } = + getConfig() ?? + ({ + publicRuntimeConfig: {}, + } as { + publicRuntimeConfig: PublicRuntimeConfig; + }); + +publicRuntimeConfigSchema.parse(config); + +type BrowserBase = { + /** + * @example https://app.affine.pro + * @example http://localhost:3000 + */ + origin: string; + isDesktop: boolean; + isBrowser: true; + isServer: false; + isDebug: boolean; + + // browser special properties + isLinux: boolean; + isMacOs: boolean; + isIOS: boolean; + isSafari: boolean; + isWindows: boolean; + isFireFox: boolean; + isMobile: boolean; + isChrome: boolean; +}; + +type NonChromeBrowser = BrowserBase & { + isChrome: false; +}; + +type ChromeBrowser = BrowserBase & { + isSafari: false; + isFireFox: false; + isChrome: true; + chromeVersion: number; +}; + +type Browser = NonChromeBrowser | ChromeBrowser; + +type Server = { + isDesktop: false; + isBrowser: false; + isServer: true; + isDebug: boolean; +}; + +interface Desktop extends ChromeBrowser { + isDesktop: true; + isBrowser: true; + isServer: false; + isDebug: boolean; +} + +export type Environment = Browser | Server | Desktop; + +let environment: Environment | null = null; + +export function getEnvironment() { + if (environment) { + return environment; + } + const isDebug = process.env.NODE_ENV === 'development'; + if (typeof window === 'undefined') { + environment = { + isDesktop: false, + isBrowser: false, + isServer: true, + isDebug, + } satisfies Server; + } else { + const uaHelper = getUaHelper(); + + environment = { + origin: window.location.origin, + isDesktop: window.appInfo?.electron, + isBrowser: true, + isServer: false, + isDebug, + isLinux: uaHelper.isLinux, + isMacOs: uaHelper.isMacOs, + isSafari: uaHelper.isSafari, + isWindows: uaHelper.isWindows, + isFireFox: uaHelper.isFireFox, + isMobile: uaHelper.isMobile, + isChrome: uaHelper.isChrome, + isIOS: uaHelper.isIOS, + } as Browser; + // Chrome on iOS is still Safari + if (environment.isChrome && !environment.isIOS) { + assertEquals(environment.isSafari, false); + assertEquals(environment.isFireFox, false); + environment = { + ...environment, + isSafari: false, + isFireFox: false, + isChrome: true, + chromeVersion: uaHelper.getChromeVersion(), + } satisfies ChromeBrowser; + } + } + globalThis.environment = environment; + return environment; +} + +function printBuildInfo() { + console.group('Build info'); + console.log('Project:', config.PROJECT_NAME); + console.log( + 'Build date:', + config.BUILD_DATE ? new Date(config.BUILD_DATE).toLocaleString() : 'Unknown' + ); + console.log('Editor Version:', config.editorVersion); + + console.log('Version:', config.gitVersion); + console.log( + 'AFFiNE is an open source project, you can view its source code on GitHub!' + ); + console.log(`https://github.com/toeverything/AFFiNE/tree/${config.hash}`); + console.groupEnd(); +} + +declare global { + // eslint-disable-next-line no-var + var environment: Environment; + // eslint-disable-next-line no-var + var $AFFINE_SETUP: boolean | undefined; + // eslint-disable-next-line no-var + var editorVersion: string | undefined; +} + +export function setupGlobal() { + if (globalThis.$AFFINE_SETUP) { + return; + } + globalThis.environment = getEnvironment(); + if (getEnvironment().isBrowser) { + printBuildInfo(); + globalThis.editorVersion = config.editorVersion; + } + globalThis.$AFFINE_SETUP = true; +} + +export { config }; diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts index d520c488af..0de38f9e82 100644 --- a/packages/env/src/index.ts +++ b/packages/env/src/index.ts @@ -1,175 +1,3 @@ -import { assertEquals } from '@blocksuite/global/utils'; -import getConfig from 'next/config'; -import { z } from 'zod'; - -import { getUaHelper } from './ua-helper'; - -export const publicRuntimeConfigSchema = z.object({ - PROJECT_NAME: z.string(), - BUILD_DATE: z.string(), - gitVersion: z.string(), - hash: z.string(), - serverAPI: z.string(), - editorVersion: z.string(), - enableIndexedDBProvider: z.boolean(), - enableBroadCastChannelProvider: z.boolean(), - prefetchWorkspace: z.boolean(), - enableDebugPage: z.boolean(), - // expose internal api to globalThis, **development only** - exposeInternal: z.boolean(), - enableSubpage: z.boolean(), - enableChangeLog: z.boolean(), -}); - -export type PublicRuntimeConfig = z.infer; - -const { publicRuntimeConfig: config } = - getConfig() ?? - ({ - publicRuntimeConfig: {}, - } as { - publicRuntimeConfig: PublicRuntimeConfig; - }); - -publicRuntimeConfigSchema.parse(config); - -type BrowserBase = { - /** - * @example https://app.affine.pro - * @example http://localhost:3000 - */ - origin: string; - isDesktop: boolean; - isBrowser: true; - isServer: false; - isDebug: boolean; - - // browser special properties - isLinux: boolean; - isMacOs: boolean; - isIOS: boolean; - isSafari: boolean; - isWindows: boolean; - isFireFox: boolean; - isMobile: boolean; - isChrome: boolean; -}; - -type NonChromeBrowser = BrowserBase & { - isChrome: false; -}; - -type ChromeBrowser = BrowserBase & { - isSafari: false; - isFireFox: false; - isChrome: true; - chromeVersion: number; -}; - -type Browser = NonChromeBrowser | ChromeBrowser; - -type Server = { - isDesktop: false; - isBrowser: false; - isServer: true; - isDebug: boolean; -}; - -interface Desktop extends ChromeBrowser { - isDesktop: true; - isBrowser: true; - isServer: false; - isDebug: boolean; -} - -export type Environment = Browser | Server | Desktop; - -let environment: Environment | null = null; - -export function getEnvironment() { - if (environment) { - return environment; - } - const isDebug = process.env.NODE_ENV === 'development'; - if (typeof window === 'undefined') { - environment = { - isDesktop: false, - isBrowser: false, - isServer: true, - isDebug, - } satisfies Server; - } else { - const uaHelper = getUaHelper(); - - environment = { - origin: window.location.origin, - isDesktop: window.appInfo?.electron, - isBrowser: true, - isServer: false, - isDebug, - isLinux: uaHelper.isLinux, - isMacOs: uaHelper.isMacOs, - isSafari: uaHelper.isSafari, - isWindows: uaHelper.isWindows, - isFireFox: uaHelper.isFireFox, - isMobile: uaHelper.isMobile, - isChrome: uaHelper.isChrome, - isIOS: uaHelper.isIOS, - } as Browser; - // Chrome on iOS is still Safari - if (environment.isChrome && !environment.isIOS) { - assertEquals(environment.isSafari, false); - assertEquals(environment.isFireFox, false); - environment = { - ...environment, - isSafari: false, - isFireFox: false, - isChrome: true, - chromeVersion: uaHelper.getChromeVersion(), - } satisfies ChromeBrowser; - } - } - globalThis.environment = environment; - return environment; -} - -function printBuildInfo() { - console.group('Build info'); - console.log('Project:', config.PROJECT_NAME); - console.log( - 'Build date:', - config.BUILD_DATE ? new Date(config.BUILD_DATE).toLocaleString() : 'Unknown' - ); - console.log('Editor Version:', config.editorVersion); - - console.log('Version:', config.gitVersion); - console.log( - 'AFFiNE is an open source project, you can view its source code on GitHub!' - ); - console.log(`https://github.com/toeverything/AFFiNE/tree/${config.hash}`); - console.groupEnd(); -} - -declare global { - // eslint-disable-next-line no-var - var environment: Environment; - // eslint-disable-next-line no-var - var $AFFINE_SETUP: boolean | undefined; - // eslint-disable-next-line no-var - var editorVersion: string | undefined; -} - -export function setupGlobal() { - if (globalThis.$AFFINE_SETUP) { - return; - } - globalThis.environment = getEnvironment(); - if (getEnvironment().isBrowser) { - printBuildInfo(); - globalThis.editorVersion = config.editorVersion; - } - globalThis.$AFFINE_SETUP = true; -} - -export { config }; +export * from './api'; +export * from './config'; export * from './constant'; diff --git a/apps/web/src/utils/is-valid-ip-address.ts b/packages/env/src/is-valid-ip-address.ts similarity index 100% rename from apps/web/src/utils/is-valid-ip-address.ts rename to packages/env/src/is-valid-ip-address.ts diff --git a/packages/workspace/src/affine/login.ts b/packages/workspace/src/affine/login.ts index 73114e976f..7de68c8e37 100644 --- a/packages/workspace/src/affine/login.ts +++ b/packages/workspace/src/affine/login.ts @@ -167,9 +167,16 @@ export function createAffineAuth(prefix = '/') { } let provider: AuthProvider; switch (method) { - case SignMethod.Google: - provider = new GoogleAuthProvider(); + case SignMethod.Google: { + const googleProvider = new GoogleAuthProvider(); + // make sure the user has a chance to select an account + // https://developers.google.com/identity/openid-connect/openid-connect#prompt + googleProvider.setCustomParameters({ + prompt: 'select_account', + }); + provider = googleProvider; break; + } case SignMethod.GitHub: provider = new GithubAuthProvider(); break; diff --git a/packages/workspace/src/affine/sync.ts b/packages/workspace/src/affine/sync.ts index 02cedebca1..3fd7bb761c 100644 --- a/packages/workspace/src/affine/sync.ts +++ b/packages/workspace/src/affine/sync.ts @@ -1,4 +1,5 @@ import { DebugLogger } from '@affine/debug'; +import { websocketPrefixUrl } from '@affine/env/api'; import { workspaceDetailSchema, workspaceSchema, @@ -61,11 +62,7 @@ export function createAffineGlobalChannel( let dispose: Disposable | undefined = undefined; const apis = { connect: () => { - client = new WebsocketClient( - `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${ - window.location.host - }/api/global/sync` - ); + client = new WebsocketClient(websocketPrefixUrl + '/api/global/sync/'); client.connect(handleMessage); dispose = storageChangeSlot.on(() => { apis.disconnect(); diff --git a/packages/workspace/src/providers/index.ts b/packages/workspace/src/providers/index.ts index 10402a5c0a..941109a4c9 100644 --- a/packages/workspace/src/providers/index.ts +++ b/packages/workspace/src/providers/index.ts @@ -1,4 +1,4 @@ -import { config } from '@affine/env'; +import { config, websocketPrefixUrl } from '@affine/env'; import { KeckProvider } from '@affine/workspace/affine/keck'; import { getLoginStorage, @@ -41,11 +41,8 @@ const createAffineWebSocketProvider = ( apis.disconnect(); apis.connect(); }); - const wsUrl = `${ - window.location.protocol === 'https:' ? 'wss' : 'ws' - }://${window.location.host}/api/sync/`; webSocketProvider = new KeckProvider( - wsUrl, + websocketPrefixUrl + '/api/sync/', blockSuiteWorkspace.id, blockSuiteWorkspace.doc, {