Skip to content

Commit 3b9d7d6

Browse files
committed
fixup! feat(repo): Add a library-agnostic getToken helper
1 parent 3d463d9 commit 3b9d7d6

File tree

2 files changed

+55
-60
lines changed

2 files changed

+55
-60
lines changed

packages/shared/src/__tests__/getToken.spec.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22

3+
import { ClerkRuntimeError } from '../errors/clerkRuntimeError';
34
import { getToken } from '../getToken';
45

56
type StatusHandler = (status: string) => void;
@@ -153,16 +154,20 @@ describe('getToken', () => {
153154
expect(token).toBe(mockToken);
154155
});
155156

156-
it('should timeout and return null if Clerk never loads', async () => {
157+
it('should throw ClerkRuntimeError if Clerk never loads', async () => {
157158
global.window = {} as any;
158159

159-
const tokenPromise = getToken();
160+
let caughtError: unknown;
161+
const tokenPromise = getToken().catch(e => {
162+
caughtError = e;
163+
});
160164

161165
// Fast-forward past timeout (10 seconds)
162166
await vi.advanceTimersByTimeAsync(15000);
167+
await tokenPromise;
163168

164-
const token = await tokenPromise;
165-
expect(token).toBeNull();
169+
expect(caughtError).toBeInstanceOf(ClerkRuntimeError);
170+
expect((caughtError as ClerkRuntimeError).code).toBe('clerk_runtime_load_timeout');
166171
});
167172
});
168173

@@ -210,16 +215,18 @@ describe('getToken', () => {
210215
});
211216

