Skip to content

Commit 0a7d03b

Browse files
committed
Add auto-retry for API calls
1 parent a57ec5d commit 0a7d03b

File tree

4 files changed

+76
-3
lines changed

4 files changed

+76
-3
lines changed

src/checkForUpdates.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getConfiguration } from './internals/getConfiguration';
77
import { getCurrentPackage } from './internals/getCurrentPackage';
88
import type { Configuration } from './internals/types';
99
import { log } from './internals/utils/log';
10-
import { requestFetchAdapter } from './internals/utils/request-fetch-adapter';
10+
import { requestFetchAdapter } from './internals/utils/requestFetchAdapter';
1111
import type { HandleBinaryVersionMismatchCallback, RemotePackage } from './types';
1212

1313
/**

src/internals/utils/fetchRetry.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
export async function fetchRetry(url: string, options: FetchRetryOptions = {}, attempt = 0): Promise<Response> {
2+
const {
3+
maxRetries = 3,
4+
initialBackoff = 1000,
5+
backoffMultiplier = 2,
6+
maxRetryDelay = 30000,
7+
...fetchOptions
8+
} = options;
9+
10+
try {
11+
const response = await fetch(url, fetchOptions);
12+
if (response.ok || !(response.status === 429 || response.status >= 500)) {
13+
return response;
14+
}
15+
16+
if (attempt >= maxRetries) {
17+
return response;
18+
}
19+
20+
const retryAfter = parseRetryAfterHeader(response);
21+
if (retryAfter && retryAfter > maxRetryDelay) {
22+
return response;
23+
}
24+
25+
const delay = retryAfter ?? addJitter(initialBackoff * Math.pow(backoffMultiplier, attempt));
26+
27+
await new Promise((resolve) => setTimeout(resolve, delay));
28+
29+
return fetchRetry(url, options, attempt + 1);
30+
} catch (error) {
31+
if (attempt >= maxRetries) {
32+
throw error;
33+
}
34+
35+
const delay = addJitter(initialBackoff * Math.pow(backoffMultiplier, attempt));
36+
37+
await new Promise((resolve) => setTimeout(resolve, delay));
38+
39+
return fetchRetry(url, options, attempt + 1);
40+
}
41+
}
42+
43+
interface FetchRetryOptions extends RequestInit {
44+
maxRetries?: number;
45+
initialBackoff?: number;
46+
backoffMultiplier?: number;
47+
maxRetryDelay?: number;
48+
}
49+
50+
function parseRetryAfterHeader(response: Response): number | null {
51+
const retryAfter = response.headers.get('Retry-After');
52+
if (!retryAfter) {
53+
return null;
54+
}
55+
56+
if (!isNaN(Number(retryAfter))) {
57+
return parseInt(retryAfter, 10) * 1000;
58+
}
59+
60+
try {
61+
const retryDate = new Date(retryAfter).getTime();
62+
const now = Date.now();
63+
return retryDate > now ? retryDate - now : 0;
64+
} catch (e) {
65+
return null;
66+
}
67+
}
68+
69+
function addJitter(delay: number): number {
70+
const jitterFactor = 0.5 + Math.random() * 0.5;
71+
return Math.floor(delay * jitterFactor);
72+
}

src/internals/utils/request-fetch-adapter.ts renamed to src/internals/utils/requestFetchAdapter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Http } from '../CodePushApiSdk.types';
22
import { version } from '../version';
3+
import { fetchRetry } from './fetchRetry';
34

45
export const requestFetchAdapter: Http.Requester = {
56
async request(method, url, requestBody) {
@@ -14,7 +15,7 @@ export const requestFetchAdapter: Http.Requester = {
1415
requestBody = JSON.stringify(requestBody);
1516
}
1617

17-
const response = await fetch(url, {
18+
const response = await fetchRetry(url, {
1819
method,
1920
headers,
2021
body: requestBody,

src/notifyAppReady.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CodePushApiSdk } from './internals/CodePushApiSdk';
33
import { NativeRNAppZungCodePushModule } from './internals/NativeRNAppZungCodePushModule';
44
import { getConfiguration } from './internals/getConfiguration';
55
import { log } from './internals/utils/log';
6-
import { requestFetchAdapter } from './internals/utils/request-fetch-adapter';
6+
import { requestFetchAdapter } from './internals/utils/requestFetchAdapter';
77
import type { StatusReport } from './types';
88

99
/**

0 commit comments

Comments
 (0)