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/dist/utils.js CHANGED
@@ -1,38 +1,37 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.sanitize = exports.sameOrigin = exports.preflight = exports.mixin = exports.merge = exports.dispatch = exports.decompress = exports.compress = exports.brandCheck = exports.affix = exports.admix = void 0;
4
+ exports.sanitize = exports.sameOrigin = exports.mixin = exports.merge = exports.maxRetryAfterError = exports.maxRetryAfter = exports.dispatch = exports.decompress = exports.compress = exports.brandCheck = exports.affix = exports.admix = void 0;
5
5
  exports.tap = tap;
6
- exports.transform = void 0;
6
+ exports.validation = exports.unwind = exports.transform = exports.transfer = void 0;
7
7
  var _nodeBuffer = require("node:buffer");
8
- var _nodeHttp = _interopRequireDefault(require("node:http2"));
8
+ var _nodeHttp = _interopRequireDefault(require("node:http"));
9
+ var _nodeHttp2 = _interopRequireDefault(require("node:http2"));
10
+ var _nodeHttps = _interopRequireDefault(require("node:https"));
9
11
  var _nodeStream = require("node:stream");
10
12
  var _consumers = require("node:stream/consumers");
13
+ var _promises = require("node:timers/promises");
11
14
  var _nodeUtil = require("node:util");
12
15
  var _nodeZlib = _interopRequireDefault(require("node:zlib"));
16
+ var _ackn = require("./ackn");
13
17
  var _constants = require("./constants");
14
- var _cookies = require("./cookies");
15
18
  var _errors = require("./errors");
16
19
  var _file = require("./file");
17
20
  var _formdata = require("./formdata");
21
+ var _index = _interopRequireDefault(require("./index"));
18
22
  var _mediatypes = require("./mediatypes");
