diff --git a/.changeset/stale-browsers-retry.md b/.changeset/stale-browsers-retry.md new file mode 100644 index 00000000000..bb4c61f859d --- /dev/null +++ b/.changeset/stale-browsers-retry.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fix development instance initialization when a stale dev browser value is rejected by clearing the value and retrying the environment and client requests. diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index 55b8b91c9e1..0774ca91149 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -15,7 +15,6 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test, import { mockJwt } from '@/test/core-fixtures'; import { mockNativeRuntime } from '../../test/utils'; -import type { DevBrowser } from '../auth/devBrowser'; import { Clerk } from '../clerk'; import { eventBus, events } from '../events'; import type { DisplayConfig, Organization } from '../resources/internal'; @@ -30,15 +29,19 @@ vi.mock('../resources/Environment'); const { mockCreateClientFromJwt } = vi.hoisted(() => ({ mockCreateClientFromJwt: vi.fn() })); vi.mock('../jwt-client', () => ({ createClientFromJwt: mockCreateClientFromJwt })); -vi.mock('../auth/devBrowser', () => ({ - createDevBrowser: (): DevBrowser => ({ +const { mockDevBrowser } = vi.hoisted(() => ({ + mockDevBrowser: { clear: vi.fn(), setup: vi.fn(), getDevBrowser: vi.fn(() => 'deadbeef'), setDevBrowser: vi.fn(), removeDevBrowser: vi.fn(), refreshCookies: vi.fn(), - }), + }, +})); + +vi.mock('../auth/devBrowser', () => ({ + createDevBrowser: () => mockDevBrowser, })); Client.getOrCreateInstance = vi.fn().mockImplementation(() => { @@ -762,6 +765,41 @@ describe('Clerk singleton', () => { }); describe('.load()', () => { + it('clears the stale dev browser before retrying the initial resources', async () => { + const callLog: string[] = []; + const devBrowserError = Object.assign(new Error('dev browser unauthenticated'), { + errors: [{ code: 'dev_browser_unauthenticated' }], + status: 401, + }); + + mockDevBrowser.clear.mockImplementationOnce(() => void callLog.push('clearDevBrowser')); + mockEnvironmentFetch + .mockImplementationOnce(() => { + callLog.push('environment'); + return Promise.reject(devBrowserError); + }) + .mockImplementation(() => { + callLog.push('environment'); + return Promise.resolve({ + userSettings: mockUserSettings, + displayConfig: mockDisplayConfig, + isSingleSession: () => false, + isProduction: () => false, + isDevelopmentOrStaging: () => true, + }); + }); + mockClientFetch.mockImplementation(() => { + callLog.push('client'); + return Promise.resolve({ signedInSessions: [] }); + }); + + const sut = new Clerk(developmentPublishableKey); + await sut.load({ unsafe_disableDevelopmentModeConsoleWarning: true }); + + expect(callLog).toEqual(['environment', 'client', 'clearDevBrowser', 'environment', 'client']); + expect(sut.status).toBe('ready'); + }); + describe.each(['active', 'pending'] satisfies Array)( 'when session has %s status', status => { diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 0622261d9af..2a9af9bc5cf 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -3305,7 +3305,11 @@ export class Clerk implements ClerkInterface { const initEnvironmentPromise = Environment.getInstance() .fetch({ touch: shouldTouchEnv }) .then(res => this.updateEnvironment(res)) - .catch(() => { + .catch(err => { + if (isError(err, 'dev_browser_unauthenticated')) { + throw err; + } + ++initializationDegradedCounter; const environmentSnapshot = SafeLocalStorage.getItem( CLERK_ENVIRONMENT_STORAGE_ENTRY, @@ -3367,7 +3371,11 @@ export class Clerk implements ClerkInterface { }); }; - const [, clientResult] = await allSettled([initEnvironmentPromise, initClient()]); + const [environmentResult, clientResult] = await allSettled([initEnvironmentPromise, initClient()]); + if (environmentResult.status === 'rejected') { + throw environmentResult.reason; + } + if (clientResult.status === 'rejected') { const e = clientResult.reason;