Skip to content

Commit 0dca1ae

Browse files
Add immediate retrying on a status 0 error (#29)
* Add immediate retrying on a status 0 error * Fix tests * Bump version
1 parent ce639e7 commit 0dca1ae

File tree

4 files changed

+40
-3
lines changed

4 files changed

+40
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "git",
66
"url": "https://github.com/PropelAuth/javascript"
77
},
8-
"version": "2.0.15",
8+
"version": "2.0.16",
99
"keywords": [
1010
"auth",
1111
"user",

src/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AuthenticationInfo, fetchAuthenticationInfo, logout } from "./api"
22
import { currentTimeSeconds, getLocalStorageNumber, hasLocalStorage, hasWindow } from "./helpers"
3+
import {runWithRetriesOnAnyError} from "./fetch_retries";
34

45
const LOGGED_IN_AT_KEY = "__PROPEL_AUTH_LOGGED_IN_AT"
56
const LOGGED_OUT_AT_KEY = "__PROPEL_AUTH_LOGGED_OUT_AT"
@@ -271,7 +272,7 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
271272
async function forceRefreshToken(returnCached: boolean): Promise<AuthenticationInfo | null> {
272273
try {
273274
// Happy case, we fetch auth info and save it
274-
const authenticationInfo = await fetchAuthenticationInfo(clientState.authUrl)
275+
const authenticationInfo = await runWithRetriesOnAnyError(() => fetchAuthenticationInfo(clientState.authUrl))
275276
setAuthenticationInfoAndUpdateDownstream(authenticationInfo)
276277
return authenticationInfo
277278
} catch (e) {

src/fetch_retries.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export const DEFAULT_RETRIES = 3
2+
3+
export const runWithRetriesOnAnyError = async <T>(fn: () => Promise<T>): Promise<T> => {
4+
return runWithRetriesInner(fn, DEFAULT_RETRIES)
5+
}
6+
7+
const runWithRetriesInner = async <T>(fn: () => Promise<T>, numRetriesLeft: number): Promise<T> => {
8+
try {
9+
return await fn()
10+
} catch (e) {
11+
if (numRetriesLeft <= 0) {
12+
throw e
13+
}
14+
await delay(numRetriesLeftToDelay(numRetriesLeft))
15+
return runWithRetriesInner(fn, numRetriesLeft - 1)
16+
}
17+
}
18+
19+
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
20+
21+
const numRetriesLeftToDelay = (numRetriesLeft: number) => {
22+
// We could be fancy, but we only retry 3 times so...
23+
if (numRetriesLeft >= 3) {
24+
return 100
25+
} else if (numRetriesLeft === 2) {
26+
return 200
27+
} else {
28+
return 300
29+
}
30+
}

src/tests/index.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
import { createClient } from "../index"
55
import { OrgIdToOrgMemberInfo } from "../org"
66
import { ok, ResponseStatus, setupMockFetch, UnauthorizedResponse, UnknownErrorResponse } from "./mockfetch.test"
7+
import {DEFAULT_RETRIES} from "../fetch_retries";
78

89
const INITIAL_TIME_MILLIS = 1619743452595
910
const INITIAL_TIME_SECONDS = INITIAL_TIME_MILLIS / 1000
1011

1112
beforeAll(() => {
1213
jest.useFakeTimers("modern")
14+
// @ts-ignore
15+
global.setTimeout = jest.fn(cb => cb());
1316
})
1417

1518
beforeEach(() => {
@@ -276,8 +279,10 @@ test("client continues to use cached value if the API fails and the value hasn't
276279
// The API will now fail, but that failure should be logged and not effect this method
277280
const { mockFetch: errorMockFetch } = setupMockFetchThatReturnsUnknownError()
278281
const newAuthenticationInfo = await client.getAuthenticationInfoOrNull()
282+
279283
expect(newAuthenticationInfo?.accessToken).toBe(expectedAccessToken)
280-
expectCorrectEndpointWasHit(errorMockFetch, "https://www.example.com/api/v1/refresh_token")
284+
const numTimesCalled = 1 + DEFAULT_RETRIES
285+
expectCorrectEndpointWasHit(errorMockFetch, "https://www.example.com/api/v1/refresh_token", numTimesCalled)
281286
})
282287

283288
test("client cannot use cached value if the API fails and the value has expired", async () => {
@@ -410,6 +415,7 @@ test("if a new client is created and cannot get an access token, it should trigg
410415

411416
const post401AuthenticationInfo1 = await client1.getAuthenticationInfoOrNull()
412417
expect(post401AuthenticationInfo1).toBeNull()
418+
jest.useRealTimers()
413419
const post401AuthenticationInfo0 = await client0.getAuthenticationInfoOrNull()
414420
expect(post401AuthenticationInfo0).toBeNull()
415421

0 commit comments

Comments
 (0)