23
+ var _postflight = require("./postflight");
24
+ var _preflight = require("./preflight");
19
25
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20
26
  const {
21
- HTTP2_HEADER_ACCEPT,
22
- HTTP2_HEADER_ACCEPT_ENCODING,
23
- HTTP2_HEADER_AUTHORITY,
24
27
  HTTP2_HEADER_CONTENT_ENCODING,
25
28
  HTTP2_HEADER_CONTENT_LENGTH,
26
29
  HTTP2_HEADER_CONTENT_TYPE,
27
- HTTP2_HEADER_COOKIE,
28
- HTTP2_HEADER_METHOD,
29
- HTTP2_HEADER_PATH,
30
- HTTP2_HEADER_SCHEME,
30
+ HTTP2_HEADER_RETRY_AFTER,
31
31
  HTTP2_HEADER_STATUS,
32
32
  HTTP2_METHOD_GET,
33
33
  HTTP2_METHOD_HEAD
34
- } = _nodeHttp.default.constants;
35
- const unwind = encodings => encodings.split(',').map(it => it.trim());
34
+ } = _nodeHttp2.default.constants;
36
35
  const admix = (res, headers, options) => {
37
36
  const {
38
37
  h2
@@ -126,6 +125,10 @@ const dispatch = ({
126
125
  }
127
126
  };
128
127
  exports.dispatch = dispatch;
128
+ const maxRetryAfter = Symbol('maxRetryAfter');
129
+ exports.maxRetryAfter = maxRetryAfter;
130
+ const maxRetryAfterError = (interval, options) => new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms.`, options);
131
+ exports.maxRetryAfterError = maxRetryAfterError;
129
132
  const merge = (target = {}, ...rest) => {
130
133
  target = JSON.parse(JSON.stringify(target));
131
134
  if (!rest.length) {
@@ -215,7 +218,7 @@ const mixin = (res, {
215
218
  if (/\bjson\b/i.test(contentType)) {
216
219
  body = JSON.parse(body.toString(charset));
217
220
  } else if (/\b(?:text|xml)\b/i.test(contentType)) {
218
- if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b/.test(charset)) {
221
+ if (/\b(?:latin1|ucs-2|utf-(?:8|16le))\b/i.test(charset)) {
219
222
  body = body.toString(charset);
220
223
  } else {
221
224
  body = new TextDecoder(charset).decode(body);
@@ -235,66 +238,9 @@ const mixin = (res, {
235
238
  });
236
239
  };
237
240
  exports.mixin = mixin;
238
- const preflight = options => {
239
- const {
240
- cookies,
241
- h2 = false,
242
- headers,
243
- method = HTTP2_METHOD_GET,
244
- redirected,
245
- url
246
- } = options;
247
- if (h2) {
248
- options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
249
- }
250
- if (cookies !== false) {
251
- let cookie = _cookies.Cookies.jar.get(url.origin);
252
- if (cookies === Object(cookies) && !redirected) {
253
- if (cookie) {
254
- new _cookies.Cookies(cookies).forEach(function (val, key) {
255
- this.set(key, val);
256
- }, cookie);
257
- } else {
258
- cookie = new _cookies.Cookies(cookies);
259
- _cookies.Cookies.jar.set(url.origin, cookie);
260
- }
261
- }
262
- options.headers = {
263
- ...(cookie && {
264
- [HTTP2_HEADER_COOKIE]: cookie
265
- }),
266
- ...headers
267
- };
268
- }
269
- options.digest ??= true;
270
- options.follow ??= 20;
271
- options.h2 ??= h2;
272
- options.headers = {
273
- [HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
274
- [HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, deflate-raw, gzip, identity',
275
- ...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
276
- ...(h2 && {
277
- [HTTP2_HEADER_AUTHORITY]: url.host,
278
- [HTTP2_HEADER_METHOD]: method,
279
- [HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
280
- [HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
281
- })
282
- };
283
- options.method ??= method;
284
- options.parse ??= true;
285
- options.redirect ??= _constants.redirectModes.follow;
286
- if (!Reflect.has(_constants.redirectModes, options.redirect)) {
287
- options.createConnection?.().destroy();
288
- throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
289
- }
290
- options.redirected ??= false;
291
- options.thenable ??= false;
292
- return options;
293
- };
294
- exports.preflight = preflight;
295
241
  const sanitize = (url, options = {}) => {
296
242
  if (options.trimTrailingSlashes) {
297
- url = `${url}`.replace(/(?<!:)\/+/gi, '/');
243
+ url = `${url}`.replace(/(?<!:)\/+/g, '/');
298
244
  }
299
245
  url = new URL(url);
300
246
  return Object.assign(options, {
@@ -313,6 +259,92 @@ async function* tap(value) {
313
259
  yield await value.arrayBuffer();
314
260
  }
315
261
  }
262
+ const transfer = async options => {
263
+ const {
264
+ url
265
+ } = options;
266
+ if (options.follow === 0) {
267
+ throw new _errors.RequestError(`Maximum redirect reached at: ${url.href}`);
268
+ }
269
+ if (url.protocol === 'https:') {
270
+ options = await (0, _ackn.ackn)(options);
271
+ } else if (Reflect.has(options, 'alpnProtocol')) {
272
+ ['alpnProtocol', 'createConnection', 'h2', 'protocol'].forEach(it => Reflect.deleteProperty(options, it));
273
+ }
274
+ try {
275
+ options = await transform((0, _preflight.preflight)(options));
276
+ } catch (ex) {
277
+ options.createConnection?.().destroy();
278
+ throw ex;
279
+ }
280
+ const {
281
+ digest,
282
+ h2,
283
+ redirected,
284
+ thenable
285
+ } = options;
286
+ const {
287
+ request
288
+ } = url.protocol === 'http:' ? _nodeHttp.default : _nodeHttps.default;
289
+ const promise = new Promise((resolve, reject) => {
290
+ let client, req;
291
+ if (h2) {
292
+ client = _nodeHttp2.default.connect(url.origin, options);
293
+ req = client.request(options.headers, options);
294
+ } else {
295
+ req = request(url, options);
296
+ }
297
+ affix(client, req, options);
298
+ req.once('error', reject);
299
+ req.once('frameError', reject);
300
+ req.once('goaway', reject);
301
+ req.once('response', res => (0, _postflight.postflight)(req, res, options, {
302
+ reject,
303
+ resolve
304
+ }));
305
+ dispatch(options, req);
306
+ });
307
+ try {
308
+ const res = await promise;
309
+ if (digest && !redirected) {
310
+ res.body = await res.body();
311
+ }
312
+ return res;
313
+ } catch (ex) {
314
+ const {
315
+ maxRetryAfter,
316
+ retry
317
+ } = options;
318
+ if (retry?.attempts && retry?.statusCodes.includes(ex.statusCode)) {
319
+ let {
320
+ interval
321
+ } = retry;
322
+ if (retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
323
+ interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
324
+ interval = Number(interval) * 1000 || new Date(interval) - Date.now();
325
+ if (interval > maxRetryAfter) {
326
+ throw maxRetryAfterError(interval, {
327
+ cause: ex
328
+ });
329
+ }
330
+ } else {
331
+ interval = new Function('interval', `return Math.ceil(${retry.backoffStrategy});`)(interval);
332
+ }
333
+ retry.attempts--;
334
+ retry.interval = interval;
335
+ return (0, _promises.setTimeout)(interval).then(() => (0, _index.default)(url, options));
336
+ }
337
+ if (digest && !redirected && ex.body) {
338
+ ex.body = await ex.body();
339
+ }
340
+ if (!thenable) {
341
+ throw ex;
342
+ } else {
343
+ return ex;
344
+ }
345
+ }
346
+ };
347
+ exports.transfer = transfer;
316
348
  const transform = async options => {
317
349
  let {
318
350
  body,
@@ -371,4 +403,19 @@ const transform = async options => {
371
403
  body
372
404
  };
373
405
  };
374
- exports.transform = transform;
406
+ exports.transform = transform;
407
+ const unwind = encodings => encodings.split(',').map(it => it.trim());
408
+ exports.unwind = unwind;
409
+ const validation = (options = {}) => {
410
+ if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
411
+ throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body.`);
412
+ }
413
+ if (!Object.values(_constants.requestCredentials).includes(options.credentials)) {
414
+ throw new TypeError(`Failed to read the 'credentials' property from 'options': The provided value '${options.credentials}' is not a valid enum value.`);
415
+ }
416
+ if (!Reflect.has(_constants.requestRedirect, options.redirect)) {
417
+ throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
418
+ }
419
+ return options;
420
+ };
421
+ exports.validation = validation;
package/package.json CHANGED
@@ -67,5 +67,5 @@
67
67
  "test:bail": "mocha --bail",
