Skip to content

Commit 3eb6e94

Browse files
committed
feat(clerk-js,shared): Remove expired_token retry flow
The previous session token is now always sent in the /tokens POST body (via the `token` param), so the backend no longer needs to request it via a 422 missing_expired_token error and retry. Removes: - MissingExpiredTokenError class and its re-export from @clerk/shared - The catch-and-retry logic in Session.#createTokenResolver - 4 related tests in Session.test.ts
1 parent 07164c8 commit 3eb6e94

5 files changed

Lines changed: 9 additions & 211 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/shared': patch
3+
'@clerk/clerk-js': patch
4+
---
5+
6+
Remove `expired_token` retry flow and `MissingExpiredTokenError`. The previous session token is now always sent in the `/tokens` POST body, so the retry-with-expired-token fallback is no longer needed.

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

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import { createCheckAuthorization } from '@clerk/shared/authorization';
22
import { isBrowserOnline, isValidBrowserOnline } from '@clerk/shared/browser';
3-
import {
4-
ClerkOfflineError,
5-
ClerkRuntimeError,
6-
ClerkWebAuthnError,
7-
is4xxError,
8-
is429Error,
9-
MissingExpiredTokenError,
10-
} from '@clerk/shared/error';
3+
import { ClerkOfflineError, ClerkRuntimeError, ClerkWebAuthnError, is4xxError, is429Error } from '@clerk/shared/error';
114
import {
125
convertJSONToPublicKeyRequestOptions,
136
serializePublicKeyCredentialAssertion,
@@ -481,16 +474,8 @@ export class Session extends BaseResource implements SessionResource {
481474
const path = template ? `${this.path()}/tokens/${template}` : `${this.path()}/tokens`;
482475
// TODO: update template endpoint to accept organizationId
483476
const params: Record<string, string | null> = template ? {} : { organizationId: organizationId ?? null };
484-
const lastActiveToken = this.lastActiveToken?.getRawString();
485-
486-
const tokenResolver = Token.create(path, params, skipCache ? { debug: 'skip_cache' } : undefined).catch(e => {
487-
if (MissingExpiredTokenError.is(e) && lastActiveToken) {
488-
return Token.create(path, { ...params }, { expired_token: lastActiveToken });
489-
}
490-
throw e;
491-
});
492477

493-
return tokenResolver;
478+
return Token.create(path, params, skipCache ? { debug: 'skip_cache' } : undefined);
494479
}
495480

496481
#dispatchTokenEvents(token: TokenResource, shouldDispatch: boolean): void {

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

Lines changed: 1 addition & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ClerkAPIResponseError, ClerkOfflineError } from '@clerk/shared/error';
1+
import { ClerkOfflineError } from '@clerk/shared/error';
22
import type { InstanceType, OrganizationJSON, SessionJSON } from '@clerk/shared/types';
33
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
44

@@ -1522,153 +1522,6 @@ describe('Session', () => {
15221522
});
15231523
});
15241524

