rekwest 4.1.0 → 4.2.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 +188 -187
- package/dist/ackn.js +1 -1
- package/dist/constants.js +11 -5
- package/dist/defaults.js +41 -0
- package/dist/index.js +27 -208
- package/dist/postflight.js +110 -0
- package/dist/preflight.js +66 -0
- package/dist/utils.js +120 -73
- package/package.json +1 -1
- package/src/ackn.mjs +1 -1
- package/src/constants.mjs +8 -2
- package/src/defaults.mjs +44 -0
- package/src/index.mjs +84 -313
- package/src/postflight.mjs +135 -0
- package/src/preflight.mjs +70 -0
- package/src/utils.mjs +143 -79
package/src/utils.mjs
CHANGED
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
import { Blob } from 'node:buffer';
|
|
2
|
+
import http from 'node:http';
|
|
2
3
|
import http2 from 'node:http2';
|
|
4
|
+
import https from 'node:https';
|
|
3
5
|
import {
|
|
4
6
|
pipeline,
|
|
5
7
|
Readable,
|
|
6
8
|
} from 'node:stream';
|
|
7
9
|
import { buffer } from 'node:stream/consumers';
|
|
10
|
+
import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
|
|
8
11
|
import { types } from 'node:util';
|
|
9
12
|
import zlib from 'node:zlib';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
+
import { ackn } from './ackn.mjs';
|
|
14
|
+
import {
|
|
15
|
+
requestCredentials,
|
|
16
|
+
requestRedirect,
|
|
17
|
+
} from './constants.mjs';
|
|
18
|
+
import {
|
|
19
|
+
RequestError,
|
|
20
|
+
TimeoutError,
|
|
21
|
+
} from './errors.mjs';
|
|
13
22
|
import { File } from './file.mjs';
|
|
14
23
|
import { FormData } from './formdata.mjs';
|
|
24
|
+
import rekwest from './index.mjs';
|
|
15
25
|
import {
|
|
16
26
|
APPLICATION_FORM_URLENCODED,
|
|
17
27
|
APPLICATION_JSON,
|
|
18
28
|
APPLICATION_OCTET_STREAM,
|
|
19
|
-
TEXT_PLAIN,
|
|
20
|
-
WILDCARD,
|
|
21
29
|
} from './mediatypes.mjs';
|
|
30
|
+
import { postflight } from './postflight.mjs';
|
|
31
|
+
import { preflight } from './preflight.mjs';
|
|
22
32
|
|
|
23
33
|
const {
|
|
24
|
-
HTTP2_HEADER_ACCEPT,
|
|
25
|
-
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
26
|
-
HTTP2_HEADER_AUTHORITY,
|
|
27
34
|
HTTP2_HEADER_CONTENT_ENCODING,
|
|
28
35
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
29
36
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
30
|
-
|
|
31
|
-
HTTP2_HEADER_METHOD,
|
|
32
|
-
HTTP2_HEADER_PATH,
|
|
33
|
-
HTTP2_HEADER_SCHEME,
|
|
37
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
34
38
|
HTTP2_HEADER_STATUS,
|
|
35
39
|
HTTP2_METHOD_GET,
|
|
36
40
|
HTTP2_METHOD_HEAD,
|
|
37
41
|
} = http2.constants;
|
|
38
42
|
|
|
39
|
-
const unwind = (encodings) => encodings.split(',').map((it) => it.trim());
|
|
40
|
-
|
|
41
43
|
export const admix = (res, headers, options) => {
|
|
42
44
|
const { h2 } = options;
|
|
43
45
|
|
|
@@ -138,6 +140,13 @@ export const dispatch = ({ body }, req) => {
|
|
|
138
140
|
}
|
|
139
141
|
};
|
|
140
142
|
|
|
143
|
+
export const maxRetryAfter = Symbol('maxRetryAfter');
|
|
144
|
+
|
|
145
|
+
export const maxRetryAfterError = (
|
|
146
|
+
interval,
|
|
147
|
+
options,
|
|
148
|
+
) => new RequestError(`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms.`, options);
|
|
149
|
+
|
|
141
150
|
export const merge = (target = {}, ...rest) => {
|
|
142
151
|
target = JSON.parse(JSON.stringify(target));
|
|
143
152
|
if (!rest.length) {
|
|
@@ -246,7 +255,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
246
255
|
if (/\bjson\b/i.test(contentType)) {
|
|
247
256
|
body = JSON.parse(body.toString(charset));
|
|
248
257
|
} else if (/\b(?:text|xml)\b/i.test(contentType)) {
|
|
249
|
-
if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b
|
|
258
|
+
if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b/i.test(charset)) {
|
|
250
259
|
body = body.toString(charset);
|
|
251
260
|
} else {
|
|
252
261
|
body = new TextDecoder(charset).decode(body);
|
|
@@ -267,72 +276,9 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
267
276
|
});
|
|
268
277
|
};
|
|
269
278
|
|
|
270
|
-
export const preflight = (options) => {
|
|
271
|
-
const { cookies, h2 = false, headers, method = HTTP2_METHOD_GET, redirected, url } = options;
|
|
272
|
-
|
|
273
|
-
if (h2) {
|
|
274
|
-
options.endStream = [
|
|
275
|
-
HTTP2_METHOD_GET,
|
|
276
|
-
HTTP2_METHOD_HEAD,
|
|
277
|
-
].includes(method);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (cookies !== false) {
|
|
281
|
-
let cookie = Cookies.jar.get(url.origin);
|
|
282
|
-
|
|
283
|
-
if (cookies === Object(cookies) && !redirected) {
|
|
284
|
-
if (cookie) {
|
|
285
|
-
new Cookies(cookies).forEach(function (val, key) {
|
|
286
|
-
this.set(key, val);
|
|
287
|
-
}, cookie);
|
|
288
|
-
} else {
|
|
289
|
-
cookie = new Cookies(cookies);
|
|
290
|
-
Cookies.jar.set(url.origin, cookie);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
options.headers = {
|
|
295
|
-
...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
|
|
296
|
-
...headers,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
options.digest ??= true;
|
|
301
|
-
options.follow ??= 20;
|
|
302
|
-
options.h2 ??= h2;
|
|
303
|
-
options.headers = {
|
|
304
|
-
[HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
|
|
305
|
-
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, deflate-raw, gzip, identity',
|
|
306
|
-
...Object.entries(options.headers ?? {})
|
|
307
|
-
.reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
308
|
-
...h2 && {
|
|
309
|
-
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
310
|
-
[HTTP2_HEADER_METHOD]: method,
|
|
311
|
-
[HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
|
|
312
|
-
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
|
|
313
|
-
},
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
options.method ??= method;
|
|
317
|
-
options.parse ??= true;
|
|
318
|
-
options.redirect ??= redirectModes.follow;
|
|
319
|
-
|
|
320
|
-
if (!Reflect.has(redirectModes, options.redirect)) {
|
|
321
|
-
options.createConnection?.().destroy();
|
|
322
|
-
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${
|
|
323
|
-
options.redirect
|
|
324
|
-
}' is not a valid enum value.`);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
options.redirected ??= false;
|
|
328
|
-
options.thenable ??= false;
|
|
329
|
-
|
|
330
|
-
return options;
|
|
331
|
-
};
|
|
332
|
-
|
|
333
279
|
export const sanitize = (url, options = {}) => {
|
|
334
280
|
if (options.trimTrailingSlashes) {
|
|
335
|
-
url = `${ url }`.replace(/(?<!:)\/+/
|
|
281
|
+
url = `${ url }`.replace(/(?<!:)\/+/g, '/');
|
|
336
282
|
}
|
|
337
283
|
|
|
338
284
|
url = new URL(url);
|
|
@@ -352,6 +298,99 @@ export async function* tap(value) {
|
|
|
352
298
|
}
|
|
353
299
|
}
|
|
354
300
|
|
|
301
|
+
export const transfer = async (options) => {
|
|
302
|
+
const { url } = options;
|
|
303
|
+
|
|
304
|
+
if (options.follow === 0) {
|
|
305
|
+
throw new RequestError(`Maximum redirect reached at: ${ url.href }`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (url.protocol === 'https:') {
|
|
309
|
+
options = await ackn(options);
|
|
310
|
+
} else if (Reflect.has(options, 'alpnProtocol')) {
|
|
311
|
+
[
|
|
312
|
+
'alpnProtocol',
|
|
313
|
+
'createConnection',
|
|
314
|
+
'h2',
|
|
315
|
+
'protocol',
|
|
316
|
+
].forEach((it) => Reflect.deleteProperty(options, it));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
options = await transform(preflight(options));
|
|
321
|
+
} catch (ex) {
|
|
322
|
+
options.createConnection?.().destroy();
|
|
323
|
+
throw ex;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const { digest, h2, redirected, thenable } = options;
|
|
327
|
+
const { request } = (url.protocol === 'http:' ? http : https);
|
|
328
|
+
|
|
329
|
+
const promise = new Promise((resolve, reject) => {
|
|
330
|
+
let client, req;
|
|
331
|
+
|
|
332
|
+
if (h2) {
|
|
333
|
+
client = http2.connect(url.origin, options);
|
|
334
|
+
req = client.request(options.headers, options);
|
|
335
|
+
} else {
|
|
336
|
+
req = request(url, options);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
affix(client, req, options);
|
|
340
|
+
|
|
341
|
+
req.once('error', reject);
|
|
342
|
+
req.once('frameError', reject);
|
|
343
|
+
req.once('goaway', reject);
|
|
344
|
+
req.once('response', (res) => postflight(req, res, options, {
|
|
345
|
+
reject,
|
|
346
|
+
resolve,
|
|
347
|
+
}));
|
|
348
|
+
|
|
349
|
+
dispatch(options, req);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const res = await promise;
|
|
354
|
+
|
|
355
|
+
if (digest && !redirected) {
|
|
356
|
+
res.body = await res.body();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return res;
|
|
360
|
+
} catch (ex) {
|
|
361
|
+
const { maxRetryAfter, retry } = options;
|
|
362
|
+
|
|
363
|
+
if (retry?.attempts && retry?.statusCodes.includes(ex.statusCode)) {
|
|
364
|
+
let { interval } = retry;
|
|
365
|
+
|
|
366
|
+
if (retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
|
|
367
|
+
interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
368
|
+
interval = Number(interval) * 1000 || new Date(interval) - Date.now();
|
|
369
|
+
if (interval > maxRetryAfter) {
|
|
370
|
+
throw maxRetryAfterError(interval, { cause: ex });
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
retry.attempts--;
|
|
377
|
+
retry.interval = interval;
|
|
378
|
+
|
|
379
|
+
return setTimeoutPromise(interval).then(() => rekwest(url, options));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (digest && !redirected && ex.body) {
|
|
383
|
+
ex.body = await ex.body();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!thenable) {
|
|
387
|
+
throw ex;
|
|
388
|
+
} else {
|
|
389
|
+
return ex;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
355
394
|
export const transform = async (options) => {
|
|
356
395
|
let { body, headers } = options;
|
|
357
396
|
|
|
@@ -408,3 +447,28 @@ export const transform = async (options) => {
|
|
|
408
447
|
body,
|
|
409
448
|
};
|
|
410
449
|
};
|
|
450
|
+
|
|
451
|
+
export const unwind = (encodings) => encodings.split(',').map((it) => it.trim());
|
|
452
|
+
|
|
453
|
+
export const validation = (options = {}) => {
|
|
454
|
+
if (options.body && [
|
|
455
|
+
HTTP2_METHOD_GET,
|
|
456
|
+
HTTP2_METHOD_HEAD,
|
|
457
|
+
].includes(options.method)) {
|
|
458
|
+
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body.`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!Object.values(requestCredentials).includes(options.credentials)) {
|
|
462
|
+
throw new TypeError(`Failed to read the 'credentials' property from 'options': The provided value '${
|
|
463
|
+
options.credentials
|
|
464
|
+
}' is not a valid enum value.`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!Reflect.has(requestRedirect, options.redirect)) {
|
|
468
|
+
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${
|
|
469
|
+
options.redirect
|
|
470
|
+
}' is not a valid enum value.`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return options;
|
|
474
|
+
};
|