diff --git a/.changeset/nest-multidomain-config.md b/.changeset/nest-multidomain-config.md new file mode 100644 index 00000000000..8ed5b53786f --- /dev/null +++ b/.changeset/nest-multidomain-config.md @@ -0,0 +1,15 @@ +--- +"@clerk/shared": minor +"@clerk/backend": minor +"@clerk/nextjs": minor +"@clerk/astro": minor +"@clerk/react-router": minor +"@clerk/tanstack-react-start": minor +"@clerk/clerk-js": minor +"@clerk/express": minor +"@clerk/react": minor +"@clerk/vue": minor +"@clerk/nuxt": minor +--- + +Nest satellite configuration under a `multiDomain` key. The top-level `isSatellite`, `domain`, and `satelliteAutoSync` options are replaced by `multiDomain: { isSatellite, domain?, proxyUrl?, autoSync? }`. The `proxyUrl` option remains available at both the top level and inside `multiDomain`. diff --git a/packages/astro/src/integration/create-integration.ts b/packages/astro/src/integration/create-integration.ts index e8902271a96..05a3bb40a15 100644 --- a/packages/astro/src/integration/create-integration.ts +++ b/packages/astro/src/integration/create-integration.ts @@ -16,7 +16,9 @@ type HotloadAstroClerkIntegrationParams = AstroClerkIntegrationParams & { function createIntegration() { return (params?: Params): AstroIntegration => { - const { proxyUrl, isSatellite, domain, signInUrl, signUpUrl, enableEnvSchema = true } = params || {}; + const { proxyUrl, multiDomain, signInUrl, signUpUrl, enableEnvSchema = true } = params || {}; + const isSatellite = multiDomain?.isSatellite; + const domain = multiDomain?.domain; // These are not provided when the "bundled" integration is used const clerkJSUrl = (params as any)?.clerkJSUrl as string | undefined; diff --git a/packages/astro/src/internal/create-injection-script-runner.ts b/packages/astro/src/internal/create-injection-script-runner.ts index e07b298edc0..d108fbe04ae 100644 --- a/packages/astro/src/internal/create-injection-script-runner.ts +++ b/packages/astro/src/internal/create-injection-script-runner.ts @@ -22,7 +22,11 @@ function createInjectionScriptRunner(creator: CreateClerkInstanceInternalFn) { clientSafeVars = JSON.parse(clientSafeVarsContainer.textContent || '{}'); } - await creator(mergeEnvVarsWithParams({ ...astroClerkOptions, ...clientSafeVars })); + await creator( + mergeEnvVarsWithParams({ ...astroClerkOptions, ...clientSafeVars }) as AstroClerkIntegrationParams & { + publishableKey: string; + }, + ); } return runner; diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index dea24762b16..cb82c408e3f 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -9,9 +9,8 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish const { signInUrl: paramSignIn, signUpUrl: paramSignUp, - isSatellite: paramSatellite, + multiDomain: paramMultiDomain, proxyUrl: paramProxy, - domain: paramDomain, publishableKey: paramPublishableKey, telemetry: paramTelemetry, clerkJSUrl: paramClerkJSUrl, @@ -21,12 +20,19 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish ...rest } = params || {}; + const isSatellite = paramMultiDomain?.isSatellite || import.meta.env.PUBLIC_CLERK_IS_SATELLITE; + const domain = paramMultiDomain?.domain || import.meta.env.PUBLIC_CLERK_DOMAIN; + return { signInUrl: paramSignIn || import.meta.env.PUBLIC_CLERK_SIGN_IN_URL, signUpUrl: paramSignUp || import.meta.env.PUBLIC_CLERK_SIGN_UP_URL, - isSatellite: paramSatellite || import.meta.env.PUBLIC_CLERK_IS_SATELLITE, + multiDomain: isSatellite + ? { + isSatellite: true as const, + ...(domain ? { domain: domain as string } : {}), + } + : paramMultiDomain, proxyUrl: paramProxy || import.meta.env.PUBLIC_CLERK_PROXY_URL, - domain: paramDomain || import.meta.env.PUBLIC_CLERK_DOMAIN, publishableKey: paramPublishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '', clerkUiUrl: paramClerkUiUrl || import.meta.env.PUBLIC_CLERK_UI_URL, clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, diff --git a/packages/astro/src/server/clerk-client.ts b/packages/astro/src/server/clerk-client.ts index 10b53c03a27..32f1b2ae2a6 100644 --- a/packages/astro/src/server/clerk-client.ts +++ b/packages/astro/src/server/clerk-client.ts @@ -13,8 +13,12 @@ const createClerkClientWithOptions: CreateClerkClientWithOptions = (context, opt apiUrl: getSafeEnv(context).apiUrl, apiVersion: getSafeEnv(context).apiVersion, proxyUrl: getSafeEnv(context).proxyUrl, - domain: getSafeEnv(context).domain, - isSatellite: getSafeEnv(context).isSatellite, + multiDomain: getSafeEnv(context).isSatellite + ? { + isSatellite: true, + ...(getSafeEnv(context).domain ? { domain: getSafeEnv(context).domain } : {}), + } + : undefined, userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`, sdkMetadata: { name: PACKAGE_NAME, diff --git a/packages/astro/src/server/clerk-middleware.ts b/packages/astro/src/server/clerk-middleware.ts index fa0500f2197..b590a313347 100644 --- a/packages/astro/src/server/clerk-middleware.ts +++ b/packages/astro/src/server/clerk-middleware.ts @@ -209,23 +209,28 @@ export const handleMultiDomainAndProxy = ( opts: AuthenticateRequestOptions, context: AstroMiddlewareContextParam, ) => { - const relativeOrAbsoluteProxyUrl = handleValueOrFn( - opts?.proxyUrl, + const isSatellite = handleValueOrFn( + opts.multiDomain?.isSatellite, + new URL(clerkRequest.url), + getSafeEnv(context).isSatellite, + ); + const domain = handleValueOrFn(opts.multiDomain?.domain, new URL(clerkRequest.url), getSafeEnv(context).domain); + const signInUrl = opts?.signInUrl || getSafeEnv(context).signInUrl; + + // Resolve proxyUrl: multiDomain.proxyUrl takes precedence, then top-level, then env + const rawProxyUrl = handleValueOrFn( + opts.multiDomain?.proxyUrl ?? opts?.proxyUrl, clerkRequest.clerkUrl, getSafeEnv(context).proxyUrl, ); let proxyUrl; - if (!!relativeOrAbsoluteProxyUrl && !isHttpOrHttps(relativeOrAbsoluteProxyUrl)) { - proxyUrl = new URL(relativeOrAbsoluteProxyUrl, clerkRequest.clerkUrl).toString(); + if (!!rawProxyUrl && !isHttpOrHttps(rawProxyUrl)) { + proxyUrl = new URL(rawProxyUrl, clerkRequest.clerkUrl).toString(); } else { - proxyUrl = relativeOrAbsoluteProxyUrl; + proxyUrl = rawProxyUrl; } - const isSatellite = handleValueOrFn(opts.isSatellite, new URL(clerkRequest.url), getSafeEnv(context).isSatellite); - const domain = handleValueOrFn(opts.domain, new URL(clerkRequest.url), getSafeEnv(context).domain); - const signInUrl = opts?.signInUrl || getSafeEnv(context).signInUrl; - if (isSatellite && !proxyUrl && !domain) { throw new Error(missingDomainAndProxy); } @@ -241,8 +246,13 @@ export const handleMultiDomainAndProxy = ( return { proxyUrl, - isSatellite, - domain, + multiDomain: isSatellite + ? { + isSatellite, + ...(domain ? { domain } : { proxyUrl: proxyUrl! }), + ...(opts.multiDomain?.autoSync !== undefined ? { autoSync: opts.multiDomain.autoSync } : {}), + } + : undefined, }; }; diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 7f0613e5968..427934bbe8f 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -16,7 +16,6 @@ type AstroClerkUpdateOptions = Pick = Without< ClerkOptions, - | 'isSatellite' | 'sdkMetadata' | 'standardBrowser' | 'selectInitialSession' diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 1559d61f7fb..ebe25a98733 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -15,7 +15,7 @@ export type ClerkOptions = Omit > & { sdkMetadata?: SDKMetadata; telemetry?: Pick }; diff --git a/packages/backend/src/tokens/__tests__/factory.test.ts b/packages/backend/src/tokens/__tests__/factory.test.ts index 48436fa11f9..b39d015b5f3 100644 --- a/packages/backend/src/tokens/__tests__/factory.test.ts +++ b/packages/backend/src/tokens/__tests__/factory.test.ts @@ -14,8 +14,6 @@ describe('createAuthenticateRequest({ options, apiClient })', () => { apiVersion: 'apiVersion', proxyUrl: 'proxyUrl', publishableKey: TEST_PK, - isSatellite: false, - domain: 'domain', audience: 'domain', }; @@ -36,8 +34,6 @@ describe('createAuthenticateRequest({ options, apiClient })', () => { apiVersion: 'apiVersion', proxyUrl: 'proxyUrl', publishableKey: TEST_PK, - isSatellite: false, - domain: 'domain', audience: 'domain', }; @@ -67,8 +63,6 @@ describe('createAuthenticateRequest({ options, apiClient })', () => { apiVersion: 'apiVersion', proxyUrl: 'proxyUrl', publishableKey: TEST_PK, - isSatellite: false, - domain: 'domain', audience: 'domain', }; diff --git a/packages/backend/src/tokens/__tests__/request.test.ts b/packages/backend/src/tokens/__tests__/request.test.ts index 115dd24be2b..42dd2d792dc 100644 --- a/packages/backend/src/tokens/__tests__/request.test.ts +++ b/packages/backend/src/tokens/__tests__/request.test.ts @@ -305,12 +305,10 @@ const mockOptions = (options?) => { publishableKey: PK_TEST, proxyUrl: '', skipJwksCache: true, - isSatellite: false, signInUrl: '', signUpUrl: '', afterSignInUrl: '', afterSignUpUrl: '', - domain: '', ...options, } satisfies AuthenticateRequestOptions; }; @@ -663,9 +661,8 @@ describe('tokens.authenticateRequest(options)', () => { mockOptions({ secretKey: 'deadbeef', clientUat: '0', - isSatellite: true, + multiDomain: { isSatellite: true, domain: 'satellite.dev' }, signInUrl: 'https://primary.dev/sign-in', - domain: 'satellite.dev', }), ); @@ -697,9 +694,8 @@ describe('tokens.authenticateRequest(options)', () => { mockOptions({ secretKey: 'sk_test_deadbeef', publishableKey: PK_TEST, - isSatellite: true, + multiDomain: { isSatellite: true, domain: 'satellite.dev' }, signInUrl: 'https://primary.dev/sign-in', - domain: 'satellite.dev', }), ); @@ -728,9 +724,8 @@ describe('tokens.authenticateRequest(options)', () => { mockOptions({ publishableKey: PK_LIVE, secretKey: 'deadbeef', - isSatellite: true, + multiDomain: { isSatellite: true, domain: 'satellite.dev' }, signInUrl: 'https://primary.dev/sign-in', - domain: 'satellite.dev', }), ); @@ -749,8 +744,7 @@ describe('tokens.authenticateRequest(options)', () => { mockOptions({ secretKey: 'sk_test_deadbeef', signInUrl: 'http://primary.example/sign-in', - isSatellite: true, - domain: 'satellite.example', + multiDomain: { isSatellite: true, domain: 'satellite.example' }, }), ); @@ -776,8 +770,7 @@ describe('tokens.authenticateRequest(options)', () => { mockOptions({ secretKey: 'sk_test_deadbeef', signInUrl: 'http://primary.example/sign-in', - isSatellite: true, - domain: 'satellite.example', + multiDomain: { isSatellite: true, domain: 'satellite.example' }, }), ); @@ -801,9 +794,7 @@ describe('tokens.authenticateRequest(options)', () => { secretKey: 'deadbeef', publishableKey: PK_LIVE, signInUrl: 'https://primary.example/sign-in', - isSatellite: true, - domain: 'satellite.example', - satelliteAutoSync: false, + multiDomain: { isSatellite: true, domain: 'satellite.example', autoSync: false }, }), ); @@ -829,9 +820,7 @@ describe('tokens.authenticateRequest(options)', () => { secretKey: 'deadbeef', publishableKey: PK_LIVE, signInUrl: 'https://primary.example/sign-in', - isSatellite: true, - domain: 'satellite.example', - satelliteAutoSync: false, + multiDomain: { isSatellite: true, domain: 'satellite.example', autoSync: false }, }), ); @@ -854,8 +843,7 @@ describe('tokens.authenticateRequest(options)', () => { secretKey: 'deadbeef', publishableKey: PK_LIVE, signInUrl: 'https://primary.example/sign-in', - isSatellite: true, - domain: 'satellite.example', + multiDomain: { isSatellite: true, domain: 'satellite.example' }, }), ); @@ -878,7 +866,7 @@ describe('tokens.authenticateRequest(options)', () => { { __client_uat: '12345', __session: mockJwt }, requestUrl, ), - mockOptions({ secretKey: 'sk_test_deadbeef', isSatellite: false }), + mockOptions({ secretKey: 'sk_test_deadbeef' }), ); expect(requestState).toMatchHandshake({ @@ -899,7 +887,7 @@ describe('tokens.authenticateRequest(options)', () => { { __client_uat: '12345', __session: mockJwt, __clerk_db_jwt: mockJwt }, requestUrl, ), - mockOptions({ secretKey: 'sk_test_deadbeef', isSatellite: false }), + mockOptions({ secretKey: 'sk_test_deadbeef' }), ); expect(requestState).toMatchHandshake({ @@ -927,7 +915,7 @@ describe('tokens.authenticateRequest(options)', () => { { __client_uat: '12345', __session: mockJwt, __clerk_db_jwt: mockJwt }, requestUrl, ), - mockOptions({ secretKey: 'sk_test_deadbeef', isSatellite: false }), + mockOptions({ secretKey: 'sk_test_deadbeef' }), ); expect(requestState).toMatchHandshake({ @@ -1016,9 +1004,8 @@ describe('tokens.authenticateRequest(options)', () => { ), mockOptions({ secretKey: 'pk_test_deadbeef', - isSatellite: true, + multiDomain: { isSatellite: true, domain: 'localhost:3001' }, signInUrl: 'https://localhost:3000/sign-in/', - domain: 'localhost:3001', }), ); @@ -1676,8 +1663,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1705,8 +1691,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1734,8 +1719,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1787,8 +1771,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1816,8 +1799,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1845,8 +1827,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1874,8 +1855,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1903,8 +1883,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1932,8 +1911,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1960,8 +1938,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -1985,8 +1962,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -2010,8 +1986,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -2036,8 +2011,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); @@ -2066,8 +2040,7 @@ describe('tokens.authenticateRequest(options)', () => { const requestState = await authenticateRequest(request, { ...mockOptions(), publishableKey: PK_LIVE, - domain: 'primary.com', - isSatellite: false, + multiDomain: { isSatellite: false, domain: 'primary.com' }, signInUrl: 'https://primary.com/sign-in', }); diff --git a/packages/backend/src/tokens/authenticateContext.ts b/packages/backend/src/tokens/authenticateContext.ts index 6d9fdcb9c5a..7947eb72e1a 100644 --- a/packages/backend/src/tokens/authenticateContext.ts +++ b/packages/backend/src/tokens/authenticateContext.ts @@ -1,4 +1,5 @@ import { buildAccountsBaseUrl } from '@clerk/shared/buildAccountsBaseUrl'; +import { resolveProxyUrl } from '@clerk/shared/multiDomain'; import type { Jwt } from '@clerk/shared/types'; import { isCurrentDevAccountPortalOrigin, isLegacyDevAccountPortalOrigin } from '@clerk/shared/url'; @@ -34,6 +35,11 @@ interface AuthenticateContext extends AuthenticateRequestOptions { handshakeRedirectLoopCounter: number; handshakeToken: string | undefined; + // resolved multi-domain values (set in initPublishableKeyValues) + isSatellite: boolean; + domain: string | undefined; + satelliteAutoSync: boolean | undefined; + // url derived from headers clerkUrl: URL; // enforce existence of the following props @@ -83,7 +89,9 @@ class AuthenticateContext implements AuthenticateContext { this.initHandshakeValues(); } - Object.assign(this, options); + // Copy options excluding fields that are resolved from multiDomain + const { multiDomain: _, proxyUrl: __, ...rest } = options; + Object.assign(this, rest); this.clerkUrl = this.clerkRequest.clerkUrl; } @@ -250,21 +258,32 @@ class AuthenticateContext implements AuthenticateContext { assertValidPublishableKey(options.publishableKey); this.publishableKey = options.publishableKey; + const isSatellite = options.multiDomain?.isSatellite ?? false; + const domain = options.multiDomain?.domain; + const proxyUrl = resolveProxyUrl(options.proxyUrl, options.multiDomain?.proxyUrl); + const satelliteAutoSync = options.multiDomain?.autoSync; + const originalPk = parsePublishableKey(this.publishableKey, { fatal: true, - domain: options.domain, - isSatellite: options.isSatellite, + domain, + isSatellite, }); this.originalFrontendApi = originalPk.frontendApi; const pk = parsePublishableKey(this.publishableKey, { fatal: true, - proxyUrl: options.proxyUrl, - domain: options.domain, - isSatellite: options.isSatellite, + proxyUrl, + domain, + isSatellite, }); this.instanceType = pk.instanceType; this.frontendApi = pk.frontendApi; + + // Set resolved values so downstream code doesn't need to re-resolve + (this as any).isSatellite = isSatellite; + (this as any).domain = domain; + (this as any).proxyUrl = proxyUrl; + (this as any).satelliteAutoSync = satelliteAutoSync; } private initHeaderValues() { diff --git a/packages/backend/src/tokens/factory.ts b/packages/backend/src/tokens/factory.ts index 7f0c3916608..c14a4e75ce7 100644 --- a/packages/backend/src/tokens/factory.ts +++ b/packages/backend/src/tokens/factory.ts @@ -11,17 +11,16 @@ type BuildTimeOptions = Partial< | 'apiUrl' | 'apiVersion' | 'audience' - | 'domain' - | 'isSatellite' | 'jwtKey' | 'proxyUrl' | 'publishableKey' | 'secretKey' | 'machineSecretKey' + | 'multiDomain' > >; -const defaultOptions = { +const defaultOptions: BuildTimeOptions = { secretKey: '', machineSecretKey: '', jwtKey: '', @@ -29,10 +28,8 @@ const defaultOptions = { apiVersion: undefined, proxyUrl: '', publishableKey: '', - isSatellite: false, - domain: '', audience: '', -} satisfies BuildTimeOptions; +}; /** * @internal diff --git a/packages/backend/src/tokens/types.ts b/packages/backend/src/tokens/types.ts index c927d3c5ba7..cccbf9adbd9 100644 --- a/packages/backend/src/tokens/types.ts +++ b/packages/backend/src/tokens/types.ts @@ -1,5 +1,5 @@ import type { MatchFunction } from '@clerk/shared/pathToRegexp'; -import type { PendingSessionOptions } from '@clerk/shared/types'; +import type { MultiDomainConfigPrimitives, PendingSessionOptions } from '@clerk/shared/types'; import type { ApiClient, APIKey, IdPOAuthAccessToken, M2MToken } from '../api'; import type { @@ -21,15 +21,6 @@ export type AuthenticateRequestOptions = { * The Clerk Publishable Key from the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard. */ publishableKey?: string; - /** - * The domain of a [satellite application](https://clerk.com/docs/guides/dashboard/dns-domains/satellite-domains) in a multi-domain setup. - */ - domain?: string; - /** - * Whether the instance is a satellite domain in a multi-domain setup. - * @default false - */ - isSatellite?: boolean; /** * The proxy URL from a multi-domain setup. */ @@ -73,19 +64,9 @@ export type AuthenticateRequestOptions = { */ machineSecretKey?: string; /** - * Controls whether satellite apps automatically sync with the primary domain on initial page load. - * - * When `false` (default), satellite apps will skip the automatic handshake if no session cookies exist, - * and only trigger the handshake after an explicit sign-in action. This provides the best performance - * by showing the satellite app immediately without attempting to sync state first. - * - * When `true`, satellite apps will automatically trigger a handshake redirect to sync authentication - * state with the primary domain on first load, even if no session cookies exist. Use this if you want - * users who are already signed in on the primary domain to be automatically recognized on the satellite. - * - * @default false + * Multi-domain configuration for satellite applications. */ - satelliteAutoSync?: boolean; + multiDomain?: MultiDomainConfigPrimitives; } & VerifyTokenOptions; /** diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index de0562da1bf..ddb005e135f 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -2194,7 +2194,7 @@ describe('Clerk singleton', () => { }); await sut.load({ - isSatellite: true, + multiDomain: { isSatellite: true }, }); expect(sut.domain).toBe('clerk.example.com'); @@ -2207,7 +2207,7 @@ describe('Clerk singleton', () => { }); await sut.load({ - isSatellite: url => url.host === 'test.host', + multiDomain: { isSatellite: url => url.host === 'test.host' }, }); expect(sut.domain).toBe('clerk.example.com'); @@ -2220,7 +2220,7 @@ describe('Clerk singleton', () => { }); await sut.load({ - isSatellite: true, + multiDomain: { isSatellite: true }, }); expect(sut.domain).toBe('clerk.example.com'); @@ -2233,7 +2233,7 @@ describe('Clerk singleton', () => { }); await sut.load({ - isSatellite: true, + multiDomain: { isSatellite: true }, }); expect(sut.domain).toBe('clerk.test.host'); @@ -2248,7 +2248,7 @@ describe('Clerk singleton', () => { domain: 'satellite.dev', }); await sut.load({ - isSatellite: true, + multiDomain: { isSatellite: true }, signInUrl: 'https://primary.dev/sign-in', }); @@ -2262,7 +2262,7 @@ describe('Clerk singleton', () => { domain: 'satellite.com', }); await sut.load({ - isSatellite: true, + multiDomain: { isSatellite: true }, }); expect(sut.getFapiClient().buildUrl({ path: '/me' }).href).toContain('https://clerk.satellite.com/v1/me'); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 38a8e411478..41df615d9f7 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -199,7 +199,6 @@ const defaultOptions: ClerkOptions = { polling: true, standardBrowser: true, touchSession: true, - isSatellite: false, signInUrl: undefined, signUpUrl: undefined, afterSignOutUrl: undefined, @@ -325,7 +324,7 @@ export class Clerk implements ClerkInterface { get isSatellite(): boolean { if (inBrowser()) { - return handleValueOrFn(this.#options.isSatellite, new URL(window.location.href), false); + return handleValueOrFn(this.#options.multiDomain?.isSatellite, new URL(window.location.href), false); } return false; } @@ -2705,10 +2704,10 @@ export class Clerk implements ClerkInterface { return true; } - // Check if satelliteAutoSync is disabled - if so, skip automatic sync + // Check if autoSync is disabled - if so, skip automatic sync // unless explicitly triggered via __clerk_synced=false - if (this.#options.satelliteAutoSync === false) { - // Skip automatic sync when satelliteAutoSync is false + if (this.#options.multiDomain?.autoSync === false) { + // Skip automatic sync when autoSync is false return false; } diff --git a/packages/express/src/authenticateRequest.ts b/packages/express/src/authenticateRequest.ts index 4788760ae98..4e88fad9fd5 100644 --- a/packages/express/src/authenticateRequest.ts +++ b/packages/express/src/authenticateRequest.ts @@ -32,11 +32,11 @@ export const authenticateRequest = (opts: AuthenticateRequestParams) => { const machineSecretKey = options?.machineSecretKey || env.machineSecretKey; const publishableKey = options?.publishableKey || env.publishableKey; - const isSatellite = handleValueOrFn(options?.isSatellite, clerkRequest.clerkUrl, env.isSatellite); - const domain = handleValueOrFn(options?.domain, clerkRequest.clerkUrl) || env.domain; + const isSatellite = handleValueOrFn(options?.multiDomain?.isSatellite, clerkRequest.clerkUrl, env.isSatellite); + const domain = handleValueOrFn(options?.multiDomain?.domain, clerkRequest.clerkUrl) || env.domain; const signInUrl = options?.signInUrl || env.signInUrl; const proxyUrl = absoluteProxyUrl( - handleValueOrFn(options?.proxyUrl, clerkRequest.clerkUrl, env.proxyUrl), + handleValueOrFn(options?.multiDomain?.proxyUrl ?? options?.proxyUrl, clerkRequest.clerkUrl, env.proxyUrl), clerkRequest.clerkUrl.toString(), ); @@ -56,8 +56,12 @@ export const authenticateRequest = (opts: AuthenticateRequestParams) => { jwtKey, authorizedParties, proxyUrl, - isSatellite, - domain, + multiDomain: isSatellite + ? { + isSatellite, + ...(domain ? { domain } : { proxyUrl: proxyUrl! }), + } + : undefined, signInUrl, acceptsToken, }); diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index f5ce9a3190d..a26d74797cc 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -109,7 +109,7 @@ export const auth: AuthFn = (async (options?: AuthOptions) => { signInUrl: decryptedRequestData.signInUrl || SIGN_IN_URL, signUpUrl: decryptedRequestData.signUpUrl || SIGN_UP_URL, sessionStatus: authObject.tokenType === TokenType.SessionToken ? authObject.sessionStatus : null, - isSatellite: decryptedRequestData.isSatellite, + isSatellite: decryptedRequestData.multiDomain?.isSatellite, }), returnBackUrl === null ? '' : returnBackUrl || clerkUrl?.toString(), ] as const; diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts index 69419e2d504..102a1190502 100644 --- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts +++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts @@ -138,12 +138,12 @@ describe('ClerkMiddleware type tests', () => { clerkMiddlewareMock({ ...defaultProps, proxyUrl: 'test' }); }); - it('proxyUrl + isSatellite (satellite app)', () => { - clerkMiddlewareMock({ ...defaultProps, proxyUrl: 'test', isSatellite: true }); + it('proxyUrl + multiDomain (satellite app)', () => { + clerkMiddlewareMock({ ...defaultProps, multiDomain: { isSatellite: true, proxyUrl: 'test' } }); }); - it('domain + isSatellite (satellite app)', () => { - clerkMiddlewareMock({ ...defaultProps, domain: 'test', isSatellite: true }); + it('domain + multiDomain (satellite app)', () => { + clerkMiddlewareMock({ ...defaultProps, multiDomain: { isSatellite: true, domain: 'test' } }); }); }); }); diff --git a/packages/nextjs/src/server/utils.ts b/packages/nextjs/src/server/utils.ts index d062891e0bf..5ba7798ab4a 100644 --- a/packages/nextjs/src/server/utils.ts +++ b/packages/nextjs/src/server/utils.ts @@ -109,19 +109,20 @@ export function decorateRequest( } export const handleMultiDomainAndProxy = (clerkRequest: ClerkRequest, opts: AuthenticateRequestOptions) => { - const relativeOrAbsoluteProxyUrl = handleValueOrFn(opts?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL); + const isSatellite = handleValueOrFn(opts.multiDomain?.isSatellite, new URL(clerkRequest.url), IS_SATELLITE); + const domain = handleValueOrFn(opts.multiDomain?.domain, new URL(clerkRequest.url), DOMAIN); + const signInUrl = opts?.signInUrl || SIGN_IN_URL; + + // Resolve proxyUrl: multiDomain.proxyUrl takes precedence, then top-level, then env + const rawProxyUrl = handleValueOrFn(opts.multiDomain?.proxyUrl ?? opts?.proxyUrl, clerkRequest.clerkUrl, PROXY_URL); let proxyUrl; - if (!!relativeOrAbsoluteProxyUrl && !isHttpOrHttps(relativeOrAbsoluteProxyUrl)) { - proxyUrl = new URL(relativeOrAbsoluteProxyUrl, clerkRequest.clerkUrl).toString(); + if (!!rawProxyUrl && !isHttpOrHttps(rawProxyUrl)) { + proxyUrl = new URL(rawProxyUrl, clerkRequest.clerkUrl).toString(); } else { - proxyUrl = relativeOrAbsoluteProxyUrl; + proxyUrl = rawProxyUrl; } - const isSatellite = handleValueOrFn(opts.isSatellite, new URL(clerkRequest.url), IS_SATELLITE); - const domain = handleValueOrFn(opts.domain, new URL(clerkRequest.url), DOMAIN); - const signInUrl = opts?.signInUrl || SIGN_IN_URL; - if (isSatellite && !proxyUrl && !domain) { throw new Error(missingDomainAndProxy); } @@ -132,8 +133,13 @@ export const handleMultiDomainAndProxy = (clerkRequest: ClerkRequest, opts: Auth return { proxyUrl, - isSatellite, - domain, + multiDomain: isSatellite + ? { + isSatellite, + ...(domain ? { domain } : { proxyUrl: proxyUrl! }), + ...(opts.multiDomain?.autoSync !== undefined ? { autoSync: opts.multiDomain.autoSync } : {}), + } + : undefined, signInUrl, }; }; diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index dab4279d06a..8194b50d6ec 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -12,8 +12,14 @@ export const mergeNextClerkPropsWithEnv = (props: Omit({ signInForceRedirectUrl: options.signInForceRedirectUrl, signUpForceRedirectUrl: options.signUpForceRedirectUrl, signUpUrl: options.signUpUrl, - domain: options.domain, + multiDomain: options.multiDomain, // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUiUrl to support // NUXT_PUBLIC_CLERK_JS_URL and NUXT_PUBLIC_CLERK_UI_URL env vars. jsUrl: options.clerkJSUrl, uiUrl: options.clerkUiUrl, clerkJSVariant: options.clerkJSVariant, clerkJSVersion: options.clerkJSVersion, - isSatellite: options.isSatellite, // Backend specific variables that are safe to share. // We want them to be overridable like the other public keys (e.g NUXT_PUBLIC_CLERK_PROXY_URL) proxyUrl: options.proxyUrl, diff --git a/packages/nuxt/src/runtime/server/clerkClient.ts b/packages/nuxt/src/runtime/server/clerkClient.ts index 06e7b6f81ae..812469342f4 100644 --- a/packages/nuxt/src/runtime/server/clerkClient.ts +++ b/packages/nuxt/src/runtime/server/clerkClient.ts @@ -13,8 +13,7 @@ export function clerkClient(event: H3Event) { apiUrl: runtimeConfig.public.clerk.apiUrl, apiVersion: runtimeConfig.public.clerk.apiVersion, proxyUrl: runtimeConfig.public.clerk.proxyUrl, - domain: runtimeConfig.public.clerk.domain, - isSatellite: runtimeConfig.public.clerk.isSatellite, + multiDomain: runtimeConfig.public.clerk.multiDomain, secretKey: runtimeConfig.clerk.secretKey, machineSecretKey: runtimeConfig.clerk.machineSecretKey, jwtKey: runtimeConfig.clerk.jwtKey, diff --git a/packages/react-router/src/server/__tests__/clerkMiddleware.test.ts b/packages/react-router/src/server/__tests__/clerkMiddleware.test.ts index d067a2cb948..04991240e74 100644 --- a/packages/react-router/src/server/__tests__/clerkMiddleware.test.ts +++ b/packages/react-router/src/server/__tests__/clerkMiddleware.test.ts @@ -67,8 +67,7 @@ describe('clerkMiddleware', () => { secretKey: 'sk_test_...', jwtKey: undefined, proxyUrl: undefined, - isSatellite: undefined, - domain: undefined, + multiDomain: undefined, publishableKey: 'pk_test_...', machineSecretKey: undefined, audience: '', diff --git a/packages/react-router/src/server/clerkClient.ts b/packages/react-router/src/server/clerkClient.ts index b37254c579d..ea9ce9075f5 100644 --- a/packages/react-router/src/server/clerkClient.ts +++ b/packages/react-router/src/server/clerkClient.ts @@ -6,15 +6,14 @@ import type { ClerkMiddlewareOptions } from './types'; export const clerkClient = (args: DataFunctionArgs, overrides: ClerkMiddlewareOptions = {}) => { const options = loadOptions(args, overrides); - const { apiUrl, secretKey, jwtKey, proxyUrl, isSatellite, domain, publishableKey, machineSecretKey } = options; + const { apiUrl, secretKey, jwtKey, proxyUrl, multiDomain, publishableKey, machineSecretKey } = options; return createClerkClient({ apiUrl, secretKey, jwtKey, proxyUrl, - isSatellite, - domain, + multiDomain, publishableKey, machineSecretKey, userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`, diff --git a/packages/react-router/src/server/clerkMiddleware.ts b/packages/react-router/src/server/clerkMiddleware.ts index 19af698c118..fb582d7f67a 100644 --- a/packages/react-router/src/server/clerkMiddleware.ts +++ b/packages/react-router/src/server/clerkMiddleware.ts @@ -42,8 +42,7 @@ export const clerkMiddleware = (options?: ClerkMiddlewareOptions): MiddlewareFun secretKey, jwtKey, proxyUrl, - isSatellite, - domain, + multiDomain, publishableKey, machineSecretKey, audience, @@ -58,8 +57,7 @@ export const clerkMiddleware = (options?: ClerkMiddlewareOptions): MiddlewareFun secretKey, jwtKey, proxyUrl, - isSatellite, - domain, + multiDomain, publishableKey, machineSecretKey, audience, diff --git a/packages/react-router/src/server/legacyAuthenticateRequest.ts b/packages/react-router/src/server/legacyAuthenticateRequest.ts index 7096059372b..3f75eba3969 100644 --- a/packages/react-router/src/server/legacyAuthenticateRequest.ts +++ b/packages/react-router/src/server/legacyAuthenticateRequest.ts @@ -13,7 +13,7 @@ export async function legacyAuthenticateRequest( const { request } = args; const { audience, authorizedParties } = opts; - const { apiUrl, secretKey, jwtKey, proxyUrl, isSatellite, domain, publishableKey, machineSecretKey } = opts; + const { apiUrl, secretKey, jwtKey, proxyUrl, multiDomain, publishableKey, machineSecretKey } = opts; const { signInUrl, signUpUrl } = opts; const requestState = await clerkClient(args).authenticateRequest(patchRequest(request), { @@ -21,8 +21,7 @@ export async function legacyAuthenticateRequest( secretKey, jwtKey, proxyUrl, - isSatellite, - domain, + multiDomain, publishableKey, machineSecretKey, audience, diff --git a/packages/react-router/src/server/loadOptions.ts b/packages/react-router/src/server/loadOptions.ts index 2ae4dab747f..afcc2c997ac 100644 --- a/packages/react-router/src/server/loadOptions.ts +++ b/packages/react-router/src/server/loadOptions.ts @@ -29,11 +29,13 @@ export const loadOptions = (args: DataFunctionArgs, overrides: ClerkMiddlewareOp const publishableKey = overrides.publishableKey || getPublicEnvVariables(context).publishableKey; const jwtKey = overrides.jwtKey || getEnvVariable('CLERK_JWT_KEY', context); const apiUrl = getEnvVariable('CLERK_API_URL', context) || apiUrlFromPublishableKey(publishableKey); - const domain = handleValueOrFn(overrides.domain, new URL(request.url)) || getPublicEnvVariables(context).domain; const isSatellite = - handleValueOrFn(overrides.isSatellite, new URL(request.url)) || getPublicEnvVariables(context).isSatellite; + handleValueOrFn(overrides.multiDomain?.isSatellite, new URL(request.url)) || + getPublicEnvVariables(context).isSatellite; + const domain = + handleValueOrFn(overrides.multiDomain?.domain, new URL(request.url)) || getPublicEnvVariables(context).domain; const relativeOrAbsoluteProxyUrl = handleValueOrFn( - overrides?.proxyUrl, + overrides.multiDomain?.proxyUrl ?? overrides?.proxyUrl, clerkRequest.clerkUrl, getPublicEnvVariables(context).proxyUrl, ); @@ -73,8 +75,6 @@ export const loadOptions = (args: DataFunctionArgs, overrides: ClerkMiddlewareOp publishableKey, jwtKey, apiUrl, - domain, - isSatellite, proxyUrl, signInUrl, signUpUrl, @@ -82,5 +82,12 @@ export const loadOptions = (args: DataFunctionArgs, overrides: ClerkMiddlewareOp signUpForceRedirectUrl, signInFallbackRedirectUrl, signUpFallbackRedirectUrl, + multiDomain: isSatellite + ? { + isSatellite, + ...(domain ? { domain } : { proxyUrl: proxyUrl! }), + ...(overrides.multiDomain?.autoSync !== undefined ? { autoSync: overrides.multiDomain.autoSync } : {}), + } + : undefined, }; }; diff --git a/packages/react-router/src/server/types.ts b/packages/react-router/src/server/types.ts index 467afce52a9..9ee675c6281 100644 --- a/packages/react-router/src/server/types.ts +++ b/packages/react-router/src/server/types.ts @@ -6,7 +6,7 @@ import type { SignedOutAuthObject, } from '@clerk/backend/internal'; import type { - MultiDomainAndOrProxy, + MultiDomainAndOrProxyPrimitives, SignInFallbackRedirectUrl, SignInForceRedirectUrl, SignUpFallbackRedirectUrl, @@ -42,7 +42,7 @@ export type ClerkMiddlewareOptions = { */ organizationSyncOptions?: OrganizationSyncOptions; } & Pick & - MultiDomainAndOrProxy & + MultiDomainAndOrProxyPrimitives & SignInForceRedirectUrl & SignInFallbackRedirectUrl & SignUpForceRedirectUrl & diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index b39bf352b8c..170fca1dcc3 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -257,8 +257,8 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { constructor(options: IsomorphicClerkOptions) { this.#publishableKey = options?.publishableKey; - this.#proxyUrl = options?.proxyUrl; - this.#domain = options?.domain; + this.#proxyUrl = options?.multiDomain?.proxyUrl ?? options?.proxyUrl; + this.#domain = options?.multiDomain?.domain; this.options = options; this.Clerk = options?.Clerk || null; this.mode = inBrowser() ? 'browser' : 'server'; @@ -300,9 +300,9 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { // This getter can run in environments where window is not available. // In those cases we should expect and use domain as a string if (typeof window !== 'undefined' && window.location) { - return handleValueOrFn(this.options.isSatellite, new URL(window.location.href), false); + return handleValueOrFn(this.options.multiDomain?.isSatellite, new URL(window.location.href), false); } - if (typeof this.options.isSatellite === 'function') { + if (typeof this.options.multiDomain?.isSatellite === 'function') { return errorThrower.throw(unsupportedNonBrowserDomainOrProxyUrlFunction); } return false; diff --git a/packages/shared/src/multiDomain.ts b/packages/shared/src/multiDomain.ts new file mode 100644 index 00000000000..2cdf16d91bd --- /dev/null +++ b/packages/shared/src/multiDomain.ts @@ -0,0 +1,6 @@ +export function resolveProxyUrl(topLevel: string | undefined, nested: string | undefined): string | undefined { + if (topLevel && nested && topLevel !== nested) { + throw new Error('proxyUrl conflict: top-level and multiDomain.proxyUrl have different values'); + } + return nested ?? topLevel; +} diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 314d186793b..b6d0009e083 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -17,7 +17,7 @@ import type { ClerkAPIResponseError } from './errors'; import type { InstanceType } from './instance'; import type { DisplayThemeJSON } from './json'; import type { LocalizationResource } from './localization'; -import type { DomainOrProxyUrl, MultiDomainAndOrProxy } from './multiDomain'; +import type { DomainOrProxyUrl, MultiDomainAndOrProxy, MultiDomainConfig } from './multiDomain'; import type { OAuthProvider, OAuthScope } from './oauth'; import type { OrganizationResource } from './organization'; import type { OrganizationCustomRoleKey } from './organizationMembership'; @@ -42,7 +42,7 @@ import type { State } from './state'; import type { Web3Strategy } from './strategies'; import type { TelemetryCollector } from './telemetry'; import type { UserResource } from './user'; -import type { Autocomplete, DeepPartial, DeepSnakeToCamel, Without } from './utils'; +import type { Autocomplete, DeepPartial, DeepSnakeToCamel } from './utils'; import type { JoinWaitlistParams, WaitlistResource } from './waitlist'; /** @@ -1128,24 +1128,6 @@ export type ClerkOptions = ClerkOptionsNavigation & * An optional array of protocols to validate user-provided redirect URLs against. If no match is made, the redirect is considered unsafe and the default redirect will be used with a warning logged in the console. */ allowedRedirectProtocols?: Array; - /** - * This option defines that the application is a satellite application. - */ - isSatellite?: boolean | ((url: URL) => boolean); - /** - * Controls whether satellite apps automatically sync with the primary domain on initial page load. - * - * When `false` (default), satellite apps will skip the automatic handshake if no session cookies exist, - * and only trigger the handshake after an explicit sign-in action. This provides the best performance - * by showing the satellite app immediately without attempting to sync state first. - * - * When `true`, satellite apps will automatically trigger a handshake redirect to sync authentication - * state with the primary domain on first load, even if no session cookies exist. Use this if you want - * users who are already signed in on the primary domain to be automatically recognized on the satellite. - * - * @default false - */ - satelliteAutoSync?: boolean; /** * Controls whether or not Clerk will collect [telemetry data](https://clerk.com/docs/guides/how-clerk-works/security/clerk-telemetry). If set to `debug`, telemetry events are only logged to the console and not sent to Clerk. */ @@ -1224,6 +1206,10 @@ export type ClerkOptions = ClerkOptionsNavigation & * @default undefined */ taskUrls?: Partial>; + /** + * Multi-domain configuration for satellite applications. + */ + multiDomain?: MultiDomainConfig; }; export interface NavigateOptions { @@ -2388,7 +2374,7 @@ export interface BrowserClerkConstructor { } export interface HeadlessBrowserClerk extends Clerk { - load: (opts?: Without) => Promise; + load: (opts?: ClerkOptions) => Promise; updateClient: (client: ClientResource) => void; } @@ -2405,7 +2391,7 @@ export type ClerkProp = | undefined | null; -export type IsomorphicClerkOptions = Without & { +export type IsomorphicClerkOptions = ClerkOptions & { Clerk?: ClerkProp; /** * The URL that `@clerk/clerk-js` should be hot-loaded from. diff --git a/packages/shared/src/types/multiDomain.ts b/packages/shared/src/types/multiDomain.ts index 3661e7132bf..5498f7ef2a6 100644 --- a/packages/shared/src/types/multiDomain.ts +++ b/packages/shared/src/types/multiDomain.ts @@ -1,64 +1,26 @@ -import type { ClerkOptions } from './clerk'; +export type MultiDomainConfig = { + isSatellite: boolean | ((url: URL) => boolean); + autoSync?: boolean; + domain?: string | ((url: URL) => string); + proxyUrl?: string | ((url: URL) => string); +}; + +export type MultiDomainConfigPrimitives = { + isSatellite: boolean; + autoSync?: boolean; + domain?: string; + proxyUrl?: string; +}; -/** - * You can configure proxy and satellite domains in a few ways: - * - * 1) none of them are set - * 2) only `proxyUrl` is set - * 3) `isSatellite` and `proxyUrl` are set - * 4) `isSatellite` and `domain` are set - */ -export type MultiDomainAndOrProxy = - | { - /** - * A boolean that indicates whether the application is a satellite application. - */ - isSatellite?: never; - /** - * **Required for applications that run behind a reverse proxy**. The URL that Clerk will proxy requests to. Can be either a relative path (`/__clerk`) or a full URL (`https:///__clerk`). - */ - proxyUrl?: never | string | ((url: URL) => string); - /** - * **Required if your application is a satellite application**. Sets the domain of the satellite application. - */ - domain?: never; - } - | { - isSatellite: Exclude; - proxyUrl?: never; - domain: string | ((url: URL) => string); - } - | { - isSatellite: Exclude; - proxyUrl: string | ((url: URL) => string); - domain?: never; - }; +export type MultiDomainAndOrProxy = { + proxyUrl?: string | ((url: URL) => string); + multiDomain?: MultiDomainConfig; +}; -export type MultiDomainAndOrProxyPrimitives = - | { - /** - * A boolean that indicates whether the application is a satellite application. - */ - isSatellite?: never; - /** - * **Required for applications that run behind a reverse proxy**. The URL that Clerk will proxy requests to. Can be either a relative path (`/__clerk`) or a full URL (`https:///__clerk`). - */ - proxyUrl?: never | string; - /** - * **Required if your application is a satellite application**. Sets the domain of the satellite application. - */ - domain?: never; - } - | { - isSatellite: boolean; - proxyUrl?: never; - domain: string; - } - | { - isSatellite: boolean; - proxyUrl: string; - domain?: never; - }; +export type MultiDomainAndOrProxyPrimitives = { + proxyUrl?: string; + multiDomain?: MultiDomainConfigPrimitives; +}; export type DomainOrProxyUrl = { /** diff --git a/packages/tanstack-react-start/src/client/utils.ts b/packages/tanstack-react-start/src/client/utils.ts index 3798f1b212f..c6a824cc2bb 100644 --- a/packages/tanstack-react-start/src/client/utils.ts +++ b/packages/tanstack-react-start/src/client/utils.ts @@ -35,8 +35,12 @@ export const pickFromClerkInitState = ( clerkSsrState: __clerk_ssr_state, publishableKey: __publishableKey, proxyUrl: __proxyUrl, - domain: __domain, - isSatellite: !!__isSatellite, + multiDomain: __isSatellite + ? { + isSatellite: true, + ...(__domain ? { domain: __domain } : {}), + } + : undefined, signInUrl: __signInUrl, signUpUrl: __signUpUrl, clerkJSUrl: __clerkJSUrl, @@ -59,8 +63,14 @@ export const mergeWithPublicEnvs = (restInitState: any) => { return { ...restInitState, publishableKey: restInitState.publishableKey || getPublicEnvVariables().publishableKey, - domain: restInitState.domain || getPublicEnvVariables().domain, - isSatellite: restInitState.isSatellite || getPublicEnvVariables().isSatellite, + multiDomain: + restInitState.multiDomain || + (getPublicEnvVariables().isSatellite + ? { + isSatellite: true, + ...(getPublicEnvVariables().domain ? { domain: getPublicEnvVariables().domain } : {}), + } + : undefined), signInUrl: restInitState.signInUrl || getPublicEnvVariables().signInUrl, signUpUrl: restInitState.signUpUrl || getPublicEnvVariables().signUpUrl, clerkJSUrl: restInitState.clerkJSUrl || getPublicEnvVariables().clerkJsUrl, diff --git a/packages/tanstack-react-start/src/server/clerkClient.ts b/packages/tanstack-react-start/src/server/clerkClient.ts index 5dedf992072..8505cdc4277 100644 --- a/packages/tanstack-react-start/src/server/clerkClient.ts +++ b/packages/tanstack-react-start/src/server/clerkClient.ts @@ -13,8 +13,12 @@ const clerkClient = (options?: ClerkOptions): ClerkClient => { apiVersion: commonEnv.API_VERSION, userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`, proxyUrl: commonEnv.PROXY_URL, - domain: commonEnv.DOMAIN, - isSatellite: commonEnv.IS_SATELLITE, + multiDomain: commonEnv.IS_SATELLITE + ? { + isSatellite: true, + ...(commonEnv.DOMAIN ? { domain: commonEnv.DOMAIN } : {}), + } + : undefined, sdkMetadata: commonEnv.SDK_METADATA, telemetry: { disabled: commonEnv.TELEMETRY_DISABLED, diff --git a/packages/tanstack-react-start/src/server/loadOptions.ts b/packages/tanstack-react-start/src/server/loadOptions.ts index 694494a8c6d..979fe2c74df 100644 --- a/packages/tanstack-react-start/src/server/loadOptions.ts +++ b/packages/tanstack-react-start/src/server/loadOptions.ts @@ -16,12 +16,11 @@ export const loadOptions = (request: ClerkRequest, overrides: LoaderOptions = {} const publishableKey = overrides.publishableKey || commonEnv.PUBLISHABLE_KEY; const jwtKey = overrides.jwtKey || commonEnv.CLERK_JWT_KEY; const apiUrl = getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey); - const domain = overrides.domain || commonEnv.DOMAIN; - const isSatellite = overrides.isSatellite || commonEnv.IS_SATELLITE; - const relativeOrAbsoluteProxyUrl = overrides.proxyUrl || commonEnv.PROXY_URL; + const isSatellite = overrides.multiDomain?.isSatellite || commonEnv.IS_SATELLITE; + const domain = overrides.multiDomain?.domain || commonEnv.DOMAIN; + const relativeOrAbsoluteProxyUrl = overrides.multiDomain?.proxyUrl || overrides.proxyUrl || commonEnv.PROXY_URL; const signInUrl = overrides.signInUrl || commonEnv.SIGN_IN_URL; const signUpUrl = overrides.signUpUrl || commonEnv.SIGN_UP_URL; - const satelliteAutoSync = overrides.satelliteAutoSync; let proxyUrl; if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) { @@ -54,11 +53,15 @@ export const loadOptions = (request: ClerkRequest, overrides: LoaderOptions = {} publishableKey, jwtKey, apiUrl, - domain, - isSatellite, proxyUrl, signInUrl, signUpUrl, - satelliteAutoSync, + multiDomain: isSatellite + ? { + isSatellite, + ...(domain ? { domain } : { proxyUrl: proxyUrl! }), + ...(overrides.multiDomain?.autoSync !== undefined ? { autoSync: overrides.multiDomain.autoSync } : {}), + } + : undefined, }; }; diff --git a/packages/tanstack-react-start/src/server/types.ts b/packages/tanstack-react-start/src/server/types.ts index b6e6b4c88cc..0badeeb578e 100644 --- a/packages/tanstack-react-start/src/server/types.ts +++ b/packages/tanstack-react-start/src/server/types.ts @@ -16,20 +16,6 @@ export type ClerkMiddlewareOptions = { signInUrl?: string; signUpUrl?: string; organizationSyncOptions?: OrganizationSyncOptions; - /** - * Controls whether satellite apps automatically sync with the primary domain on initial page load. - * - * When `false` (default), satellite apps will skip the automatic handshake if no session cookies exist, - * and only trigger the handshake after an explicit sign-in action. This provides the best performance - * by showing the satellite app immediately without attempting to sync state first. - * - * When `true`, satellite apps will automatically trigger a handshake redirect to sync authentication - * state with the primary domain on first load, even if no session cookies exist. Use this if you want - * users who are already signed in on the primary domain to be automatically recognized on the satellite. - * - * @default false - */ - satelliteAutoSync?: boolean; } & Pick & MultiDomainAndOrProxyPrimitives & SignInForceRedirectUrl & diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index 91a89ffe91d..4ba77896fbc 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -23,7 +23,7 @@ declare global { } } -export type PluginOptions = Without & +export type PluginOptions = Without & MultiDomainAndOrProxy & { initialState?: InitialState; appearance?: Appearance;