1525-
describe('origin outage mode fallback', () => {
1526-
let dispatchSpy: ReturnType<typeof vi.spyOn>;
1527-
let fetchSpy: ReturnType<typeof vi.spyOn>;
1528-
1529-
beforeEach(() => {
1530-
SessionTokenCache.clear();
1531-
dispatchSpy = vi.spyOn(eventBus, 'emit');
1532-
fetchSpy = vi.spyOn(BaseResource, '_fetch' as any);
1533-
BaseResource.clerk = clerkMock() as any;
1534-
});
1535-
1536-
afterEach(() => {
1537-
dispatchSpy?.mockRestore();
1538-
fetchSpy?.mockRestore();
1539-
BaseResource.clerk = null as any;
1540-
});
1541-
1542-
it('should retry with expired token when API returns 422 with missing_expired_token error', async () => {
1543-
const session = new Session({
1544-
status: 'active',
1545-
id: 'session_1',
1546-
object: 'session',
1547-
user: createUser({}),
1548-
last_active_organization_id: null,
1549-
last_active_token: { object: 'token', jwt: mockJwt },
1550-
actor: null,
1551-
created_at: new Date().getTime(),
1552-
updated_at: new Date().getTime(),
1553-
} as SessionJSON);
1554-
1555-
SessionTokenCache.clear();
1556-
1557-
const errorResponse = new ClerkAPIResponseError('Missing expired token', {
1558-
data: [
1559-
{ code: 'missing_expired_token', message: 'Missing expired token', long_message: 'Missing expired token' },
1560-
],
1561-
status: 422,
1562-
});
1563-
fetchSpy.mockRejectedValueOnce(errorResponse);
1564-
1565-
fetchSpy.mockResolvedValueOnce({ object: 'token', jwt: mockJwt });
1566-
1567-
await session.getToken();
1568-
1569-
expect(fetchSpy).toHaveBeenCalledTimes(2);
1570-
1571-
expect(fetchSpy.mock.calls[0][0]).toMatchObject({
1572-
path: '/client/sessions/session_1/tokens',
1573-
method: 'POST',
1574-
body: { organizationId: null },
1575-
});
1576-
1577-
expect(fetchSpy.mock.calls[1][0]).toMatchObject({
1578-
path: '/client/sessions/session_1/tokens',
1579-
method: 'POST',
1580-
body: { organizationId: null },
1581-
search: { expired_token: mockJwt },
1582-
});
1583-
});
1584-
1585-
it('should not retry with expired token when lastActiveToken is not available', async () => {
1586-
const session = new Session({
1587-
status: 'active',
1588-
id: 'session_1',
1589-
object: 'session',
1590-
user: createUser({}),
1591-
last_active_organization_id: null,
1592-
last_active_token: null,
1593-
actor: null,
1594-
created_at: new Date().getTime(),
1595-
updated_at: new Date().getTime(),
1596-
} as unknown as SessionJSON);
1597-
1598-
SessionTokenCache.clear();
1599-
1600-
const errorResponse = new ClerkAPIResponseError('Missing expired token', {
1601-
data: [
1602-
{ code: 'missing_expired_token', message: 'Missing expired token', long_message: 'Missing expired token' },
1603-
],
1604-
status: 422,
1605-
});
1606-
fetchSpy.mockRejectedValue(errorResponse);
1607-
1608-
await expect(session.getToken()).rejects.toMatchObject({
1609-
status: 422,
1610-
errors: [{ code: 'missing_expired_token' }],
1611-
});
1612-
1613-
expect(fetchSpy).toHaveBeenCalledTimes(1);
1614-
});
1615-
1616-
it('should not retry with expired token for non-422 errors', async () => {
1617-
const session = new Session({
1618-
status: 'active',
1619-
id: 'session_1',
1620-
object: 'session',
1621-
user: createUser({}),
1622-
last_active_organization_id: null,
1623-
last_active_token: { object: 'token', jwt: mockJwt },
1624-
actor: null,
1625-
created_at: new Date().getTime(),
1626-
updated_at: new Date().getTime(),
1627-
} as SessionJSON);
1628-
1629-
SessionTokenCache.clear();
1630-
1631-
const errorResponse = new ClerkAPIResponseError('Bad request', {
1632-
data: [{ code: 'bad_request', message: 'Bad request', long_message: 'Bad request' }],
1633-
status: 400,
1634-
});
1635-
fetchSpy.mockRejectedValueOnce(errorResponse);
1636-
1637-
await expect(session.getToken()).rejects.toThrow(ClerkAPIResponseError);
1638-
1639-
expect(fetchSpy).toHaveBeenCalledTimes(1);
1640-
});
1641-
1642-
it('should not retry with expired token when error code is different', async () => {
1643-
const session = new Session({
1644-
status: 'active',
1645-
id: 'session_1',
1646-
object: 'session',
1647-
user: createUser({}),
1648-
last_active_organization_id: null,
1649-
last_active_token: { object: 'token', jwt: mockJwt },
1650-
actor: null,
1651-
created_at: new Date().getTime(),
1652-
updated_at: new Date().getTime(),
1653-
} as unknown as SessionJSON);
1654-
1655-
SessionTokenCache.clear();
1656-
1657-
const errorResponse = new ClerkAPIResponseError('Validation failed', {
1658-
data: [{ code: 'validation_error', message: 'Validation failed', long_message: 'Validation failed' }],
1659-
status: 422,
1660-
});
1661-
fetchSpy.mockRejectedValue(errorResponse);
1662-
1663-
await expect(session.getToken()).rejects.toMatchObject({
1664-
status: 422,
1665-
errors: [{ code: 'validation_error' }],
1666-
});
1667-
1668-
expect(fetchSpy).toHaveBeenCalledTimes(1);
1669-
});
1670-
});
1671-
16721525
describe('agent', () => {
16731526
it('sets agent to null when actor is null', () => {
16741527
const session = new Session({

packages/shared/src/error.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ export { errorToJSON, parseError, parseErrors } from './errors/parseError';
33
export { ClerkAPIError, isClerkAPIError } from './errors/clerkApiError';
44
export { ClerkAPIResponseError, isClerkAPIResponseError } from './errors/clerkApiResponseError';
55
export { ClerkError, isClerkError } from './errors/clerkError';
6-
export { MissingExpiredTokenError } from './errors/missingExpiredTokenError';
76
export { ClerkOfflineError } from './errors/clerkOfflineError';
87

98
export { buildErrorThrower, type ErrorThrower, type ErrorThrowerOptions } from './errors/errorThrower';

packages/shared/src/errors/missingExpiredTokenError.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)