212217
describe('in non-browser environment', () => {
213-
it('should return null when window is undefined', async () => {
218+
it('should throw ClerkRuntimeError when window is undefined', async () => {
214219
global.window = undefined as any;
215220

216-
const token = await getToken();
217-
expect(token).toBeNull();
221+
await expect(getToken()).rejects.toThrow(ClerkRuntimeError);
222+
await expect(getToken()).rejects.toMatchObject({
223+
code: 'clerk_runtime_not_browser',
224+
});
218225
});
219226
});
220227

221228
describe('when Clerk enters error status', () => {
222-
it('should return null', async () => {
229+
it('should throw ClerkRuntimeError', async () => {
223230
let statusHandler: StatusHandler | null = null;
224231

225232
const mockClerk = {
@@ -244,13 +251,15 @@ describe('getToken', () => {
244251
(statusHandler as StatusHandler)('error');
245252
}
246253

247-
const token = await tokenPromise;
248-
expect(token).toBeNull();
254+
await expect(tokenPromise).rejects.toThrow(ClerkRuntimeError);
255+
await expect(tokenPromise).rejects.toMatchObject({
256+
code: 'clerk_runtime_init_error',
257+
});
249258
});
250259
});
251260

252261
describe('when session.getToken throws', () => {
253-
it('should return null and not propagate the error', async () => {
262+
it('should propagate the error', async () => {
254263
const mockClerk = {
255264
status: 'ready',
256265
session: {
@@ -260,8 +269,7 @@ describe('getToken', () => {
260269

261270
global.window = { Clerk: mockClerk } as any;
262271

263-
const token = await getToken();
264-
expect(token).toBeNull();
272+
await expect(getToken()).rejects.toThrow('Token fetch failed');
265273
});
266274
});
267275

packages/shared/src/getToken.ts

Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,26 @@
11
import { inBrowser } from './browser';
2-
import type { ClerkStatus, GetTokenOptions, LoadedClerk } from './types';
2+
import { ClerkRuntimeError } from './errors/clerkRuntimeError';
3+
import type { Clerk, ClerkStatus, GetTokenOptions, LoadedClerk } from './types';
34

45
const POLL_INTERVAL_MS = 50;
56
const MAX_POLL_RETRIES = 100; // 5 seconds of polling
67
const TIMEOUT_MS = 10000; // 10 second absolute timeout
78

8-
type WindowClerk = LoadedClerk & {
9-
status?: ClerkStatus;
10-
loaded?: boolean;
11-
on?: (event: 'status', handler: (status: ClerkStatus) => void, opts?: { notify?: boolean }) => void;
12-
off?: (event: 'status', handler: (status: ClerkStatus) => void) => void;
13-
};
14-
15-
function getWindowClerk(): WindowClerk | undefined {
9+
function getWindowClerk(): Clerk | undefined {
1610
if (inBrowser() && 'Clerk' in window) {
17-
return (window as unknown as { Clerk?: WindowClerk }).Clerk;
11+
return (window as unknown as { Clerk?: Clerk }).Clerk;
1812
}
1913
return undefined;
2014
}
2115

22-
class ClerkNotLoadedError extends Error {
23-
constructor() {
24-
super('Clerk: Timeout waiting for Clerk to load. Ensure ClerkProvider is mounted.');
25-
this.name = 'ClerkNotLoadedError';
26-
}
27-
}
28-
29-
class ClerkNotAvailableError extends Error {
30-
constructor() {
31-
super('Clerk: getToken can only be used in browser environments.');
32-
this.name = 'ClerkNotAvailableError';
33-
}
34-
}
35-
3616
function waitForClerk(): Promise<LoadedClerk> {
3717
return new Promise((resolve, reject) => {
3818
if (!inBrowser()) {
39-
reject(new ClerkNotAvailableError());
19+
reject(
20+
new ClerkRuntimeError('getToken can only be used in browser environments.', {
21+
code: 'clerk_runtime_not_browser',
22+
}),
23+
);
4024
return;
4125
}
4226

@@ -53,22 +37,25 @@ function waitForClerk(): Promise<LoadedClerk> {
5337
}
5438

5539
let retries = 0;
56-
let timeoutId: ReturnType<typeof setTimeout>;
5740
let statusHandler: ((status: ClerkStatus) => void) | undefined;
5841
let pollTimeoutId: ReturnType<typeof setTimeout>;
59-
let currentClerk: WindowClerk | undefined = clerk;
42+
let currentClerk: Clerk | undefined = clerk;
6043

6144
const cleanup = () => {
6245
clearTimeout(timeoutId);
6346
clearTimeout(pollTimeoutId);
64-
if (statusHandler && currentClerk?.off) {
47+
if (statusHandler && currentClerk) {
6548
currentClerk.off('status', statusHandler);
6649
}
6750
};
6851

69-
timeoutId = setTimeout(() => {
52+
const timeoutId = setTimeout(() => {
7053
cleanup();
71-
reject(new ClerkNotLoadedError());
54+
reject(
55+
new ClerkRuntimeError('Timeout waiting for Clerk to load.', {
56+
code: 'clerk_runtime_load_timeout',
57+
}),
58+
);
7259
}, TIMEOUT_MS);
7360

7461
const checkAndResolve = () => {
@@ -94,14 +81,18 @@ function waitForClerk(): Promise<LoadedClerk> {
9481
return;
9582
}
9683

97-
if (!statusHandler && currentClerk.on) {
84+
if (!statusHandler) {
9885
statusHandler = (status: ClerkStatus) => {
9986
if (status === 'ready' || status === 'degraded') {
10087
cleanup();
10188
resolve(currentClerk as LoadedClerk);
10289
} else if (status === 'error') {
10390
cleanup();
104-
reject(new ClerkNotLoadedError());
91+
reject(
92+
new ClerkRuntimeError('Clerk failed to initialize.', {
93+
code: 'clerk_runtime_init_error',
94+
}),
95+
);
10596
}
10697
};
10798

@@ -123,10 +114,13 @@ function waitForClerk(): Promise<LoadedClerk> {
123114
* @param options.organizationId - Organization ID to include in the token
124115
* @param options.leewayInSeconds - Number of seconds of leeway for token expiration
125116
* @param options.skipCache - Whether to skip the token cache
126-
* @returns A Promise that resolves to the session token, or `null` if:
127-
* - The user is not signed in
128-
* - Clerk failed to load
129-
* - Called in a non-browser environment
117+
* @returns A Promise that resolves to the session token, or `null` if the user is not signed in
118+
*
119+
* @throws {ClerkRuntimeError} When called in a non-browser environment (code: `clerk_runtime_not_browser`)
120+
*
121+
* @throws {ClerkRuntimeError} When Clerk fails to load within timeout (code: `clerk_runtime_load_timeout`)
122+
*
123+
* @throws {ClerkRuntimeError} When Clerk fails to initialize (code: `clerk_runtime_init_error`)
130124
*
131125
* @example
132126
* ```typescript
@@ -143,18 +137,11 @@ function waitForClerk(): Promise<LoadedClerk> {
143137
* ```
144138
*/
145139
export async function getToken(options?: GetTokenOptions): Promise<string | null> {
146-
try {
147-
const clerk = await waitForClerk();
140+
const clerk = await waitForClerk();
148141

149-
if (!clerk.session) {
150-
return null;
151-
}
152-
153-
return await clerk.session.getToken(options);
154-
} catch (error) {
155-
if (process.env.NODE_ENV === 'development') {
156-
console.warn('[Clerk] getToken failed:', error);
157-
}
142+
if (!clerk.session) {
158143
return null;
159144
}
145+
146+
return clerk.session.getToken(options);
160147
}

0 commit comments

Comments
 (0)