rekwest 7.1.0 → 7.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 +16 -10
- package/dist/config.cjs +2 -2
- package/dist/cookies.cjs +2 -2
- package/dist/formdata.cjs +5 -5
- package/dist/mixin.cjs +1 -1
- package/dist/postflight.cjs +7 -4
- package/dist/redirects.cjs +11 -7
- package/dist/retries.cjs +8 -6
- package/dist/utils.cjs +7 -7
- package/dist/validation.cjs +3 -3
- package/package.json +1 -1
- package/src/codecs.js +1 -0
- package/src/config.js +2 -2
- package/src/cookies.js +2 -2
- package/src/formdata.js +5 -5
- package/src/mixin.js +1 -1
- package/src/postflight.js +9 -5
- package/src/redirects.js +20 -8
- package/src/retries.js +7 -7
- package/src/utils.js +7 -7
- package/src/validation.js +3 -3
package/README.md
CHANGED
|
@@ -49,8 +49,7 @@ const res = await rekwest(url, {
|
|
|
49
49
|
headers: {
|
|
50
50
|
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
51
51
|
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body encoding
|
|
52
|
-
/** [HTTP2_HEADER_CONTENT_TYPE]
|
|
53
|
-
* is undue for
|
|
52
|
+
/** [HTTP2_HEADER_CONTENT_TYPE] is undue for
|
|
54
53
|
* Array/Blob/File/FormData/Object/URLSearchParams body types
|
|
55
54
|
* and will be set automatically, with an option to override it here
|
|
56
55
|
*/
|
|
@@ -82,7 +81,7 @@ const {
|
|
|
82
81
|
} = constants;
|
|
83
82
|
|
|
84
83
|
const blob = new Blob(['bits']);
|
|
85
|
-
const file = new File(['bits'], 'file.
|
|
84
|
+
const file = new File(['bits'], 'file.xyz');
|
|
86
85
|
const readable = Readable.from('bits');
|
|
87
86
|
|
|
88
87
|
const fd = new FormData({
|
|
@@ -90,9 +89,9 @@ const fd = new FormData({
|
|
|
90
89
|
});
|
|
91
90
|
|
|
92
91
|
fd.append('celestial', 'payload');
|
|
93
|
-
fd.append('blob', blob, 'blob.
|
|
92
|
+
fd.append('blob', blob, 'blob.xyz');
|
|
94
93
|
fd.append('file', file);
|
|
95
|
-
fd.append('readable', readable, 'readable.
|
|
94
|
+
fd.append('readable', readable, 'readable.xyz');
|
|
96
95
|
|
|
97
96
|
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
98
97
|
|
|
@@ -121,36 +120,37 @@ console.log(res.body);
|
|
|
121
120
|
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options)
|
|
122
121
|
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)
|
|
123
122
|
for HTTP/2 attunes
|
|
123
|
+
* `allowDowngrade` **{boolean}** `Default: false` Controls whether `https:` redirects to `http:` are allowed
|
|
124
124
|
* `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL
|
|
125
125
|
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
|
|
126
126
|
FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
|
|
127
127
|
with the request
|
|
128
128
|
* `bufferBody` **{boolean}** `Default: false` Toggles the buffering of the streamable request bodies for redirects and
|
|
129
129
|
retries
|
|
130
|
-
* `cookies` **{boolean | Array<[k, v]> |
|
|
130
|
+
* `cookies` **{boolean | string[] | Array<[k, v]> | Cookies | Object | URLSearchParams}** `Default: true` The
|
|
131
131
|
cookies to add to
|
|
132
132
|
the request
|
|
133
133
|
* `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache
|
|
134
134
|
* `credentials` **{include | omit | same-origin}** `Default: same-origin` Controls credentials in case of cross-origin
|
|
135
135
|
redirects
|
|
136
|
-
* `decodersOptions` **{Object}** Configures decoders options, e.g.: `brotli`, `
|
|
136
|
+
* `decodersOptions` **{Object}** Configures decoders options, e.g.: `brotli`, `zlib`, `zstd`
|
|
137
137
|
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or add a mixin
|
|
138
|
-
* `encodersOptions` **{Object}** Configures encoders options, e.g.: `brotli`, `
|
|
138
|
+
* `encodersOptions` **{Object}** Configures encoders options, e.g.: `brotli`, `zlib`, `zstd`
|
|
139
139
|
* `follow` **{number}** `Default: 20` The number of redirects to follow
|
|
140
140
|
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
|
|
141
141
|
* `headers` **{Object}** The headers to add to the request
|
|
142
|
-
* `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
|
|
143
142
|
* `params` **{Object}** The search params to add to the `url`
|
|
144
143
|
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or return a buffer
|
|
145
144
|
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
|
|
146
145
|
* `retry` **{Object}** Represents the retry options
|
|
147
146
|
* `attempts` **{number}** `Default: 0` The number of retry attempts
|
|
148
147
|
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
|
|
149
|
-
The backoff strategy
|
|
148
|
+
The backoff strategy uses a log-uniform algorithm. To fix the interval, set the value to `interval * 1`.
|
|
150
149
|
* `errorCodes` **{string[]}**
|
|
151
150
|
`Default: ['ECONNREFUSED', 'ECONNRESET', 'EHOSTDOWN', 'EHOSTUNREACH', 'ENETDOWN', 'ENETUNREACH', 'ENOTFOUND', 'ERR_HTTP2_STREAM_ERROR']`
|
|
152
151
|
The list of error codes to retry on
|
|
153
152
|
* `interval` **{number}** `Default: 1e3` The initial retry interval
|
|
153
|
+
* `maxRetryAfter` **{number}** `Default: 3e5` The maximum `retry-after` limit in milliseconds
|
|
154
154
|
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
|
|
155
155
|
* `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on
|
|
156
156
|
* `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL
|
|
@@ -202,10 +202,16 @@ const rk = rekwest.extend({
|
|
|
202
202
|
baseURL: 'https://somewhe.re',
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
const params = {
|
|
206
|
+
id: '[uid]',
|
|
207
|
+
signature: '[code]',
|
|
208
|
+
variant: 'A',
|
|
209
|
+
};
|
|
205
210
|
const signal = AbortSignal.timeout(1e4);
|
|
206
211
|
const url = '/somewhat/endpoint';
|
|
207
212
|
|
|
208
213
|
const res = await rk(url, {
|
|
214
|
+
params,
|
|
209
215
|
signal,
|
|
210
216
|
});
|
|
211
217
|
|
package/dist/config.cjs
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
} = _nodeHttp.default.constants;
|
|
23
23
|
const timeout = 3e5;
|
|
24
24
|
const defaults = {
|
|
25
|
+
allowDowngrade: false,
|
|
25
26
|
bufferBody: false,
|
|
26
27
|
cookiesTTL: false,
|
|
27
28
|
credentials: _constants.requestCredentials.sameOrigin,
|
|
@@ -45,16 +46,15 @@ const defaults = {
|
|
|
45
46
|
[HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
|
|
46
47
|
[HTTP2_HEADER_ACCEPT_ENCODING]: `br,${isZstdSupported ? ' zstd, ' : ' '}gzip, deflate, deflate-raw`
|
|
47
48
|
},
|
|
48
|
-
maxRetryAfter: timeout,
|
|
49
49
|
method: HTTP2_METHOD_GET,
|
|
50
50
|
parse: true,
|
|
51
51
|
redirect: _constants.requestRedirect.follow,
|
|
52
|
-
redirected: false,
|
|
53
52
|
retry: {
|
|
54
53
|
attempts: 0,
|
|
55
54
|
backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
|
|
56
55
|
errorCodes: ['ECONNREFUSED', 'ECONNRESET', 'EHOSTDOWN', 'EHOSTUNREACH', 'ENETDOWN', 'ENETUNREACH', 'ENOTFOUND', 'ERR_HTTP2_STREAM_ERROR'],
|
|
57
56
|
interval: 1e3,
|
|
57
|
+
maxRetryAfter: timeout,
|
|
58
58
|
retryAfter: true,
|
|
59
59
|
statusCodes: [HTTP_STATUS_TOO_MANY_REQUESTS, HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_BAD_GATEWAY, HTTP_STATUS_SERVICE_UNAVAILABLE, HTTP_STATUS_GATEWAY_TIMEOUT]
|
|
60
60
|
},
|
package/dist/cookies.cjs
CHANGED
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.Cookies = void 0;
|
|
7
7
|
var _utils = require("./utils.cjs");
|
|
8
|
-
const lifetimeCap = 3456e7; //
|
|
8
|
+
const lifetimeCap = 3456e7; // 400 days
|
|
9
9
|
|
|
10
10
|
class Cookies extends URLSearchParams {
|
|
11
11
|
static #finalizers = new Set();
|
|
@@ -37,7 +37,7 @@ class Cookies extends URLSearchParams {
|
|
|
37
37
|
for (const attr of attrs) {
|
|
38
38
|
if (/(?:expires|max-age)=/i.test(attr)) {
|
|
39
39
|
const [key, val] = attr.toLowerCase().split('=');
|
|
40
|
-
const ms = Number.isFinite(Number(val)) ? val * 1e3 : Date.parse(val) - Date.now();
|
|
40
|
+
const ms = Number.isFinite(Number.parseInt(val, 10)) ? val * 1e3 : Date.parse(val) - Date.now();
|
|
41
41
|
ttl[(0, _utils.toCamelCase)(key)] = Math.min(ms, lifetimeCap);
|
|
42
42
|
}
|
|
43
43
|
}
|
package/dist/formdata.cjs
CHANGED
|
@@ -70,9 +70,9 @@ class FormData {
|
|
|
70
70
|
if (Object(input) === input) {
|
|
71
71
|
if (Array.isArray(input)) {
|
|
72
72
|
if (!input.every(it => Array.isArray(it))) {
|
|
73
|
-
throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': The provided value cannot be converted to a sequence
|
|
73
|
+
throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': The provided value cannot be converted to a sequence`);
|
|
74
74
|
} else if (!input.every(it => it.length === 2)) {
|
|
75
|
-
throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': Sequence initializer must only contain pair elements
|
|
75
|
+
throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': Sequence initializer must only contain pair elements`);
|
|
76
76
|
}
|
|
77
77
|
input = Array.from(input);
|
|
78
78
|
} else if (!Reflect.has(input, Symbol.iterator)) {
|
|
@@ -85,16 +85,16 @@ class FormData {
|
|
|
85
85
|
}
|
|
86
86
|
#ensureArgs(args, expected, method) {
|
|
87
87
|
if (args.length < expected) {
|
|
88
|
-
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': ${expected} arguments required, but only ${args.length} present
|
|
88
|
+
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': ${expected} arguments required, but only ${args.length} present`);
|
|
89
89
|
}
|
|
90
90
|
if (['append', 'set'].includes(method)) {
|
|
91
91
|
if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
|
|
92
|
-
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Blob'
|
|
92
|
+
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Blob'`);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
if (method === 'forEach') {
|
|
96
96
|
if (args[0]?.constructor !== Function) {
|
|
97
|
-
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Function'
|
|
97
|
+
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Function'`);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
}
|
package/dist/mixin.cjs
CHANGED
|
@@ -74,7 +74,7 @@ const mixin = (res, {
|
|
|
74
74
|
value: async function () {
|
|
75
75
|
(0, _utils.brandCheck)(this, res?.constructor);
|
|
76
76
|
if (this.bodyUsed) {
|
|
77
|
-
throw new TypeError('Response stream already read
|
|
77
|
+
throw new TypeError('Response stream already read');
|
|
78
78
|
}
|
|
79
79
|
let body = await (0, _consumers.buffer)((0, _codecs.decode)(this, this.headers[HTTP2_HEADER_CONTENT_ENCODING], {
|
|
80
80
|
decodersOptions
|
package/dist/postflight.cjs
CHANGED
|
@@ -48,13 +48,16 @@ const postflight = (req, res, options, {
|
|
|
48
48
|
enumerable: true,
|
|
49
49
|
value: cookies !== false && _cookies.Cookies.jar.has(url.origin) ? _cookies.Cookies.jar.get(url.origin) : void 0
|
|
50
50
|
});
|
|
51
|
-
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = (0, _redirects.redirects)(res, options);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
res.emit('error', err);
|
|
56
|
+
return reject((0, _mixin.mixin)(res, options));
|
|
57
|
+
}
|
|
52
58
|
if (Object(result) === result) {
|
|
53
59
|
return result.then(resolve, reject);
|
|
54
60
|
}
|
|
55
|
-
if (result) {
|
|
56
|
-
return reject((0, _mixin.mixin)(res, options));
|
|
57
|
-
}
|
|
58
61
|
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
59
62
|
return reject((0, _mixin.mixin)(res, options));
|
|
60
63
|
}
|
package/dist/redirects.cjs
CHANGED
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
} = _nodeHttp.default.constants;
|
|
25
25
|
const redirects = (res, options) => {
|
|
26
26
|
const {
|
|
27
|
+
allowDowngrade,
|
|
27
28
|
credentials,
|
|
28
29
|
follow,
|
|
29
30
|
redirect,
|
|
@@ -31,21 +32,24 @@ const redirects = (res, options) => {
|
|
|
31
32
|
} = options;
|
|
32
33
|
if (follow && /3\d{2}/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
33
34
|
if (redirect === _constants.requestRedirect.error) {
|
|
34
|
-
|
|
35
|
+
throw new _errors.RequestError(`Unexpected redirect, redirect mode is set to: ${redirect}`);
|
|
35
36
|
}
|
|
36
37
|
if (redirect === _constants.requestRedirect.follow) {
|
|
37
|
-
const
|
|
38
|
-
if (!/^https?:/i.test(
|
|
39
|
-
|
|
38
|
+
const loc = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
|
|
39
|
+
if (!/^https?:/i.test(loc.protocol)) {
|
|
40
|
+
throw new _errors.RequestError('URL scheme must be "http" or "https"');
|
|
40
41
|
}
|
|
41
|
-
if (!
|
|
42
|
+
if (!allowDowngrade && loc.protocol === 'http:' && url.protocol === 'https:') {
|
|
43
|
+
throw new _errors.RequestError(`Protocol downgrade detected, redirect from "${url.protocol}" to "${loc.protocol}": ${loc}`);
|
|
44
|
+
}
|
|
45
|
+
if (!(0, _utils.sameOrigin)(loc, url)) {
|
|
42
46
|
if (credentials !== _constants.requestCredentials.include) {
|
|
43
47
|
options.credentials = _constants.requestCredentials.omit;
|
|
44
48
|
}
|
|
45
49
|
options.h2 = false;
|
|
46
50
|
}
|
|
47
51
|
if ([HTTP_STATUS_PERMANENT_REDIRECT, HTTP_STATUS_TEMPORARY_REDIRECT].includes(res.statusCode) && (0, _utils.isPipeStream)(options.body) && !(0, _nodeStream.isReadable)(options.body)) {
|
|
48
|
-
|
|
52
|
+
throw new _errors.RequestError(`Unable to ${redirect} redirect with streamable body`);
|
|
49
53
|
}
|
|
50
54
|
if ([HTTP_STATUS_MOVED_PERMANENTLY, HTTP_STATUS_FOUND].includes(res.statusCode) && options.method === HTTP2_METHOD_POST || res.statusCode === HTTP_STATUS_SEE_OTHER && ![HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
51
55
|
options.body = null;
|
|
@@ -53,7 +57,7 @@ const redirects = (res, options) => {
|
|
|
53
57
|
}
|
|
54
58
|
options.follow--;
|
|
55
59
|
options.redirected = true;
|
|
56
|
-
return (0, _index.default)(
|
|
60
|
+
return (0, _index.default)(loc, options);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
};
|
package/dist/retries.cjs
CHANGED
|
@@ -19,14 +19,13 @@ const {
|
|
|
19
19
|
const retries = (err, options) => {
|
|
20
20
|
const {
|
|
21
21
|
body,
|
|
22
|
-
maxRetryAfter,
|
|
23
22
|
method,
|
|
24
23
|
retry,
|
|
25
24
|
url
|
|
26
25
|
} = options;
|
|
27
26
|
if (retry?.attempts > 0) {
|
|
28
27
|
if (![HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method) && (0, _utils.isPipeStream)(body) && !(0, _nodeStream.isReadable)(body)) {
|
|
29
|
-
throw new _errors.RequestError('Request stream already read
|
|
28
|
+
throw new _errors.RequestError('Request stream already read', {
|
|
30
29
|
cause: err
|
|
31
30
|
});
|
|
32
31
|
}
|
|
@@ -36,9 +35,9 @@ const retries = (err, options) => {
|
|
|
36
35
|
} = retry;
|
|
37
36
|
if (retry.retryAfter && err.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
|
|
38
37
|
interval = err.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
39
|
-
interval =
|
|
40
|
-
if (interval > maxRetryAfter) {
|
|
41
|
-
throw new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms
|
|
38
|
+
interval = Number.isFinite(Number.parseInt(interval, 10)) ? interval * 1e3 : new Date(interval) - Date.now();
|
|
39
|
+
if (interval > retry.maxRetryAfter) {
|
|
40
|
+
throw new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms`, {
|
|
42
41
|
cause: err
|
|
43
42
|
});
|
|
44
43
|
}
|
|
@@ -50,7 +49,10 @@ const retries = (err, options) => {
|
|
|
50
49
|
}
|
|
51
50
|
retry.attempts--;
|
|
52
51
|
retry.interval = interval;
|
|
53
|
-
return
|
|
52
|
+
return _promises.scheduler.wait(interval).then(() => (0, _index.default)(url, {
|
|
53
|
+
...options,
|
|
54
|
+
params: void 0
|
|
55
|
+
}));
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
};
|
package/dist/utils.cjs
CHANGED
|
@@ -60,7 +60,7 @@ const augment = (res, headers, options) => {
|
|
|
60
60
|
exports.augment = augment;
|
|
61
61
|
const brandCheck = (val, ctor) => {
|
|
62
62
|
if (!(val instanceof ctor)) {
|
|
63
|
-
throw new TypeError('Illegal invocation
|
|
63
|
+
throw new TypeError('Illegal invocation');
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
exports.brandCheck = brandCheck;
|
|
@@ -127,15 +127,15 @@ const normalize = (url, options = {}) => {
|
|
|
127
127
|
});
|
|
128
128
|
};
|
|
129
129
|
exports.normalize = normalize;
|
|
130
|
-
const normalizeHeaders = headers => {
|
|
130
|
+
const normalizeHeaders = (headers = {}) => {
|
|
131
131
|
const acc = {};
|
|
132
|
-
for (const [key, val] of Object.entries(headers
|
|
132
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
133
133
|
const name = key.toLowerCase();
|
|
134
134
|
acc[key] = val;
|
|
135
135
|
if (key === HTTP2_HEADER_ACCEPT_ENCODING && !_config.isZstdSupported) {
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
acc[key] =
|
|
136
|
+
const modified = val.replace(/\s?zstd,?/gi, '').trim();
|
|
137
|
+
if (modified) {
|
|
138
|
+
acc[key] = modified;
|
|
139
139
|
} else {
|
|
140
140
|
Reflect.deleteProperty(acc, name);
|
|
141
141
|
}
|
|
@@ -149,7 +149,7 @@ exports.sameOrigin = sameOrigin;
|
|
|
149
149
|
const snoop = (client, req, options) => {
|
|
150
150
|
req.once('close', () => client?.close());
|
|
151
151
|
req.once('end', () => client?.close());
|
|
152
|
-
req.once('timeout', () => req.destroy(new _errors.TimeoutError(`Timed out after ${options.timeout} ms
|
|
152
|
+
req.once('timeout', () => req.destroy(new _errors.TimeoutError(`Timed out after ${options.timeout} ms`)));
|
|
153
153
|
req.once('trailers', trailers => {
|
|
154
154
|
Reflect.defineProperty(req, 'trailers', {
|
|
155
155
|
enumerable: true,
|
package/dist/validation.cjs
CHANGED
|
@@ -13,13 +13,13 @@ const {
|
|
|
13
13
|
} = _nodeHttp.default.constants;
|
|
14
14
|
const validation = (options = {}) => {
|
|
15
15
|
if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
16
|
-
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body
|
|
16
|
+
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body`);
|
|
17
17
|
}
|
|
18
18
|
if (!Object.values(_constants.requestCredentials).includes(options.credentials)) {
|
|
19
|
-
throw new TypeError(`Failed to read the 'credentials' property from 'options': The provided value '${options.credentials}' is not a valid enum value
|
|
19
|
+
throw new TypeError(`Failed to read the 'credentials' property from 'options': The provided value '${options.credentials}' is not a valid enum value`);
|
|
20
20
|
}
|
|
21
21
|
if (!Reflect.has(_constants.requestRedirect, options.redirect)) {
|
|
22
|
-
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value
|
|
22
|
+
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value`);
|
|
23
23
|
}
|
|
24
24
|
return options;
|
|
25
25
|
};
|
package/package.json
CHANGED
package/src/codecs.js
CHANGED
|
@@ -36,6 +36,7 @@ export const encodeCodecs = {
|
|
|
36
36
|
gzip: (opts) => zlib.createGzip(opts?.zlib),
|
|
37
37
|
zstd: (opts) => isZstdSupported && zlib.createZstdCompress(opts?.zstd),
|
|
38
38
|
};
|
|
39
|
+
|
|
39
40
|
export const encode = (readable, encodings = '', { encodersOptions } = {}) => {
|
|
40
41
|
const encoders = [];
|
|
41
42
|
|
package/src/config.js
CHANGED
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
const timeout = 3e5;
|
|
27
27
|
|
|
28
28
|
const defaults = {
|
|
29
|
+
allowDowngrade: false,
|
|
29
30
|
bufferBody: false,
|
|
30
31
|
cookiesTTL: false,
|
|
31
32
|
credentials: requestCredentials.sameOrigin,
|
|
@@ -49,11 +50,9 @@ const defaults = {
|
|
|
49
50
|
[HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
|
|
50
51
|
[HTTP2_HEADER_ACCEPT_ENCODING]: `br,${ isZstdSupported ? ' zstd, ' : ' ' }gzip, deflate, deflate-raw`,
|
|
51
52
|
},
|
|
52
|
-
maxRetryAfter: timeout,
|
|
53
53
|
method: HTTP2_METHOD_GET,
|
|
54
54
|
parse: true,
|
|
55
55
|
redirect: requestRedirect.follow,
|
|
56
|
-
redirected: false,
|
|
57
56
|
retry: {
|
|
58
57
|
attempts: 0,
|
|
59
58
|
backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
|
|
@@ -68,6 +67,7 @@ const defaults = {
|
|
|
68
67
|
'ERR_HTTP2_STREAM_ERROR',
|
|
69
68
|
],
|
|
70
69
|
interval: 1e3,
|
|
70
|
+
maxRetryAfter: timeout,
|
|
71
71
|
retryAfter: true,
|
|
72
72
|
statusCodes: [
|
|
73
73
|
HTTP_STATUS_TOO_MANY_REQUESTS,
|
package/src/cookies.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
toCamelCase,
|
|
4
4
|
} from './utils.js';
|
|
5
5
|
|
|
6
|
-
const lifetimeCap = 3456e7; //
|
|
6
|
+
const lifetimeCap = 3456e7; // 400 days
|
|
7
7
|
|
|
8
8
|
export class Cookies extends URLSearchParams {
|
|
9
9
|
|
|
@@ -39,7 +39,7 @@ export class Cookies extends URLSearchParams {
|
|
|
39
39
|
for (const attr of attrs) {
|
|
40
40
|
if (/(?:expires|max-age)=/i.test(attr)) {
|
|
41
41
|
const [key, val] = attr.toLowerCase().split('=');
|
|
42
|
-
const ms = Number.isFinite(Number(val)) ? val * 1e3 : Date.parse(val) - Date.now();
|
|
42
|
+
const ms = Number.isFinite(Number.parseInt(val, 10)) ? val * 1e3 : Date.parse(val) - Date.now();
|
|
43
43
|
|
|
44
44
|
ttl[toCamelCase(key)] = Math.min(ms, lifetimeCap);
|
|
45
45
|
}
|
package/src/formdata.js
CHANGED
|
@@ -98,11 +98,11 @@ export class FormData {
|
|
|
98
98
|
if (!input.every((it) => Array.isArray(it))) {
|
|
99
99
|
throw new TypeError(`Failed to construct '${
|
|
100
100
|
this[Symbol.toStringTag]
|
|
101
|
-
}': The provided value cannot be converted to a sequence
|
|
101
|
+
}': The provided value cannot be converted to a sequence`);
|
|
102
102
|
} else if (!input.every((it) => it.length === 2)) {
|
|
103
103
|
throw new TypeError(`Failed to construct '${
|
|
104
104
|
this[Symbol.toStringTag]
|
|
105
|
-
}': Sequence initializer must only contain pair elements
|
|
105
|
+
}': Sequence initializer must only contain pair elements`);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
input = Array.from(input);
|
|
@@ -120,7 +120,7 @@ export class FormData {
|
|
|
120
120
|
if (args.length < expected) {
|
|
121
121
|
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
122
122
|
this[Symbol.toStringTag]
|
|
123
|
-
}': ${ expected } arguments required, but only ${ args.length } present
|
|
123
|
+
}': ${ expected } arguments required, but only ${ args.length } present`);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
if ([
|
|
@@ -130,7 +130,7 @@ export class FormData {
|
|
|
130
130
|
if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
|
|
131
131
|
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
132
132
|
this[Symbol.toStringTag]
|
|
133
|
-
}': parameter ${ expected } is not of type 'Blob'
|
|
133
|
+
}': parameter ${ expected } is not of type 'Blob'`);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -138,7 +138,7 @@ export class FormData {
|
|
|
138
138
|
if (args[0]?.constructor !== Function) {
|
|
139
139
|
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
140
140
|
this[Symbol.toStringTag]
|
|
141
|
-
}': parameter ${ expected } is not of type 'Function'
|
|
141
|
+
}': parameter ${ expected } is not of type 'Function'`);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
}
|
package/src/mixin.js
CHANGED
|
@@ -71,7 +71,7 @@ export const mixin = (res, { decodersOptions, digest = false, parse = false } =
|
|
|
71
71
|
brandCheck(this, res?.constructor);
|
|
72
72
|
|
|
73
73
|
if (this.bodyUsed) {
|
|
74
|
-
throw new TypeError('Response stream already read
|
|
74
|
+
throw new TypeError('Response stream already read');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
let body = await buffer(decode(this, this.headers[HTTP2_HEADER_CONTENT_ENCODING], { decodersOptions }));
|
package/src/postflight.js
CHANGED
|
@@ -42,16 +42,20 @@ export const postflight = (req, res, options, { reject, resolve }) => {
|
|
|
42
42
|
value: cookies !== false && Cookies.jar.has(url.origin) ? Cookies.jar.get(url.origin) : void 0,
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
let result;
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
47
|
+
try {
|
|
48
|
+
result = redirects(res, options);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
res.emit('error', err);
|
|
50
51
|
|
|
51
|
-
if (result) {
|
|
52
52
|
return reject(mixin(res, options));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
if (Object(result) === result) {
|
|
56
|
+
return result.then(resolve, reject);
|
|
57
|
+
}
|
|
58
|
+
|
|
55
59
|
if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
56
60
|
return reject(mixin(res, options));
|
|
57
61
|
}
|
package/src/redirects.js
CHANGED
|
@@ -24,21 +24,33 @@ const {
|
|
|
24
24
|
} = http2.constants;
|
|
25
25
|
|
|
26
26
|
export const redirects = (res, options) => {
|
|
27
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
allowDowngrade,
|
|
29
|
+
credentials,
|
|
30
|
+
follow,
|
|
31
|
+
redirect,
|
|
32
|
+
url,
|
|
33
|
+
} = options;
|
|
28
34
|
|
|
29
35
|
if (follow && /3\d{2}/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
30
36
|
if (redirect === requestRedirect.error) {
|
|
31
|
-
|
|
37
|
+
throw new RequestError(`Unexpected redirect, redirect mode is set to: ${ redirect }`);
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
if (redirect === requestRedirect.follow) {
|
|
35
|
-
const
|
|
41
|
+
const loc = new URL(res.headers[HTTP2_HEADER_LOCATION], url);
|
|
36
42
|
|
|
37
|
-
if (!/^https?:/i.test(
|
|
38
|
-
|
|
43
|
+
if (!/^https?:/i.test(loc.protocol)) {
|
|
44
|
+
throw new RequestError('URL scheme must be "http" or "https"');
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
if (!
|
|
47
|
+
if (!allowDowngrade && loc.protocol === 'http:' && url.protocol === 'https:') {
|
|
48
|
+
throw new RequestError(
|
|
49
|
+
`Protocol downgrade detected, redirect from "${ url.protocol }" to "${ loc.protocol }": ${ loc }`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!sameOrigin(loc, url)) {
|
|
42
54
|
if (credentials !== requestCredentials.include) {
|
|
43
55
|
options.credentials = requestCredentials.omit;
|
|
44
56
|
}
|
|
@@ -50,7 +62,7 @@ export const redirects = (res, options) => {
|
|
|
50
62
|
HTTP_STATUS_PERMANENT_REDIRECT,
|
|
51
63
|
HTTP_STATUS_TEMPORARY_REDIRECT,
|
|
52
64
|
].includes(res.statusCode) && isPipeStream(options.body) && !isReadable(options.body)) {
|
|
53
|
-
|
|
65
|
+
throw new RequestError(`Unable to ${ redirect } redirect with streamable body`);
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
if (([
|
|
@@ -68,7 +80,7 @@ export const redirects = (res, options) => {
|
|
|
68
80
|
options.follow--;
|
|
69
81
|
options.redirected = true;
|
|
70
82
|
|
|
71
|
-
return rekwest(
|
|
83
|
+
return rekwest(loc, options);
|
|
72
84
|
}
|
|
73
85
|
}
|
|
74
86
|
};
|
package/src/retries.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http2 from 'node:http2';
|
|
2
2
|
import { isReadable } from 'node:stream';
|
|
3
|
-
import {
|
|
3
|
+
import { scheduler } from 'node:timers/promises';
|
|
4
4
|
import { RequestError } from './errors.js';
|
|
5
5
|
import rekwest from './index.js';
|
|
6
6
|
import { isPipeStream } from './utils.js';
|
|
@@ -12,14 +12,14 @@ const {
|
|
|
12
12
|
} = http2.constants;
|
|
13
13
|
|
|
14
14
|
export const retries = (err, options) => {
|
|
15
|
-
const { body,
|
|
15
|
+
const { body, method, retry, url } = options;
|
|
16
16
|
|
|
17
17
|
if (retry?.attempts > 0) {
|
|
18
18
|
if (![
|
|
19
19
|
HTTP2_METHOD_GET,
|
|
20
20
|
HTTP2_METHOD_HEAD,
|
|
21
21
|
].includes(method) && isPipeStream(body) && !isReadable(body)) {
|
|
22
|
-
throw new RequestError('Request stream already read
|
|
22
|
+
throw new RequestError('Request stream already read', { cause: err });
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (retry.errorCodes?.includes(err.code) || retry.statusCodes?.includes(err.statusCode)) {
|
|
@@ -27,10 +27,10 @@ export const retries = (err, options) => {
|
|
|
27
27
|
|
|
28
28
|
if (retry.retryAfter && err.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
|
|
29
29
|
interval = err.headers[HTTP2_HEADER_RETRY_AFTER];
|
|
30
|
-
interval =
|
|
31
|
-
if (interval > maxRetryAfter) {
|
|
30
|
+
interval = Number.isFinite(Number.parseInt(interval, 10)) ? interval * 1e3 : new Date(interval) - Date.now();
|
|
31
|
+
if (interval > retry.maxRetryAfter) {
|
|
32
32
|
throw new RequestError(
|
|
33
|
-
`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms
|
|
33
|
+
`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms`,
|
|
34
34
|
{ cause: err },
|
|
35
35
|
);
|
|
36
36
|
}
|
|
@@ -45,7 +45,7 @@ export const retries = (err, options) => {
|
|
|
45
45
|
retry.attempts--;
|
|
46
46
|
retry.interval = interval;
|
|
47
47
|
|
|
48
|
-
return
|
|
48
|
+
return scheduler.wait(interval).then(() => rekwest(url, { ...options, params: void 0 }));
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
};
|
package/src/utils.js
CHANGED
|
@@ -62,7 +62,7 @@ export const augment = (res, headers, options) => {
|
|
|
62
62
|
|
|
63
63
|
export const brandCheck = (val, ctor) => {
|
|
64
64
|
if (!(val instanceof ctor)) {
|
|
65
|
-
throw new TypeError('Illegal invocation
|
|
65
|
+
throw new TypeError('Illegal invocation');
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -137,19 +137,19 @@ export const normalize = (url, options = {}) => {
|
|
|
137
137
|
});
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
-
export const normalizeHeaders = (headers) => {
|
|
140
|
+
export const normalizeHeaders = (headers = {}) => {
|
|
141
141
|
const acc = {};
|
|
142
142
|
|
|
143
|
-
for (const [key, val] of Object.entries(headers
|
|
143
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
144
144
|
const name = key.toLowerCase();
|
|
145
145
|
|
|
146
146
|
acc[key] = val;
|
|
147
147
|
|
|
148
148
|
if (key === HTTP2_HEADER_ACCEPT_ENCODING && !isZstdSupported) {
|
|
149
|
-
const
|
|
149
|
+
const modified = val.replace(/\s?zstd,?/gi, '').trim();
|
|
150
150
|
|
|
151
|
-
if (
|
|
152
|
-
acc[key] =
|
|
151
|
+
if (modified) {
|
|
152
|
+
acc[key] = modified;
|
|
153
153
|
} else {
|
|
154
154
|
Reflect.deleteProperty(acc, name);
|
|
155
155
|
}
|
|
@@ -164,7 +164,7 @@ export const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname ===
|
|
|
164
164
|
export const snoop = (client, req, options) => {
|
|
165
165
|
req.once('close', () => client?.close());
|
|
166
166
|
req.once('end', () => client?.close());
|
|
167
|
-
req.once('timeout', () => req.destroy(new TimeoutError(`Timed out after ${ options.timeout } ms
|
|
167
|
+
req.once('timeout', () => req.destroy(new TimeoutError(`Timed out after ${ options.timeout } ms`)));
|
|
168
168
|
req.once('trailers', (trailers) => {
|
|
169
169
|
Reflect.defineProperty(req, 'trailers', {
|
|
170
170
|
enumerable: true,
|
package/src/validation.js
CHANGED
|
@@ -14,19 +14,19 @@ export const validation = (options = {}) => {
|
|
|
14
14
|
HTTP2_METHOD_GET,
|
|
15
15
|
HTTP2_METHOD_HEAD,
|
|
16
16
|
].includes(options.method)) {
|
|
17
|
-
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body
|
|
17
|
+
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body`);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (!Object.values(requestCredentials).includes(options.credentials)) {
|
|
21
21
|
throw new TypeError(`Failed to read the 'credentials' property from 'options': The provided value '${
|
|
22
22
|
options.credentials
|
|
23
|
-
}' is not a valid enum value
|
|
23
|
+
}' is not a valid enum value`);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if (!Reflect.has(requestRedirect, options.redirect)) {
|
|
27
27
|
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${
|
|
28
28
|
options.redirect
|
|
29
|
-
}' is not a valid enum value
|
|
29
|
+
}' is not a valid enum value`);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
return options;
|