@42paris/intraoapi42 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +127 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/retry-fetch.d.ts +7 -0
- package/dist/retry-fetch.d.ts.map +1 -0
- package/dist/retry-fetch.js +56 -0
- package/dist/retry-fetch.js.map +1 -0
- package/dist/types.d.ts +3109 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# intraoapi42
|
|
2
|
+
|
|
3
|
+
Typed TypeScript client for the [42 Intra API](https://api.intra.42.fr/), built on [`openapi-fetch`](https://openapi-ts.dev/packages/openapi-fetch/).
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
|
|
7
|
+
- Fully typed requests and responses generated from the OpenAPI spec.
|
|
8
|
+
- Automatic OAuth2 client credentials flow with token refresh.
|
|
9
|
+
- Built‑in retry logic for transient failures.
|
|
10
|
+
- Ready‑made configs for production and staging environments.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install intraoapi42
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or with a scoped name if you publish as such:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @your-username/intraoapi42
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Node.js with ESM support (your `package.json` should have `"type": "module"` or use `.mjs` extensions).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
createApiClient,
|
|
35
|
+
ProductionConfig,
|
|
36
|
+
withClientCredentials,
|
|
37
|
+
withScopes,
|
|
38
|
+
} from "intraoapi42";
|
|
39
|
+
|
|
40
|
+
// Configure OAuth2 client credentials
|
|
41
|
+
const config = withClientCredentials(
|
|
42
|
+
ProductionConfig,
|
|
43
|
+
"YOUR_CLIENT_ID",
|
|
44
|
+
"YOUR_CLIENT_SECRET",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Optionally restrict scopes
|
|
48
|
+
const scopedConfig = withScopes(
|
|
49
|
+
config,
|
|
50
|
+
"public",
|
|
51
|
+
"projects",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Create the API client
|
|
55
|
+
const api = createApiClient(scopedConfig);
|
|
56
|
+
|
|
57
|
+
// Example: GET /v2/users/:id
|
|
58
|
+
const { data, error } = await api.GET("/users/{id}", {
|
|
59
|
+
params: {
|
|
60
|
+
path: { id: 12345 },
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (error) {
|
|
65
|
+
throw new Error(`API error: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(data);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Types for paths, parameters, and responses are inferred from the OpenAPI spec, so you get full TypeScript autocomplete and type checking.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
### Environments
|
|
78
|
+
|
|
79
|
+
Two built‑in configs are provided:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { ProductionConfig, StagingConfig } from "intraoapi42";
|
|
83
|
+
|
|
84
|
+
ProductionConfig;
|
|
85
|
+
// {
|
|
86
|
+
// tokenUrl: "https://api.intra.42.fr/oauth/token",
|
|
87
|
+
// serverUrl: "https://api.intra.42.fr/v2",
|
|
88
|
+
// }
|
|
89
|
+
|
|
90
|
+
StagingConfig;
|
|
91
|
+
// {
|
|
92
|
+
// tokenUrl: "https://api.intra-staging.42.fr/oauth/token",
|
|
93
|
+
// serverUrl: "https://api.intra-staging.42.fr/v2",
|
|
94
|
+
// }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Adding credentials
|
|
98
|
+
|
|
99
|
+
Use `withClientCredentials` to attach your OAuth2 client ID and secret:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import {
|
|
103
|
+
ProductionConfig,
|
|
104
|
+
withClientCredentials,
|
|
105
|
+
createApiClient,
|
|
106
|
+
} from "intraoapi42";
|
|
107
|
+
|
|
108
|
+
const config = withClientCredentials(
|
|
109
|
+
ProductionConfig,
|
|
110
|
+
process.env.INTRA_CLIENT_ID!,
|
|
111
|
+
process.env.INTRA_CLIENT_SECRET!,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const api = createApiClient(config);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Adding scopes
|
|
118
|
+
|
|
119
|
+
Optionally restrict the token’s scopes:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { withScopes } from "intraoapi42";
|
|
123
|
+
|
|
124
|
+
const config = withScopes(
|
|
125
|
+
ProductionConfig,
|
|
126
|
+
"public",
|
|
127
|
+
"projects",
|
|
128
|
+
"activities",
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Scopes are passed to the token endpoint as a space‑separated string.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## How authentication works
|
|
137
|
+
|
|
138
|
+
`createApiClient` sets up:
|
|
139
|
+
|
|
140
|
+
- A **refreshable token source** that:
|
|
141
|
+
- Requests a new access token using client credentials when needed.
|
|
142
|
+
- Caches the token until close to expiry (with a 60s safety margin).
|
|
143
|
+
- Deduplicates concurrent token requests.
|
|
144
|
+
- An **auth middleware** that:
|
|
145
|
+
- Adds `Authorization: Bearer <token>` to every request.
|
|
146
|
+
- On `401 Unauthorized`, invalidates the cached token, fetches a fresh one, and retries the request once.
|
|
147
|
+
|
|
148
|
+
You don’t need to manage tokens manually; just use the client.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Usage patterns
|
|
153
|
+
|
|
154
|
+
### List users with pagination
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const { data, error } = await api.GET("/users", {
|
|
158
|
+
params: {
|
|
159
|
+
query: {
|
|
160
|
+
page_size: 50,
|
|
161
|
+
page: 1,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (error) {
|
|
167
|
+
throw new Error(error.message);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// data is typed according to the OpenAPI spec
|
|
171
|
+
console.log(data);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### POST / PATCH / DELETE
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// Example: update a user
|
|
178
|
+
const { data, error } = await api.PATCH("/users/{id}", {
|
|
179
|
+
params: {
|
|
180
|
+
path: { id: 12345 },
|
|
181
|
+
},
|
|
182
|
+
body: {
|
|
183
|
+
// typed fields here
|
|
184
|
+
displayname: "New Name",
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
All HTTP methods (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, etc.) are available with full typing.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Error handling
|
|
194
|
+
|
|
195
|
+
Each call returns `{ data, error }`:
|
|
196
|
+
|
|
197
|
+
- `data` is defined when the request succeeds.
|
|
198
|
+
- `error` is defined when the request fails (network error, non‑2xx response, etc.).
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const { data, error } = await api.GET("/users/{id}", {
|
|
202
|
+
params: { path: { id: 12345 } },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (error) {
|
|
206
|
+
// error has shape: { message, body?, response, ... }
|
|
207
|
+
console.error("Status:", error.response.status);
|
|
208
|
+
console.error("Body:", await error.response.clone().text());
|
|
209
|
+
throw new Error("Failed to fetch user");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Use data safely
|
|
213
|
+
console.log(data.id, data.login);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Refer to `openapi-fetch` docs for the exact error shape and advanced patterns.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## TypeScript usage
|
|
221
|
+
|
|
222
|
+
The package exports types generated from the OpenAPI spec:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import type { paths } from "intraoapi42";
|
|
226
|
+
|
|
227
|
+
// paths describes all available endpoints and their shapes
|
|
228
|
+
type UsersEndpoint = paths["/users"];
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Your IDE will infer types automatically from `api.GET`, `api.POST`, etc., so you usually don’t need to import these manually.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Development / Contributing
|
|
236
|
+
|
|
237
|
+
If you’re working on the client itself:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
cd clients/typescript
|
|
241
|
+
|
|
242
|
+
# Install dependencies
|
|
243
|
+
npm install
|
|
244
|
+
|
|
245
|
+
# Generate types from OpenAPI spec
|
|
246
|
+
npm run generate:api
|
|
247
|
+
|
|
248
|
+
# Typecheck
|
|
249
|
+
npm run typecheck
|
|
250
|
+
|
|
251
|
+
# Build
|
|
252
|
+
npm run build
|
|
253
|
+
|
|
254
|
+
# Run tests
|
|
255
|
+
npm test
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { paths } from "./types.js";
|
|
2
|
+
export type Config = {
|
|
3
|
+
tokenUrl: string;
|
|
4
|
+
serverUrl: string;
|
|
5
|
+
clientId?: string;
|
|
6
|
+
clientSecret?: string;
|
|
7
|
+
scopes?: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare const ProductionConfig: Config;
|
|
10
|
+
export declare const StagingConfig: Config;
|
|
11
|
+
export declare function withClientCredentials(config: Config, clientId: string, clientSecret: string): Config;
|
|
12
|
+
export declare function withScopes(config: Config, ...scopes: string[]): Config;
|
|
13
|
+
export declare function createApiClient(config: Config): import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
14
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAUxC,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,MAG9B,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAG3B,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,MAAM,CAMR;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAKtE;AAwGD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,gEAsC7C"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import createClient, {} from "openapi-fetch";
|
|
2
|
+
import { retryFetch } from "./retry-fetch.js";
|
|
3
|
+
export const ProductionConfig = {
|
|
4
|
+
tokenUrl: "https://api.intra.42.fr/oauth/token",
|
|
5
|
+
serverUrl: "https://api.intra.42.fr/v2",
|
|
6
|
+
};
|
|
7
|
+
export const StagingConfig = {
|
|
8
|
+
tokenUrl: "https://api.intra-staging.42.fr/oauth/token",
|
|
9
|
+
serverUrl: "https://api.intra-staging.42.fr/v2",
|
|
10
|
+
};
|
|
11
|
+
export function withClientCredentials(config, clientId, clientSecret) {
|
|
12
|
+
return {
|
|
13
|
+
...config,
|
|
14
|
+
clientId,
|
|
15
|
+
clientSecret,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function withScopes(config, ...scopes) {
|
|
19
|
+
return {
|
|
20
|
+
...config,
|
|
21
|
+
scopes,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
class RefreshableTokenSource {
|
|
25
|
+
config;
|
|
26
|
+
accessToken;
|
|
27
|
+
expiresAt = 0;
|
|
28
|
+
pendingRequest;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
}
|
|
32
|
+
async token() {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
if (this.accessToken !== undefined &&
|
|
35
|
+
now < this.expiresAt - 60_000) {
|
|
36
|
+
return this.accessToken;
|
|
37
|
+
}
|
|
38
|
+
if (this.pendingRequest !== undefined) {
|
|
39
|
+
return this.pendingRequest;
|
|
40
|
+
}
|
|
41
|
+
const request = this.fetchToken();
|
|
42
|
+
this.pendingRequest = request;
|
|
43
|
+
try {
|
|
44
|
+
return await request;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
if (this.pendingRequest === request) {
|
|
48
|
+
this.pendingRequest = undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
invalidate() {
|
|
53
|
+
this.accessToken = undefined;
|
|
54
|
+
this.expiresAt = 0;
|
|
55
|
+
}
|
|
56
|
+
async fetchToken() {
|
|
57
|
+
const { clientId, clientSecret } = this.config;
|
|
58
|
+
if (clientId === undefined || clientSecret === undefined) {
|
|
59
|
+
throw new Error("OAuth client credentials are missing");
|
|
60
|
+
}
|
|
61
|
+
const params = new URLSearchParams({
|
|
62
|
+
client_id: clientId,
|
|
63
|
+
client_secret: clientSecret,
|
|
64
|
+
grant_type: "client_credentials",
|
|
65
|
+
});
|
|
66
|
+
if (this.config.scopes !== undefined && this.config.scopes.length > 0) {
|
|
67
|
+
params.set("scope", this.config.scopes.join(" "));
|
|
68
|
+
}
|
|
69
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
Accept: "application/json",
|
|
73
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
74
|
+
},
|
|
75
|
+
body: params,
|
|
76
|
+
});
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new Error(`OAuth token request failed: ${response.status} ${await response.text()}`);
|
|
79
|
+
}
|
|
80
|
+
const payload = await response.json();
|
|
81
|
+
if (!isOAuthTokenResponse(payload)) {
|
|
82
|
+
throw new Error("OAuth token response did not contain a valid access_token");
|
|
83
|
+
}
|
|
84
|
+
this.accessToken = payload.access_token;
|
|
85
|
+
this.expiresAt =
|
|
86
|
+
Date.now() + (payload.expires_in ?? 3_600) * 1_000;
|
|
87
|
+
return payload.access_token;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isOAuthTokenResponse(value) {
|
|
91
|
+
if (typeof value !== "object" || value === null) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return ("access_token" in value &&
|
|
95
|
+
typeof value.access_token === "string" &&
|
|
96
|
+
value.access_token.length > 0 &&
|
|
97
|
+
"token_type" in value &&
|
|
98
|
+
typeof value.token_type === "string");
|
|
99
|
+
}
|
|
100
|
+
export function createApiClient(config) {
|
|
101
|
+
const tokenSource = new RefreshableTokenSource(config);
|
|
102
|
+
const client = createClient({
|
|
103
|
+
baseUrl: config.serverUrl,
|
|
104
|
+
fetch: retryFetch,
|
|
105
|
+
});
|
|
106
|
+
const authMiddleware = {
|
|
107
|
+
async onRequest({ request }) {
|
|
108
|
+
const token = await tokenSource.token();
|
|
109
|
+
request.headers.set("Authorization", `Bearer ${token}`);
|
|
110
|
+
return request;
|
|
111
|
+
},
|
|
112
|
+
async onResponse({ request, response }) {
|
|
113
|
+
if (response.status !== 401) {
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
await response.body?.cancel();
|
|
117
|
+
tokenSource.invalidate();
|
|
118
|
+
const retryRequest = request.clone();
|
|
119
|
+
const token = await tokenSource.token();
|
|
120
|
+
retryRequest.headers.set("Authorization", `Bearer ${token}`);
|
|
121
|
+
return retryFetch(retryRequest);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
client.use(authMiddleware);
|
|
125
|
+
return client;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-fetch.d.ts","sourceRoot":"","sources":["../src/retry-fetch.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAYF,wBAAsB,UAAU,CAC9B,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,QAAQ,CAAC,CAuCnB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const RETRYABLE_STATUS_CODES = new Set([
|
|
2
|
+
408,
|
|
3
|
+
425,
|
|
4
|
+
429,
|
|
5
|
+
500,
|
|
6
|
+
502,
|
|
7
|
+
503,
|
|
8
|
+
504,
|
|
9
|
+
]);
|
|
10
|
+
export async function retryFetch(input, init) {
|
|
11
|
+
const maxAttempts = 3;
|
|
12
|
+
const baseDelayMs = 250;
|
|
13
|
+
const maxDelayMs = 5_000;
|
|
14
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(input, init);
|
|
17
|
+
const shouldRetry = RETRYABLE_STATUS_CODES.has(response.status) &&
|
|
18
|
+
attempt < maxAttempts - 1;
|
|
19
|
+
if (!shouldRetry) {
|
|
20
|
+
return response;
|
|
21
|
+
}
|
|
22
|
+
const retryAfterMs = getRetryAfterMs(response);
|
|
23
|
+
const exponentialDelay = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
|
|
24
|
+
await delay(retryAfterMs ?? exponentialDelay);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (attempt >= maxAttempts - 1) {
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const exponentialDelay = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
|
|
31
|
+
await delay(exponentialDelay);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw new Error("Retry loop terminated unexpectedly");
|
|
35
|
+
}
|
|
36
|
+
function getRetryAfterMs(response) {
|
|
37
|
+
const value = response.headers.get("retry-after");
|
|
38
|
+
if (value === null) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const seconds = Number(value);
|
|
42
|
+
if (Number.isFinite(seconds)) {
|
|
43
|
+
return Math.max(0, seconds * 1_000);
|
|
44
|
+
}
|
|
45
|
+
const date = Date.parse(value);
|
|
46
|
+
if (Number.isNaN(date)) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return Math.max(0, date - Date.now());
|
|
50
|
+
}
|
|
51
|
+
function delay(milliseconds) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
setTimeout(resolve, milliseconds);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=retry-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-fetch.js","sourceRoot":"","sources":["../src/retry-fetch.ts"],"names":[],"mappings":"AAMA,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;CACJ,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAwB,EACxB,IAAkB;IAElB,MAAM,WAAW,GAAG,CAAC,CAAC;IACtB,MAAM,WAAW,GAAG,GAAG,CAAC;IACxB,MAAM,UAAU,GAAG,KAAK,CAAC;IAEzB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC1D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAE1C,MAAM,WAAW,GACf,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC3C,OAAO,GAAG,WAAW,GAAG,CAAC,CAAC;YAE5B,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,MAAM,YAAY,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;YAC/C,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAC/B,UAAU,EACV,WAAW,GAAG,CAAC,IAAI,OAAO,CAC3B,CAAC;YAEF,MAAM,KAAK,CAAC,YAAY,IAAI,gBAAgB,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;gBAC/B,MAAM,KAAK,CAAC;YACd,CAAC;YAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAC/B,UAAU,EACV,WAAW,GAAG,CAAC,IAAI,OAAO,CAC3B,CAAC;YAEF,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,eAAe,CAAC,QAAkB;IACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAElD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE9B,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAE/B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,KAAK,CAAC,YAAoB;IACjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC"}
|