rekwest 4.0.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 +4 -3
- package/dist/ackn.js +1 -1
- package/dist/constants.js +27 -0
- package/dist/cookies.js +1 -1
- package/dist/defaults.js +41 -0
- package/dist/formdata.js +12 -12
- package/dist/index.js +27 -185
- package/dist/postflight.js +110 -0
- package/dist/preflight.js +66 -0
- package/dist/utils.js +138 -95
- package/package.json +3 -3
- package/src/ackn.mjs +1 -1
- package/src/constants.mjs +29 -0
- package/src/cookies.mjs +2 -2
- package/src/defaults.mjs +44 -0
- package/src/formdata.mjs +16 -13
- package/src/index.mjs +84 -282
- package/src/postflight.mjs +135 -0
- package/src/preflight.mjs +70 -0
- package/src/utils.mjs +159 -99
package/src/utils.mjs
CHANGED
|
@@ -1,42 +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 {
|
|
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';
|
|
12
22
|
import { File } from './file.mjs';
|
|
13
23
|
import { FormData } from './formdata.mjs';
|
|
24
|
+
import rekwest from './index.mjs';
|
|
14
25
|
import {
|
|
15
26
|
APPLICATION_FORM_URLENCODED,
|
|
16
27
|
APPLICATION_JSON,
|
|
17
28
|
APPLICATION_OCTET_STREAM,
|
|
18
|
-
TEXT_PLAIN,
|
|
19
|
-
WILDCARD,
|
|
20
29
|
} from './mediatypes.mjs';
|
|
30
|
+
import { postflight } from './postflight.mjs';
|
|
31
|
+
import { preflight } from './preflight.mjs';
|
|
21
32
|
|
|
22
33
|
const {
|
|
23
|
-
HTTP2_HEADER_ACCEPT,
|
|
24
|
-
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
25
|
-
HTTP2_HEADER_AUTHORITY,
|
|
26
34
|
HTTP2_HEADER_CONTENT_ENCODING,
|
|
27
35
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
28
36
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
29
|
-
|
|
30
|
-
HTTP2_HEADER_METHOD,
|
|
31
|
-
HTTP2_HEADER_PATH,
|
|
32
|
-
HTTP2_HEADER_SCHEME,
|
|
37
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
33
38
|
HTTP2_HEADER_STATUS,
|
|
34
39
|
HTTP2_METHOD_GET,
|
|
35
40
|
HTTP2_METHOD_HEAD,
|
|
36
41
|
} = http2.constants;
|
|
37
42
|
|
|
38
|
-
const unwind = (encodings) => encodings.split(',').map((it) => it.trim());
|
|
39
|
-
|
|
40
43
|
export const admix = (res, headers, options) => {
|
|
41
44
|
const { h2 } = options;
|
|
42
45
|
|
|
@@ -79,8 +82,8 @@ export const affix = (client, req, options) => {
|
|
|
79
82
|
});
|
|
80
83
|
};
|
|
81
84
|
|
|
82
|
-
export const
|
|
83
|
-
if (
|
|
85
|
+
export const brandCheck = (value, ctor) => {
|
|
86
|
+
if (!(value instanceof ctor)) {
|
|
84
87
|
throw new TypeError('Illegal invocation');
|
|
85
88
|
}
|
|
86
89
|
};
|
|
@@ -137,6 +140,13 @@ export const dispatch = ({ body }, req) => {
|
|
|
137
140
|
}
|
|
138
141
|
};
|
|
139
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
|
+
|
|
140
150
|
export const merge = (target = {}, ...rest) => {
|
|
141
151
|
target = JSON.parse(JSON.stringify(target));
|
|
142
152
|
if (!rest.length) {
|
|
@@ -174,7 +184,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
174
184
|
arrayBuffer: {
|
|
175
185
|
enumerable: true,
|
|
176
186
|
value: async function () {
|
|
177
|
-
|
|
187
|
+
brandCheck(this, res?.constructor);
|
|
178
188
|
parse &&= false;
|
|
179
189
|
const { buffer, byteLength, byteOffset } = await this.body();
|
|
180
190
|
|
|
@@ -184,7 +194,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
184
194
|
blob: {
|
|
185
195
|
enumerable: true,
|
|
186
196
|
value: async function () {
|
|
187
|
-
|
|
197
|
+
brandCheck(this, res?.constructor);
|
|
188
198
|
const val = await this.arrayBuffer();
|
|
189
199
|
|
|
190
200
|
return new Blob([val]);
|
|
@@ -193,7 +203,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
193
203
|
json: {
|
|
194
204
|
enumerable: true,
|
|
195
205
|
value: async function () {
|
|
196
|
-
|
|
206
|
+
brandCheck(this, res?.constructor);
|
|
197
207
|
const val = await this.text();
|
|
198
208
|
|
|
199
209
|
return JSON.parse(val);
|
|
@@ -202,7 +212,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
202
212
|
text: {
|
|
203
213
|
enumerable: true,
|
|
204
214
|
value: async function () {
|
|
205
|
-
|
|
215
|
+
brandCheck(this, res?.constructor);
|
|
206
216
|
const blob = await this.blob();
|
|
207
217
|
|
|
208
218
|
return blob.text();
|
|
@@ -215,7 +225,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
215
225
|
body: {
|
|
216
226
|
enumerable: true,
|
|
217
227
|
value: async function () {
|
|
218
|
-
|
|
228
|
+
brandCheck(this, res?.constructor);
|
|
219
229
|
|
|
220
230
|
if (this.bodyUsed) {
|
|
221
231
|
throw new TypeError('Response stream already read');
|
|
@@ -245,7 +255,7 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
245
255
|
if (/\bjson\b/i.test(contentType)) {
|
|
246
256
|
body = JSON.parse(body.toString(charset));
|
|
247
257
|
} else if (/\b(?:text|xml)\b/i.test(contentType)) {
|
|
248
|
-
if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b
|
|
258
|
+
if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b/i.test(charset)) {
|
|
249
259
|
body = body.toString(charset);
|
|
250
260
|
} else {
|
|
251
261
|
body = new TextDecoder(charset).decode(body);
|
|
@@ -266,78 +276,9 @@ export const mixin = (res, { digest = false, parse = false } = {}) => {
|
|
|
266
276
|
});
|
|
267
277
|
};
|
|
268
278
|
|
|
269
|
-
export const preflight = (options) => {
|
|
270
|
-
const { cookies, h2 = false, headers, method = HTTP2_METHOD_GET, redirected, url } = options;
|
|
271
|
-
|
|
272
|
-
if (h2) {
|
|
273
|
-
options.endStream = [
|
|
274
|
-
HTTP2_METHOD_GET,
|
|
275
|
-
HTTP2_METHOD_HEAD,
|
|
276
|
-
].includes(method);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (cookies !== false) {
|
|
280
|
-
let cookie = Cookies.jar.get(url.origin);
|
|
281
|
-
|
|
282
|
-
if (cookies === Object(cookies) && !redirected) {
|
|
283
|
-
if (cookie) {
|
|
284
|
-
new Cookies(cookies).forEach(function (val, key) {
|
|
285
|
-
this.set(key, val);
|
|
286
|
-
}, cookie);
|
|
287
|
-
} else {
|
|
288
|
-
cookie = new Cookies(cookies);
|
|
289
|
-
Cookies.jar.set(url.origin, cookie);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
options.headers = {
|
|
294
|
-
...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
|
|
295
|
-
...headers,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
options.digest ??= true;
|
|
300
|
-
options.follow ??= 20;
|
|
301
|
-
options.h2 ??= h2;
|
|
302
|
-
options.headers = {
|
|
303
|
-
[HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
|
|
304
|
-
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, deflate-raw, gzip, identity',
|
|
305
|
-
...Object.entries(options.headers ?? {})
|
|
306
|
-
.reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
307
|
-
...h2 && {
|
|
308
|
-
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
309
|
-
[HTTP2_HEADER_METHOD]: method,
|
|
310
|
-
[HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
|
|
311
|
-
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
|
|
312
|
-
},
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
options.method ??= method;
|
|
316
|
-
options.parse ??= true;
|
|
317
|
-
options.redirect ??= redirects.follow;
|
|
318
|
-
|
|
319
|
-
if (!Object.values(redirects).includes(options.redirect)) {
|
|
320
|
-
options.createConnection?.().destroy();
|
|
321
|
-
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${
|
|
322
|
-
options.redirect
|
|
323
|
-
}' is not a valid enum value.`);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
options.redirected ??= false;
|
|
327
|
-
options.thenable ??= false;
|
|
328
|
-
|
|
329
|
-
return options;
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
export const redirects = {
|
|
333
|
-
error: 'error',
|
|
334
|
-
follow: 'follow',
|
|
335
|
-
manual: 'manual',
|
|
336
|
-
};
|
|
337
|
-
|
|
338
279
|
export const sanitize = (url, options = {}) => {
|
|
339
280
|
if (options.trimTrailingSlashes) {
|
|
340
|
-
url = `${ url }`.replace(/(?<!:)\/+/
|
|
281
|
+
url = `${ url }`.replace(/(?<!:)\/+/g, '/');
|
|
341
282
|
}
|
|
342
283
|
|
|
343
284
|
url = new URL(url);
|
|
@@ -345,6 +286,8 @@ export const sanitize = (url, options = {}) => {
|
|
|
345
286
|
return Object.assign(options, { url });
|
|
346
287
|
};
|
|
347
288
|
|
|
289
|
+
export const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;
|
|
290
|
+
|
|
348
291
|
export async function* tap(value) {
|
|
349
292
|
if (Reflect.has(value, Symbol.asyncIterator)) {
|
|
350
293
|
yield* value;
|
|
@@ -355,6 +298,99 @@ export async function* tap(value) {
|
|
|
355
298
|
}
|
|
356
299
|
}
|
|
357
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
|
+
|
|
358
394
|
export const transform = async (options) => {
|
|
359
395
|
let { body, headers } = options;
|
|
360
396
|
|
|
@@ -389,19 +425,18 @@ export const transform = async (options) => {
|
|
|
389
425
|
|
|
390
426
|
const encodings = options.headers[HTTP2_HEADER_CONTENT_ENCODING];
|
|
391
427
|
|
|
392
|
-
if (
|
|
393
|
-
if (Reflect.has(body, Symbol.asyncIterator)) {
|
|
394
|
-
body = compress(Readable.from(body), encodings);
|
|
395
|
-
} else {
|
|
396
|
-
body = await buffer(compress(Readable.from(body), encodings));
|
|
397
|
-
}
|
|
398
|
-
} else if (body === Object(body)
|
|
428
|
+
if (body === Object(body)
|
|
399
429
|
&& (Reflect.has(body, Symbol.asyncIterator) || (!Array.isArray(body) && Reflect.has(body, Symbol.iterator)))) {
|
|
400
|
-
body = Readable.from(body);
|
|
430
|
+
body = encodings ? compress(Readable.from(body), encodings) : Readable.from(body);
|
|
431
|
+
} else if (encodings) {
|
|
432
|
+
body = await buffer(compress(Readable.from(body), encodings));
|
|
401
433
|
}
|
|
402
434
|
|
|
403
435
|
Object.assign(options.headers, {
|
|
404
436
|
...headers,
|
|
437
|
+
...!body[Symbol.asyncIterator] && {
|
|
438
|
+
[HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body),
|
|
439
|
+
},
|
|
405
440
|
...options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
|
|
406
441
|
[HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE],
|
|
407
442
|
},
|
|
@@ -412,3 +447,28 @@ export const transform = async (options) => {
|
|
|
412
447
|
body,
|
|
413
448
|
};
|
|
414
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
|
+
};
|