Skip to content

Commit cf813c8

Browse files
committed
store promise right away to prevent race conditions
1 parent 8bf6b64 commit cf813c8

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

packages/clerk-js/src/core/resources/Session.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,13 @@ export class Session extends BaseResource implements SessionResource {
393393

394394
const tokenResolver = Token.create(path, params);
395395

396+
// Cache the promise immediately to prevent concurrent calls from triggering duplicate requests
397+
SessionTokenCache.set({ tokenId, tokenResolver });
398+
396399
return tokenResolver.then(token => {
397400
const tokenRaw = token.getRawString();
398401

402+
// Update cache with broadcast metadata now that we have the resolved token
399403
SessionTokenCache.set(
400404
{ tokenId, tokenResolver: Promise.resolve(token) },
401405
tokenRaw

packages/clerk-js/src/core/resources/__tests__/Session.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,102 @@ describe('Session', () => {
259259
body: { organizationId: 'newActiveOrganization' },
260260
});
261261
});
262+
263+
it('deduplicates concurrent getToken calls to prevent multiple API requests', async () => {
264+
BaseResource.clerk = clerkMock() as any;
265+
266+
const session = new Session({
267+
status: 'active',
268+
id: 'session_1',
269+
object: 'session',
270+
user: createUser({}),
271+
last_active_organization_id: null,
272+
actor: null,
273+
created_at: new Date().getTime(),
274+
updated_at: new Date().getTime(),
275+
} as SessionJSON);
276+
277+
const requestSpy = BaseResource.clerk.getFapiClient().request as Mock<any>;
278+
requestSpy.mockClear();
279+
280+
const [token1, token2, token3] = await Promise.all([session.getToken(), session.getToken(), session.getToken()]);
281+
282+
expect(requestSpy).toHaveBeenCalledTimes(1);
283+
expect(token1).toEqual(mockJwt);
284+
expect(token2).toEqual(mockJwt);
285+
expect(token3).toEqual(mockJwt);
286+
});
287+
288+
it('deduplicates concurrent getToken calls with same template', async () => {
289+
BaseResource.clerk = clerkMock() as any;
290+
291+
const session = new Session({
292+
status: 'active',
293+
id: 'session_1',
294+
object: 'session',
295+
user: createUser({}),
296+
last_active_organization_id: null,
297+
actor: null,
298+
created_at: new Date().getTime(),
299+
updated_at: new Date().getTime(),
300+
} as SessionJSON);
301+
302+
const requestSpy = BaseResource.clerk.getFapiClient().request as Mock<any>;
303+
requestSpy.mockClear();
304+
305+
const [token1, token2] = await Promise.all([
306+
session.getToken({ template: 'custom-template' }),
307+
session.getToken({ template: 'custom-template' }),
308+
]);
309+
310+
expect(requestSpy).toHaveBeenCalledTimes(1);
311+
expect(token1).toEqual(mockJwt);
312+
expect(token2).toEqual(mockJwt);
313+
});
314+
315+
it('does not deduplicate getToken calls with different templates', async () => {
316+
BaseResource.clerk = clerkMock() as any;
317+
318+
const session = new Session({
319+
status: 'active',
320+
id: 'session_1',
321+
object: 'session',
322+
user: createUser({}),
323+
last_active_organization_id: null,
324+
actor: null,
325+
created_at: new Date().getTime(),
326+
updated_at: new Date().getTime(),
327+
} as SessionJSON);
328+
329+
const requestSpy = BaseResource.clerk.getFapiClient().request as Mock<any>;
330+
requestSpy.mockClear();
331+
332+
await Promise.all([session.getToken({ template: 'template1' }), session.getToken({ template: 'template2' })]);
333+
334+
expect(requestSpy).toHaveBeenCalledTimes(2);
335+
});
336+
337+
it('does not deduplicate getToken calls with different organization IDs', async () => {
338+
BaseResource.clerk = clerkMock() as any;
339+
340+
const session = new Session({
341+
status: 'active',
342+
id: 'session_1',
343+
object: 'session',
344+
user: createUser({}),
345+
last_active_organization_id: null,
346+
actor: null,
347+
created_at: new Date().getTime(),
348+
updated_at: new Date().getTime(),
349+
} as SessionJSON);
350+
351+
const requestSpy = BaseResource.clerk.getFapiClient().request as Mock<any>;
352+
requestSpy.mockClear();
353+
354+
await Promise.all([session.getToken({ organizationId: 'org_1' }), session.getToken({ organizationId: 'org_2' })]);
355+
356+
expect(requestSpy).toHaveBeenCalledTimes(2);
357+
});
262358
});
263359

264360
describe('touch()', () => {

0 commit comments

Comments
 (0)