68
68
  "test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
69
69
  },
70
- "version": "4.1.0"
70
+ "version": "4.2.0"
71
71
  }
package/src/ackn.mjs CHANGED
@@ -23,7 +23,7 @@ export const ackn = (options) => new Promise((resolve, reject) => {
23
23
  createConnection() {
24
24
  return socket;
25
25
  },
26
- h2: /h2c?/.test(alpnProtocol),
26
+ h2: /h2c?/i.test(alpnProtocol),
27
27
  protocol: url.protocol,
28
28
  });
29
29
  });
package/src/constants.mjs CHANGED
@@ -8,13 +8,19 @@ const {
8
8
  HTTP_STATUS_TEMPORARY_REDIRECT,
9
9
  } = http2.constants;
10
10
 
11
- export const redirectModes = {
11
+ export const requestCredentials = {
12
+ include: 'include',
13
+ omit: 'omit',
14
+ sameOrigin: 'same-origin',
15
+ };
16
+
17
+ export const requestRedirect = {
12
18
  error: 'error',
13
19
  follow: 'follow',
14
20
  manual: 'manual',
15
21
  };
16
22
 
17
- export const redirectStatusCodes = [
23
+ export const requestRedirectCodes = [
18
24
  HTTP_STATUS_MOVED_PERMANENTLY,
19
25
  HTTP_STATUS_FOUND,
20
26
  HTTP_STATUS_SEE_OTHER,
@@ -0,0 +1,44 @@
1
+ import http2 from 'node:http2';
2
+ import {
3
+ requestCredentials,
4
+ requestRedirect,
5
+ } from './constants.mjs';
6
+ import { maxRetryAfter } from './utils.mjs';
7
+
8
+ const {
9
+ HTTP2_METHOD_GET,
10
+ HTTP_STATUS_SERVICE_UNAVAILABLE,
11
+ HTTP_STATUS_TOO_MANY_REQUESTS,
12
+ } = http2.constants;
13
+
14
+ const stash = {
15
+ credentials: requestCredentials.sameOrigin,
16
+ digest: true,
17
+ follow: 20,
18
+ get maxRetryAfter() {
19
+ return this[maxRetryAfter] ?? this.timeout;
20
+ },
21
+ set maxRetryAfter(value) {
22
+ this[maxRetryAfter] = value;
23
+ },
24
+ method: HTTP2_METHOD_GET,
25
+ parse: true,
26
+ redirect: requestRedirect.follow,
27
+ redirected: false,
28
+ retry: {
29
+ attempts: 0,
30
+ backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
31
+ interval: 1e3,
32
+ retryAfter: true,
33
+ statusCodes: [
34
+ HTTP_STATUS_TOO_MANY_REQUESTS,
35
+ HTTP_STATUS_SERVICE_UNAVAILABLE,
36
+ ],
37
+ },
38
+ thenable: false,
39
+ timeout: 3e5,
40
+ };
41
+
42
+ export default {
43
+ stash,
44
+ };