Skip to content

Commit 63ee32b

Browse files
committed
feat: retry on 503, add Retry-After date (RFC1123) support
1 parent cfaa021 commit 63ee32b

File tree

4 files changed

+56
-4
lines changed

4 files changed

+56
-4
lines changed

packages/backend/src/api/__tests__/factory.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,25 @@ describe('api.client', () => {
169169
expect(errResponse.retryAfter).toBe(123);
170170
});
171171

172+
it('executes a failed backend API request and includes Retry-After header RFC1123 date', async () => {
173+
server.use(
174+
http.get(
175+
`https://api.clerk.test/v1/users/user_deadbeef`,
176+
validateHeaders(() => {
177+
return HttpResponse.json(
178+
{ errors: [] },
179+
{ status: 503, headers: { 'retry-after': new Date(new Date().getTime() + 60000).toUTCString() } },
180+
);
181+
}),
182+
),
183+
);
184+
185+
const errResponse = await apiClient.users.getUser('user_deadbeef').catch(err => err);
186+
187+
expect(errResponse.status).toBe(503);
188+
expect(errResponse.retryAfter).not.toBeNaN();
189+
});
190+
172191
it('executes a failed backend API request and ignores invalid Retry-After header', async () => {
173192
server.use(
174193
http.get(

packages/backend/src/api/request.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,17 @@ function getRetryAfter(headers?: Headers): number | undefined {
250250
}
251251

252252
const value = parseInt(retryAfter, 10);
253-
if (isNaN(value)) {
254-
return;
253+
if (!isNaN(value)) {
254+
return value;
255+
}
256+
257+
const date = new Date(retryAfter);
258+
if (!isNaN(date.getTime())) {
259+
const value = date.getTime() - Date.now();
260+
return value > 0 ? value : 0;
255261
}
256262

257-
return value;
263+
return;
258264
}
259265

260266
function parseErrors(data: unknown): ClerkAPIError[] {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,19 @@ export abstract class BaseResource {
146146
assertProductionKeysOnDev(status, errors);
147147

148148
const apiResponseOptions: ConstructorParameters<typeof ClerkAPIResponseError>[1] = { data: errors, status };
149-
if (status === 429 && headers) {
149+
if ((status === 429 || status == 503) && headers) {
150150
const retryAfter = headers.get('retry-after');
151151
if (retryAfter) {
152152
const value = parseInt(retryAfter, 10);
153153
if (!isNaN(value)) {
154154
apiResponseOptions.retryAfter = value;
155155
}
156+
157+
const date = new Date(retryAfter);
158+
if (!isNaN(date.getTime())) {
159+
const value = date.getTime() - Date.now();
160+
apiResponseOptions.retryAfter = value > 0 ? value : 0;
161+
}
156162
}
157163
}
158164

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ describe('BaseResource', () => {
3838
expect(errResponse.retryAfter).toBe(60);
3939
});
4040

41+
it('populates retryAfter on 503 error responses', async () => {
42+
BaseResource.clerk = {
43+
// @ts-expect-error - We're not about to mock the entire FapiClient
44+
getFapiClient: () => {
45+
return {
46+
request: vi.fn().mockResolvedValue({
47+
payload: {},
48+
status: 503,
49+
statusText: 'Service Unavailable',
50+
headers: new Headers({ 'Retry-After': new Date(new Date().getTime() + 60000).toUTCString() }),
51+
}),
52+
};
53+
},
54+
__internal_setCountry: vi.fn(),
55+
};
56+
const resource = new TestResource();
57+
const errResponse = await resource.fetch().catch(err => err);
58+
console.dir(errResponse);
59+
expect(errResponse.retryAfter).toBe(60);
60+
});
61+
4162
it('does not populate retryAfter on invalid header', async () => {
4263
BaseResource.clerk = {
4364
// @ts-expect-error - We're not about to mock the entire FapiClient

0 commit comments

Comments
 (0)