@@ -7,6 +7,10 @@ import { isBrowser, isNode } from "../util";
7
7
export class IsomorphicFetchHttpLibrary implements HttpLibrary {
8
8
public debug = false;
9
9
public zstdCompressorCallback: ZstdCompressorCallback | undefined;
10
+ public enableRetry!: boolean;
11
+ public maxRetries!: number ;
12
+ public backoffBase!: number ;
13
+ public backoffMultiplier!: number;
10
14
11
15
public send(request: RequestContext): Promise<ResponseContext > {
12
16
if (this.debug) {
@@ -46,60 +50,94 @@ export class IsomorphicFetchHttpLibrary implements HttpLibrary {
46
50
}
47
51
}
48
52
}
53
+
54
+ return this.executeRequest(request,0,headers);
55
+ }
49
56
50
- let resultPromise: Promise<ResponseContext >;
51
-
57
+ private async executeRequest(
58
+ request: RequestContext,
59
+ currentAttempt: number,
60
+ headers: {[key: string]: string}
61
+ ): Promise<ResponseContext > {
52
62
// On non-node environments, use native fetch if available.
53
- // `cross-fetch` incorrectly assumes all browsers have XHR available.
63
+ // `cross-fetch` incorrectly assumes all browsers have XHR available.
54
64
// See https://github.com/lquixada/cross-fetch/issues/78
55
65
// TODO: Remove once once above issue is resolved.
56
- if (!isNode && typeof fetch === "function") {
57
- resultPromise = fetch(request.getUrl(), {
58
- method: method,
59
- body: body as any,
60
- headers: headers,
61
- signal: request.getHttpConfig().signal,
62
- }).then((resp: any) => {
63
- const headers: { [name: string]: string } = {};
66
+ const fetchFunction =!isNode && typeof fetch === "function" ? fetch : crossFetch;
67
+ const fetchOptions = {
68
+ method: request.getHttpMethod().toString(),
69
+ body: request.getBody() as any,
70
+ headers: headers,
71
+ signal: request.getHttpConfig().signal,
72
+ }
73
+ try {
74
+ const resp = await fetchFunction(request.getUrl(),fetchOptions);
75
+ const responseHeaders: { [name: string]: string } = {};
64
76
resp.headers.forEach((value: string, name: string) => {
65
- headers [name] = value;
77
+ responseHeaders [name] = value;
66
78
});
67
79
68
- const body = {
69
- text: () => resp.text(),
70
- binary: () => resp.buffer(),
71
- };
72
- const response = new ResponseContext(resp.status, headers, body);
73
- if (this.debug) {
74
- this.logResponse(response);
75
- }
76
- return response;
77
- });
78
- } else {
79
- resultPromise = crossFetch(request.getUrl(), {
80
- method: method,
81
- body: body as any,
82
- headers: headers,
83
- signal: request.getHttpConfig().signal,
84
- }).then((resp: any) => {
85
- const headers: { [name: string]: string } = {};
86
- resp.headers.forEach((value: string, name: string) => {
87
- headers[name] = value;
88
- });
80
+ const responseBody = {
81
+ text: () => resp.text(),
82
+ binary: async () => {
83
+ const arrayBuffer = await resp.arrayBuffer();
84
+ return Buffer.from(arrayBuffer);
85
+ },
86
+ };
89
87
90
- const body = {
91
- text: () => resp.text(),
92
- binary: () => resp.buffer(),
93
- };
94
- const response = new ResponseContext(resp.status, headers, body);
95
- if (this.debug) {
96
- this.logResponse(response);
97
- }
98
- return response;
99
- });
88
+ const response = new ResponseContext(
89
+ resp.status,
90
+ responseHeaders,
91
+ responseBody
92
+ );
93
+
94
+ if (this.debug) {
95
+ this.logResponse(response);
96
+ }
97
+
98
+ if (
99
+ this.shouldRetry(
100
+ this.enableRetry,
101
+ currentAttempt,
102
+ this.maxRetries,
103
+ response.httpStatusCode
104
+ )
105
+ ) {
106
+ const delay = this.calculateRetryInterval(
107
+ currentAttempt,
108
+ this.backoffBase,
109
+ this.backoffMultiplier,
110
+ responseHeaders
111
+ );
112
+ currentAttempt++;
113
+ await this.sleep(delay * 1000);
114
+ return this.executeRequest(request, currentAttempt, headers);
115
+ }
116
+ return response;
117
+ } catch (error) {
118
+ console.error("An error occurred during the HTTP request:", error);
119
+ throw error;
100
120
}
121
+ }
122
+
123
+ private sleep(milliseconds: number): Promise<void > {
124
+ return new Promise((resolve) => {
125
+ setTimeout(resolve, milliseconds);
126
+ });
127
+ }
128
+
129
+ private shouldRetry(enableRetry:boolean, currentAttempt:number, maxRetries:number, responseCode:number):boolean{
130
+ return (responseCode == 429 || responseCode >=500 ) && maxRetries>currentAttempt && enableRetry
131
+ }
101
132
102
- return resultPromise;
133
+ private calculateRetryInterval(currentAttempt:number, backoffBase:number, backoffMultiplier:number, headers: {[name: string]: string}) : number{
134
+ if ("x-ratelimit-reset" in headers) {
135
+ const rateLimitHeaderString = headers["x-ratelimit-reset"]
136
+ const retryIntervalFromHeader = parseInt(rateLimitHeaderString,10);
137
+ return retryIntervalFromHeader
138
+ } else {
139
+ return (backoffMultiplier ** currentAttempt) * backoffBase
140
+ }
103
141
}
104
142
105
143
private logRequest(request: RequestContext): void {
0 commit comments