rekwest 7.1.1 → 7.2.1
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 +19 -13
- package/dist/config.cjs +2 -2
- package/dist/cookies.cjs +2 -2
- package/dist/postflight.cjs +7 -4
- package/dist/redirects.cjs +11 -7
- package/dist/retries.cjs +6 -4
- package/dist/utils.cjs +5 -5
- 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/postflight.js +9 -5
- package/src/redirects.js +20 -8
- package/src/retries.js +5 -5
- package/src/utils.js +5 -5
package/README.md
CHANGED
|
@@ -48,9 +48,8 @@ const res = await rekwest(url, {
|
|
|
48
48
|
body: { celestial: 'payload' },
|
|
49
49
|
headers: {
|
|
50
50
|
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
51
|
-
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', //
|
|
52
|
-
/** [HTTP2_HEADER_CONTENT_TYPE]
|
|
53
|
-
* is undue for
|
|
51
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // Enables: body encoding
|
|
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,17 +81,17 @@ 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({
|
|
89
|
-
aux: Date.now(), //
|
|
88
|
+
aux: Date.now(), // Either [[key, value]] or kv sequenceable
|
|
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
|
|
|
@@ -100,7 +99,7 @@ const res = await rekwest(url, {
|
|
|
100
99
|
body: fd,
|
|
101
100
|
headers: {
|
|
102
101
|
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
103
|
-
[HTTP2_HEADER_CONTENT_ENCODING]: 'zstd', //
|
|
102
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'zstd', // Enables: body encoding
|
|
104
103
|
},
|
|
105
104
|
method: HTTP2_METHOD_POST,
|
|
106
105
|
});
|
|
@@ -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/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,7 +19,6 @@ const {
|
|
|
19
19
|
const retries = (err, options) => {
|
|
20
20
|
const {
|
|
21
21
|
body,
|
|
22
|
-
maxRetryAfter,
|
|
23
22
|
method,
|
|
24
23
|
retry,
|
|
25
24
|
url
|
|
@@ -36,8 +35,8 @@ 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) {
|
|
38
|
+
interval = Number.isFinite(Number.parseInt(interval, 10)) ? interval * 1e3 : new Date(interval) - Date.now();
|
|
39
|
+
if (interval > retry.maxRetryAfter) {
|
|
41
40
|
throw new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms`, {
|
|
42
41
|
cause: err
|
|
43
42
|
});
|
|
@@ -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
|
@@ -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
|
}
|
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/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,7 +12,7 @@ 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 (![
|
|
@@ -27,8 +27,8 @@ 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
33
|
`Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms`,
|
|
34
34
|
{ cause: err },
|
|
@@ -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
|
@@ -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
|
}
|