From dc55518c5be20e2c46f9c840c74b7cf78cf693b4 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Fri, 27 Jun 2025 17:41:37 +0800 Subject: [PATCH] feat(server): support multiple hosts in one deployment (#12950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close CLOUD-233 #### PR Dependency Tree * **PR #12950** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **New Features** * Added support for configuring multiple server hosts across backend and frontend settings. * Enhanced deployment and Helm chart configuration to allow specifying multiple ingress hosts. * Updated admin and configuration interfaces to display and manage multiple server hosts. * **Improvements** * Improved URL generation, OAuth, and worker service logic to dynamically handle requests from multiple hosts. * Enhanced captcha verification to support multiple allowed hostnames. * Updated frontend logic for platform-specific server base URLs and allowed origins, including Apple app domains. * Expanded test coverage for multi-host scenarios. * **Bug Fixes** * Corrected backend logic to consistently use dynamic base URLs and origins based on request host context. --- .docker/selfhost/schema.json | 5 ++ .github/actions/deploy/deploy.mjs | 15 +++-- .github/helm/affine/templates/ingress.yaml | 14 +++-- .github/helm/affine/values.yaml | 8 ++- .../configs/providers/generic.ts | 1 + .../src/__tests__/oauth/controller.spec.ts | 36 +++++++++++ packages/backend/server/src/app.module.ts | 8 ++- .../src/base/helpers/__tests__/url.spec.ts | 41 +++++++++++- .../backend/server/src/base/helpers/url.ts | 62 ++++++++++++++----- .../backend/server/src/core/config/config.ts | 6 ++ .../server/src/core/config/resolver.ts | 2 +- packages/backend/server/src/env.ts | 3 + .../server/src/plugins/captcha/service.ts | 23 +++++-- .../server/src/plugins/oauth/controller.ts | 2 +- .../server/src/plugins/worker/controller.ts | 2 +- .../server/src/plugins/worker/service.ts | 4 +- packages/backend/server/src/server.ts | 2 +- packages/frontend/admin/src/config.json | 4 ++ .../admin/src/modules/settings/config.ts | 2 +- .../core/src/modules/cloud/constant.ts | 16 ++++- .../core/src/modules/navigation/utils.ts | 1 + 21 files changed, 214 insertions(+), 43 deletions(-) diff --git a/.docker/selfhost/schema.json b/.docker/selfhost/schema.json index 0bcba58ff..533740398 100644 --- a/.docker/selfhost/schema.json +++ b/.docker/selfhost/schema.json @@ -540,6 +540,11 @@ "description": "Where the server get deployed(FQDN).\n@default \"localhost\"\n@environment `AFFINE_SERVER_HOST`", "default": "localhost" }, + "hosts": { + "type": "array", + "description": "Multiple hosts the server will accept requests from.\n@default []", + "default": [] + }, "port": { "type": "number", "description": "Which port the server will listen on.\n@default 3010\n@environment `AFFINE_SERVER_PORT`", diff --git a/.github/actions/deploy/deploy.mjs b/.github/actions/deploy/deploy.mjs index 3a374fad8..4a67287af 100644 --- a/.github/actions/deploy/deploy.mjs +++ b/.github/actions/deploy/deploy.mjs @@ -126,7 +126,10 @@ const createHelmCommand = ({ isDryRun }) => { ? 'internal' : 'dev'; - const host = DEPLOY_HOST || CANARY_DEPLOY_HOST; + const hosts = (DEPLOY_HOST || CANARY_DEPLOY_HOST) + .split(',') + .map(host => host.trim()) + .filter(host => host); const deployCommand = [ `helm upgrade --install affine .github/helm/affine`, `--namespace ${namespace}`, @@ -135,7 +138,9 @@ const createHelmCommand = ({ isDryRun }) => { `--set-string global.app.buildType="${buildType}"`, `--set global.ingress.enabled=true`, `--set-json global.ingress.annotations="{ \\"kubernetes.io/ingress.class\\": \\"gce\\", \\"kubernetes.io/ingress.allow-http\\": \\"true\\", \\"kubernetes.io/ingress.global-static-ip-name\\": \\"${STATIC_IP_NAME}\\" }"`, - `--set-string global.ingress.host="${host}"`, + ...hosts.map( + (host, index) => `--set global.ingress.hosts[${index}]=${host}` + ), `--set-string global.version="${APP_VERSION}"`, ...redisAndPostgres, ...indexerOptions, @@ -143,14 +148,14 @@ const createHelmCommand = ({ isDryRun }) => { `--set-string web.image.tag="${imageTag}"`, `--set graphql.replicaCount=${replica.graphql}`, `--set-string graphql.image.tag="${imageTag}"`, - `--set graphql.app.host=${host}`, + `--set graphql.app.host=${hosts[0]}`, `--set sync.replicaCount=${replica.sync}`, `--set-string sync.image.tag="${imageTag}"`, `--set-string renderer.image.tag="${imageTag}"`, - `--set renderer.app.host=${host}`, + `--set renderer.app.host=${hosts[0]}`, `--set renderer.replicaCount=${replica.renderer}`, `--set-string doc.image.tag="${imageTag}"`, - `--set doc.app.host=${host}`, + `--set doc.app.host=${hosts[0]}`, `--set doc.replicaCount=${replica.doc}`, ...serviceAnnotations, ...resources, diff --git a/.github/helm/affine/templates/ingress.yaml b/.github/helm/affine/templates/ingress.yaml index a4c4f8998..e8af90668 100644 --- a/.github/helm/affine/templates/ingress.yaml +++ b/.github/helm/affine/templates/ingress.yaml @@ -36,7 +36,8 @@ spec: {{- end }} {{- end }} rules: - - host: "{{ .Values.global.ingress.host }}" + {{- range .Values.global.ingress.hosts }} + - host: {{ . | quote }} http: paths: - path: /socket.io @@ -45,33 +46,34 @@ spec: service: name: affine-sync port: - number: {{ .Values.sync.service.port }} + number: {{ $.Values.sync.service.port }} - path: /graphql pathType: Prefix backend: service: name: affine-graphql port: - number: {{ .Values.graphql.service.port }} + number: {{ $.Values.graphql.service.port }} - path: /api pathType: Prefix backend: service: name: affine-graphql port: - number: {{ .Values.graphql.service.port }} + number: {{ $.Values.graphql.service.port }} - path: /workspace pathType: Prefix backend: service: name: affine-renderer port: - number: {{ .Values.renderer.service.port }} + number: {{ $.Values.renderer.service.port }} - path: / pathType: Prefix backend: service: name: affine-web port: - number: {{ .Values.web.service.port }} + number: {{ $.Values.web.service.port }} + {{- end }} {{- end }} diff --git a/.github/helm/affine/values.yaml b/.github/helm/affine/values.yaml index 577727480..e6a4ac3e9 100644 --- a/.github/helm/affine/values.yaml +++ b/.github/helm/affine/values.yaml @@ -4,7 +4,13 @@ global: ingress: enabled: false className: '' - host: affine.pro + # hosts for ingress rules + # e.g. + # hosts: + # - affine.pro + # - www.affine.pro + hosts: + - affine.pro tls: [] secret: secretName: 'server-private-key' diff --git a/blocksuite/affine/blocks/embed/src/embed-iframe-block/configs/providers/generic.ts b/blocksuite/affine/blocks/embed/src/embed-iframe-block/configs/providers/generic.ts index b40c547b0..6d96eff46 100644 --- a/blocksuite/affine/blocks/embed/src/embed-iframe-block/configs/providers/generic.ts +++ b/blocksuite/affine/blocks/embed/src/embed-iframe-block/configs/providers/generic.ts @@ -15,6 +15,7 @@ const AFFINE_DOMAINS = [ 'insider.affine.pro', // Beta/internal cloud domain 'affine.fail', // Canary cloud domain 'toeverything.app', // Safety measure for potential future use + 'apple.getaffineapp.com', // Cloud domain for Apple app ]; /** diff --git a/packages/backend/server/src/__tests__/oauth/controller.spec.ts b/packages/backend/server/src/__tests__/oauth/controller.spec.ts index 28f86bafd..8e62022f8 100644 --- a/packages/backend/server/src/__tests__/oauth/controller.spec.ts +++ b/packages/backend/server/src/__tests__/oauth/controller.spec.ts @@ -37,6 +37,10 @@ test.before(async t => { }, }, }, + server: { + hosts: ['localhost', 'test.affine.dev'], + https: true, + }, }), AppModule, ], @@ -90,6 +94,38 @@ test("should be able to redirect to oauth provider's login page", async t => { ); }); +test('should be able to redirect to oauth provider with multiple hosts', async t => { + const { app } = t.context; + + const res = await app + .POST('/api/oauth/preflight') + .set('host', 'test.affine.dev') + .send({ provider: 'Google' }) + .expect(HttpStatus.OK); + + const { url } = res.body; + + const redirect = new URL(url); + t.is(redirect.origin, 'https://accounts.google.com'); + + t.is(redirect.pathname, '/o/oauth2/v2/auth'); + t.is(redirect.searchParams.get('client_id'), 'google-client-id'); + t.is( + redirect.searchParams.get('redirect_uri'), + 'https://test.affine.dev/oauth/callback' + ); + t.is(redirect.searchParams.get('response_type'), 'code'); + t.is(redirect.searchParams.get('prompt'), 'select_account'); + t.truthy(redirect.searchParams.get('state')); + // state should be a json string + const state = JSON.parse(redirect.searchParams.get('state')!); + t.is(state.provider, 'Google'); + t.regex( + state.state, + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ + ); +}); + test('should be able to redirect to oauth provider with client_nonce', async t => { const { app } = t.context; diff --git a/packages/backend/server/src/app.module.ts b/packages/backend/server/src/app.module.ts index 7d8fc29d6..ffe00fe25 100644 --- a/packages/backend/server/src/app.module.ts +++ b/packages/backend/server/src/app.module.ts @@ -8,6 +8,7 @@ import { ClsModule } from 'nestjs-cls'; import { AppController } from './app.controller'; import { + getRequestFromHost, getRequestIdFromHost, getRequestIdFromRequest, ScannerModule, @@ -66,8 +67,9 @@ export const FunctionalityModules = [ // make every request has a unique id to tracing return getRequestIdFromRequest(req, 'http'); }, - setup(cls, _req, res: Response) { + setup(cls, req: Request, res: Response) { res.setHeader('X-Request-Id', cls.getId()); + cls.set(CLS_REQUEST_HOST, req.hostname); }, }, // for websocket connection @@ -79,6 +81,10 @@ export const FunctionalityModules = [ // make every request has a unique id to tracing return getRequestIdFromHost(context); }, + setup(cls, context: ExecutionContext) { + const req = getRequestFromHost(context); + cls.set(CLS_REQUEST_HOST, req.hostname); + }, }, plugins: [ // https://papooch.github.io/nestjs-cls/plugins/available-plugins/transactional/prisma-adapter diff --git a/packages/backend/server/src/base/helpers/__tests__/url.spec.ts b/packages/backend/server/src/base/helpers/__tests__/url.spec.ts index d34eb0c17..69c076bcb 100644 --- a/packages/backend/server/src/base/helpers/__tests__/url.spec.ts +++ b/packages/backend/server/src/base/helpers/__tests__/url.spec.ts @@ -12,6 +12,7 @@ test.beforeEach(async t => { server: { externalUrl: '', host: 'app.affine.local', + hosts: [], port: 3010, https: true, path: '', @@ -28,6 +29,7 @@ test('can factor base url correctly with specified external url', t => { server: { externalUrl: 'https://external.domain.com', host: 'app.affine.local', + hosts: [], port: 3010, https: true, path: '/ignored', @@ -42,6 +44,7 @@ test('can factor base url correctly with specified external url and path', t => server: { externalUrl: 'https://external.domain.com/anything', host: 'app.affine.local', + hosts: [], port: 3010, https: true, path: '/ignored', @@ -56,6 +59,7 @@ test('can factor base url correctly with specified external url with port', t => server: { externalUrl: 'https://external.domain.com:123', host: 'app.affine.local', + hosts: [], port: 3010, https: true, }, @@ -95,7 +99,7 @@ test('can safe redirect', t => { function deny(to: string) { t.context.url.safeRedirect(res, to); - t.true(spy.calledOnceWith(t.context.url.home)); + t.true(spy.calledOnceWith(t.context.url.baseUrl)); spy.resetHistory(); } @@ -106,3 +110,38 @@ test('can safe redirect', t => { ].forEach(allow); ['https://other.domain.com', 'a://invalid.uri'].forEach(deny); }); + +test('can get request origin', t => { + t.is(t.context.url.requestOrigin, 'https://app.affine.local'); +}); + +test('can get request base url', t => { + t.is(t.context.url.requestBaseUrl, 'https://app.affine.local'); +}); + +test('can get request base url with multiple hosts', t => { + // mock cls + const cls = new Map(); + const url = new URLHelper( + { + server: { + externalUrl: '', + host: 'app.affine.local1', + hosts: ['app.affine.local1', 'app.affine.local2'], + port: 3010, + https: true, + path: '', + }, + } as any, + cls as any + ); + + // no cls, use default origin + t.is(url.requestOrigin, 'https://app.affine.local1'); + t.is(url.requestBaseUrl, 'https://app.affine.local1'); + + // set cls + cls.set(CLS_REQUEST_HOST, 'app.affine.local2'); + t.is(url.requestOrigin, 'https://app.affine.local2'); + t.is(url.requestBaseUrl, 'https://app.affine.local2'); +}); diff --git a/packages/backend/server/src/base/helpers/url.ts b/packages/backend/server/src/base/helpers/url.ts index ed04aff89..dc49877f4 100644 --- a/packages/backend/server/src/base/helpers/url.ts +++ b/packages/backend/server/src/base/helpers/url.ts @@ -2,6 +2,7 @@ import { isIP } from 'node:net'; import { Injectable } from '@nestjs/common'; import type { Response } from 'express'; +import { ClsService } from 'nestjs-cls'; import { Config } from '../config'; import { OnEvent } from '../event'; @@ -11,10 +12,13 @@ export class URLHelper { redirectAllowHosts!: string[]; origin!: string; + allowedOrigins!: string[]; baseUrl!: string; - home!: string; - constructor(private readonly config: Config) { + constructor( + private readonly config: Config, + private readonly cls?: ClsService + ) { this.init(); } @@ -34,19 +38,40 @@ export class URLHelper { this.baseUrl = externalUrl.origin + externalUrl.pathname.replace(/\/$/, ''); } else { - this.origin = [ - this.config.server.https ? 'https' : 'http', - '://', - this.config.server.host, - this.config.server.host === 'localhost' || isIP(this.config.server.host) - ? `:${this.config.server.port}` - : '', - ].join(''); + this.origin = this.convertHostToOrigin(this.config.server.host); this.baseUrl = this.origin + this.config.server.path; } - this.home = this.baseUrl; this.redirectAllowHosts = [this.baseUrl]; + + this.allowedOrigins = [this.origin]; + if (this.config.server.hosts.length > 0) { + for (const host of this.config.server.hosts) { + this.allowedOrigins.push(this.convertHostToOrigin(host)); + } + } + } + + get requestOrigin() { + if (this.config.server.hosts.length === 0) { + return this.origin; + } + + // support multiple hosts + const requestHost = this.cls?.get(CLS_REQUEST_HOST); + if (!requestHost || !this.config.server.hosts.includes(requestHost)) { + return this.origin; + } + + return this.convertHostToOrigin(requestHost); + } + + get requestBaseUrl() { + if (this.config.server.hosts.length === 0) { + return this.baseUrl; + } + + return this.requestOrigin + this.config.server.path; } stringify(query: Record) { @@ -72,7 +97,7 @@ export class URLHelper { } url(path: string, query: Record = {}) { - const url = new URL(path, this.origin); + const url = new URL(path, this.requestOrigin); for (const key in query) { url.searchParams.set(key, query[key]); @@ -87,7 +112,7 @@ export class URLHelper { safeRedirect(res: Response, to: string) { try { - const finalTo = new URL(decodeURIComponent(to), this.baseUrl); + const finalTo = new URL(decodeURIComponent(to), this.requestBaseUrl); for (const host of this.redirectAllowHosts) { const hostURL = new URL(host); @@ -103,7 +128,7 @@ export class URLHelper { } // redirect to home if the url is invalid - return res.redirect(this.home); + return res.redirect(this.baseUrl); } verify(url: string | URL) { @@ -118,4 +143,13 @@ export class URLHelper { return false; } } + + private convertHostToOrigin(host: string) { + return [ + this.config.server.https ? 'https' : 'http', + '://', + host, + host === 'localhost' || isIP(host) ? `:${this.config.server.port}` : '', + ].join(''); + } } diff --git a/packages/backend/server/src/core/config/config.ts b/packages/backend/server/src/core/config/config.ts index b84515c3f..3b6bd421b 100644 --- a/packages/backend/server/src/core/config/config.ts +++ b/packages/backend/server/src/core/config/config.ts @@ -12,6 +12,7 @@ declare global { externalUrl?: string; https: boolean; host: string; + hosts: ConfigItem; port: number; path: string; name?: string; @@ -52,6 +53,11 @@ Default to be \`[server.protocol]://[server.host][:server.port]\` if not specifi default: 'localhost', env: 'AFFINE_SERVER_HOST', }, + hosts: { + desc: 'Multiple hosts the server will accept requests from.', + default: [], + shape: z.array(z.string()), + }, port: { desc: 'Which port the server will listen on.', default: 3010, diff --git a/packages/backend/server/src/core/config/resolver.ts b/packages/backend/server/src/core/config/resolver.ts index d9838e411..f16b7012a 100644 --- a/packages/backend/server/src/core/config/resolver.ts +++ b/packages/backend/server/src/core/config/resolver.ts @@ -82,7 +82,7 @@ export class ServerConfigResolver { ? 'AFFiNE Beta Cloud' : 'AFFiNE Cloud'), version: env.version, - baseUrl: this.url.home, + baseUrl: this.url.requestBaseUrl, type: env.DEPLOYMENT_TYPE, features: this.server.features, }; diff --git a/packages/backend/server/src/env.ts b/packages/backend/server/src/env.ts index d82236cee..c37ae0f51 100644 --- a/packages/backend/server/src/env.ts +++ b/packages/backend/server/src/env.ts @@ -12,6 +12,8 @@ declare global { var readEnv: (key: string, defaultValue: T, availableValues?: T[]) => T; // oxlint-disable-next-line no-var var CUSTOM_CONFIG_PATH: string; + // oxlint-disable-next-line no-var + var CLS_REQUEST_HOST: 'CLS_REQUEST_HOST'; } } @@ -53,6 +55,7 @@ export type AppEnv = { version: string; }; +globalThis.CLS_REQUEST_HOST = 'CLS_REQUEST_HOST'; globalThis.CUSTOM_CONFIG_PATH = join(homedir(), '.affine/config'); globalThis.readEnv = function readEnv( env: string, diff --git a/packages/backend/server/src/plugins/captcha/service.ts b/packages/backend/server/src/plugins/captcha/service.ts index c687abdc3..01ebd9fc2 100644 --- a/packages/backend/server/src/plugins/captcha/service.ts +++ b/packages/backend/server/src/plugins/captcha/service.ts @@ -56,13 +56,26 @@ export class CaptchaService { body: formData, method: 'POST', }); - const outcome: any = await result.json(); + const outcome = (await result.json()) as { + success: boolean; + hostname: string; + }; - return ( - !!outcome.success && - // skip hostname check in dev mode - (env.dev || outcome.hostname === this.config.server.host) + if (!outcome.success) return false; + + // skip hostname check in dev mode + if (env.dev) return true; + + // check if the hostname is in the hosts + if (this.config.server.hosts.includes(outcome.hostname)) return true; + + // check if the hostname is in the host + if (this.config.server.host === outcome.hostname) return true; + + this.logger.warn( + `Captcha verification failed for hostname: ${outcome.hostname}` ); + return false; } private async verifyChallengeResponse(response: any, resource: string) { diff --git a/packages/backend/server/src/plugins/oauth/controller.ts b/packages/backend/server/src/plugins/oauth/controller.ts index 5664589c1..773868858 100644 --- a/packages/backend/server/src/plugins/oauth/controller.ts +++ b/packages/backend/server/src/plugins/oauth/controller.ts @@ -142,7 +142,7 @@ export class OAuthController { provider: rawState.provider, }) ); - clientUrl.searchParams.set('server', this.url.origin); + clientUrl.searchParams.set('server', this.url.requestOrigin); return res.redirect( this.url.link('/open-app/url?', { diff --git a/packages/backend/server/src/plugins/worker/controller.ts b/packages/backend/server/src/plugins/worker/controller.ts index b2e2bd86c..93b34fe33 100644 --- a/packages/backend/server/src/plugins/worker/controller.ts +++ b/packages/backend/server/src/plugins/worker/controller.ts @@ -56,7 +56,7 @@ export class WorkerController { this.logger.error('Invalid Origin', 'ERROR', { origin, referer }); throw new BadRequest('Invalid header'); } - const url = new URL(req.url, this.url.baseUrl); + const url = new URL(req.url, this.url.requestBaseUrl); const imageURL = url.searchParams.get('url'); if (!imageURL) { throw new BadRequest('Missing "url" parameter'); diff --git a/packages/backend/server/src/plugins/worker/service.ts b/packages/backend/server/src/plugins/worker/service.ts index 4bf0a8d60..99027d682 100644 --- a/packages/backend/server/src/plugins/worker/service.ts +++ b/packages/backend/server/src/plugins/worker/service.ts @@ -5,7 +5,7 @@ import { fixUrl, OriginRules } from './utils'; @Injectable() export class WorkerService { - allowedOrigins: OriginRules = [this.url.origin]; + allowedOrigins: OriginRules = [...this.url.allowedOrigins]; constructor( private readonly config: Config, @@ -18,7 +18,7 @@ export class WorkerService { ...this.config.worker.allowedOrigin .map(u => fixUrl(u)?.origin as string) .filter(v => !!v), - this.url.origin, + ...this.url.allowedOrigins, ]; } diff --git a/packages/backend/server/src/server.ts b/packages/backend/server/src/server.ts index 35a6b4c1e..69eb93dd1 100644 --- a/packages/backend/server/src/server.ts +++ b/packages/backend/server/src/server.ts @@ -66,5 +66,5 @@ export async function run() { logger.log(`AFFiNE Server is running in [${env.DEPLOYMENT_TYPE}] mode`); logger.log(`Listening on http://${listeningHost}:${config.server.port}`); - logger.log(`And the public server should be recognized as ${url.home}`); + logger.log(`And the public server should be recognized as ${url.baseUrl}`); } diff --git a/packages/frontend/admin/src/config.json b/packages/frontend/admin/src/config.json index 0859e80b7..0e6184d0f 100644 --- a/packages/frontend/admin/src/config.json +++ b/packages/frontend/admin/src/config.json @@ -171,6 +171,10 @@ "desc": "Where the server get deployed(FQDN).", "env": "AFFINE_SERVER_HOST" }, + "hosts": { + "type": "Array", + "desc": "Multiple hosts the server will accept requests from." + }, "port": { "type": "Number", "desc": "Which port the server will listen on.", diff --git a/packages/frontend/admin/src/modules/settings/config.ts b/packages/frontend/admin/src/modules/settings/config.ts index 13ed9088d..04983a4de 100644 --- a/packages/frontend/admin/src/modules/settings/config.ts +++ b/packages/frontend/admin/src/modules/settings/config.ts @@ -48,7 +48,7 @@ export const KNOWN_CONFIG_GROUPS = [ { name: 'Server', module: 'server', - fields: ['externalUrl', 'name'], + fields: ['externalUrl', 'name', 'hosts'], } as ConfigGroup<'server'>, { name: 'Auth', diff --git a/packages/frontend/core/src/modules/cloud/constant.ts b/packages/frontend/core/src/modules/cloud/constant.ts index 3a803e5ac..d8b8f9589 100644 --- a/packages/frontend/core/src/modules/cloud/constant.ts +++ b/packages/frontend/core/src/modules/cloud/constant.ts @@ -63,7 +63,11 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ? [ { id: 'affine-cloud', - baseUrl: 'https://app.affine.pro', + baseUrl: BUILD_CONFIG.isNative + ? BUILD_CONFIG.isIOS + ? 'https://apple.getaffineapp.com' + : 'https://app.affine.pro' + : location.origin, config: { serverName: 'Affine Cloud', features: [ @@ -91,7 +95,11 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ? [ { id: 'affine-cloud', - baseUrl: 'https://insider.affine.pro', + baseUrl: BUILD_CONFIG.isNative + ? BUILD_CONFIG.isIOS + ? 'https://apple.getaffineapp.com' + : 'https://insider.affine.pro' + : location.origin, config: { serverName: 'Affine Cloud', features: [ @@ -147,7 +155,9 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ? [ { id: 'affine-cloud', - baseUrl: 'https://affine.fail', + baseUrl: BUILD_CONFIG.isNative + ? 'https://affine.fail' + : location.origin, config: { serverName: 'Affine Cloud', features: [ diff --git a/packages/frontend/core/src/modules/navigation/utils.ts b/packages/frontend/core/src/modules/navigation/utils.ts index 34aaf40d0..ad1c3a7ee 100644 --- a/packages/frontend/core/src/modules/navigation/utils.ts +++ b/packages/frontend/core/src/modules/navigation/utils.ts @@ -8,6 +8,7 @@ function maybeAffineOrigin(origin: string, baseUrl: string) { return ( origin.startsWith('file://') || origin.endsWith('affine.pro') || // stable/beta + origin.endsWith('apple.getaffineapp.com') || // stable/beta origin.endsWith('affine.fail') || // canary origin === baseUrl // localhost or self-hosted );