diff --git a/.changeset/old-wombats-tease.md b/.changeset/old-wombats-tease.md new file mode 100644 index 00000000000..7c099c005e5 --- /dev/null +++ b/.changeset/old-wombats-tease.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Added debug query param to token requests initiated with `skipCache` option. diff --git a/packages/clerk-js/src/core/constants.ts b/packages/clerk-js/src/core/constants.ts index d7c0b640d73..bddf6b20541 100644 --- a/packages/clerk-js/src/core/constants.ts +++ b/packages/clerk-js/src/core/constants.ts @@ -12,9 +12,9 @@ export const PRESERVED_QUERYSTRING_PARAMS = [ ]; export const CLERK_MODAL_STATE = '__clerk_modal_state'; -export const CLERK_SYNCED = '__clerk_synced'; -export const CLERK_SUFFIXED_COOKIES = 'suffixed_cookies'; export const CLERK_SATELLITE_URL = '__clerk_satellite_url'; +export const CLERK_SUFFIXED_COOKIES = 'suffixed_cookies'; +export const CLERK_SYNCED = '__clerk_synced'; export const ERROR_CODES = { FORM_IDENTIFIER_NOT_FOUND: 'form_identifier_not_found', FORM_PASSWORD_INCORRECT: 'form_password_incorrect', diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index b774b12730f..7ac829ca520 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -405,7 +405,7 @@ export class Session extends BaseResource implements SessionResource { // TODO: update template endpoint to accept organizationId const params: Record = template ? {} : { organizationId }; - const tokenResolver = Token.create(path, params); + const tokenResolver = Token.create(path, params, skipCache); // Cache the promise immediately to prevent concurrent calls from triggering duplicate requests SessionTokenCache.set({ tokenId, tokenResolver }); diff --git a/packages/clerk-js/src/core/resources/Token.ts b/packages/clerk-js/src/core/resources/Token.ts index 56f3581b51c..2b75d8b3c52 100644 --- a/packages/clerk-js/src/core/resources/Token.ts +++ b/packages/clerk-js/src/core/resources/Token.ts @@ -1,18 +1,22 @@ import type { JWT, TokenJSON, TokenJSONSnapshot, TokenResource } from '@clerk/shared/types'; -import { decode } from '../../utils'; -import { BaseResource } from './internal'; +import { decode } from '@/utils'; + +import { BaseResource } from './Base'; export class Token extends BaseResource implements TokenResource { pathRoot = 'tokens'; jwt?: JWT; - static async create(path: string, body: any = {}): Promise { + static async create(path: string, body: any = {}, skipCache = false): Promise { + const search = skipCache ? `debug=skip_cache` : undefined; + const json = (await BaseResource._fetch({ - path, - method: 'POST', body, + method: 'POST', + path, + search, })) as unknown as TokenJSON; return new Token(json, path); diff --git a/packages/clerk-js/src/core/resources/__tests__/Token.test.ts b/packages/clerk-js/src/core/resources/__tests__/Token.test.ts index aed2f6f2863..bd1830cb4e5 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Token.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Token.test.ts @@ -1,7 +1,7 @@ import type { InstanceType } from '@clerk/shared/types'; import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; -import { mockFetch, mockNetworkFailedFetch } from '@/test/core-fixtures'; +import { mockFetch, mockJwt, mockNetworkFailedFetch } from '@/test/core-fixtures'; import { debugLogger } from '@/utils/debug'; import { SUPPORTED_FAPI_VERSION } from '../../constants'; @@ -44,7 +44,7 @@ describe('Token', () => { }); describe('with offline browser and network failure', () => { - let warnSpy; + let warnSpy: ReturnType; beforeEach(() => { Object.defineProperty(window.navigator, 'onLine', { @@ -103,5 +103,54 @@ describe('Token', () => { }); }); }); + + it('creates token successfully with valid response', async () => { + mockFetch(true, 200, { jwt: mockJwt }); + BaseResource.clerk = { getFapiClient: () => createFapiClient(baseFapiClientOptions) } as any; + + const token = await Token.create('/path/to/tokens', { organizationId: 'org_123' }); + + expect(global.fetch).toHaveBeenCalledTimes(1); + const [url, options] = (global.fetch as Mock).mock.calls[0]; + expect(url.toString()).toContain('https://clerk.example.com/v1/path/to/tokens'); + expect(url.toString()).not.toContain('debug=skip_cache'); + expect(options).toMatchObject({ + body: 'organization_id=org_123', + credentials: 'include', + method: 'POST', + }); + expect(token).toBeInstanceOf(Token); + expect(token.jwt).toBeDefined(); + }); + + it('creates token with skipCache=false by default', async () => { + mockFetch(true, 200, { jwt: mockJwt }); + BaseResource.clerk = { getFapiClient: () => createFapiClient(baseFapiClientOptions) } as any; + + await Token.create('/path/to/tokens'); + + const [url] = (global.fetch as Mock).mock.calls[0]; + expect(url.toString()).not.toContain('debug=skip_cache'); + }); + + it('creates token with skipCache=true and includes query parameter', async () => { + mockFetch(true, 200, { jwt: mockJwt }); + BaseResource.clerk = { getFapiClient: () => createFapiClient(baseFapiClientOptions) } as any; + + await Token.create('/path/to/tokens', {}, true); + + const [url] = (global.fetch as Mock).mock.calls[0]; + expect(url.toString()).toContain('debug=skip_cache'); + }); + + it('creates token with skipCache=false explicitly and excludes query parameter', async () => { + mockFetch(true, 200, { jwt: mockJwt }); + BaseResource.clerk = { getFapiClient: () => createFapiClient(baseFapiClientOptions) } as any; + + await Token.create('/path/to/tokens', {}, false); + + const [url] = (global.fetch as Mock).mock.calls[0]; + expect(url.toString()).not.toContain('debug=skip_cache'); + }); }); });