rekwest 2.3.6 → 2.4.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 +2 -1
- package/dist/helpers.js +15 -3
- package/dist/index.js +7 -6
- package/package.json +5 -4
- package/src/helpers.mjs +331 -317
- package/src/index.mjs +7 -5
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ console.log(res.body);
|
|
|
130
130
|
* `h2` **{boolean}** `Default: false` Forces use of the HTTP2 protocol
|
|
131
131
|
* `headers` **{Object}** Headers to add to the request
|
|
132
132
|
* `parse` **{boolean}** `Default: true` Parse response body, or simply return a buffer
|
|
133
|
-
* `redirect` **{
|
|
133
|
+
* `redirect` **{error | follow | manual}** `Default: 'follow'` Controls redirect flow
|
|
134
134
|
* `thenable` **{boolean}** `Default: false` Controls promise resolutions
|
|
135
135
|
* **Returns:** Promise that resolves to
|
|
136
136
|
extended [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
|
|
@@ -168,6 +168,7 @@ Method with limited functionality to use with streams and pipes
|
|
|
168
168
|
* No automata
|
|
169
169
|
* No redirects
|
|
170
170
|
* Pass `h2: true` in options to use the HTTP2 protocol
|
|
171
|
+
* Or use `ackn({ url: URL })` method in advance to probe the available protocols
|
|
171
172
|
|
|
172
173
|
---
|
|
173
174
|
|
package/dist/helpers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
|
-
exports.premix = exports.preflight = exports.merge = exports.dispatch = exports.decompress = exports.compress = void 0;
|
|
4
|
+
exports.redirects = exports.premix = exports.preflight = exports.merge = exports.dispatch = exports.decompress = exports.compress = void 0;
|
|
5
5
|
exports.tap = tap;
|
|
6
6
|
exports.transform = void 0;
|
|
7
7
|
|
|
@@ -179,7 +179,13 @@ const preflight = options => {
|
|
|
179
179
|
};
|
|
180
180
|
options.method ??= method;
|
|
181
181
|
options.parse ??= true;
|
|
182
|
-
options.redirect ??=
|
|
182
|
+
options.redirect ??= redirects.follow;
|
|
183
|
+
|
|
184
|
+
if (!Object.values(redirects).includes(options.redirect)) {
|
|
185
|
+
options.createConnection?.().destroy();
|
|
186
|
+
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
|
|
187
|
+
}
|
|
188
|
+
|
|
183
189
|
options.redirected ??= false;
|
|
184
190
|
options.thenable ??= false;
|
|
185
191
|
return options;
|
|
@@ -230,7 +236,7 @@ const premix = (res, {
|
|
|
230
236
|
enumerable: true,
|
|
231
237
|
value: async function () {
|
|
232
238
|
if (this.bodyUsed) {
|
|
233
|
-
throw new TypeError('Response stream already read');
|
|
239
|
+
throw new TypeError('Response stream already read.');
|
|
234
240
|
}
|
|
235
241
|
|
|
236
242
|
let spool = [];
|
|
@@ -276,6 +282,12 @@ const premix = (res, {
|
|
|
276
282
|
};
|
|
277
283
|
|
|
278
284
|
exports.premix = premix;
|
|
285
|
+
const redirects = {
|
|
286
|
+
error: 'error',
|
|
287
|
+
follow: 'follow',
|
|
288
|
+
manual: 'manual'
|
|
289
|
+
};
|
|
290
|
+
exports.redirects = redirects;
|
|
279
291
|
|
|
280
292
|
async function* tap(value) {
|
|
281
293
|
if (Reflect.has(value, Symbol.asyncIterator)) {
|
package/dist/index.js
CHANGED
|
@@ -96,7 +96,7 @@ async function rekwest(url, options = {}) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
|
|
99
|
-
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body
|
|
99
|
+
throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body.`);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
if (!options.follow) {
|
|
@@ -175,15 +175,15 @@ async function rekwest(url, options = {}) {
|
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
if (follow && /^3\d{2}$/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
178
|
-
if (redirect ===
|
|
179
|
-
res.emit('error', new _errors.RequestError(`Unexpected redirect, redirect mode is set to '${redirect}'
|
|
178
|
+
if (redirect === _helpers.redirects.error) {
|
|
179
|
+
res.emit('error', new _errors.RequestError(`Unexpected redirect, redirect mode is set to '${redirect}'.`));
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
if (redirect ===
|
|
182
|
+
if (redirect === _helpers.redirects.follow) {
|
|
183
183
|
options.url = new URL(res.headers[HTTP2_HEADER_LOCATION], url).href;
|
|
184
184
|
|
|
185
185
|
if (res.statusCode !== HTTP_STATUS_SEE_OTHER && body === Object(body) && body.pipe?.constructor === Function) {
|
|
186
|
-
res.emit('error', new _errors.RequestError(`Unable to ${redirect} redirect with body as readable stream
|
|
186
|
+
res.emit('error', new _errors.RequestError(`Unable to ${redirect} redirect with body as readable stream.`));
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
options.follow--;
|
|
@@ -262,7 +262,8 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
262
262
|
headers: {
|
|
263
263
|
[HTTP2_HEADER_CONTENT_TYPE]: _mediatypes.APPLICATION_OCTET_STREAM
|
|
264
264
|
}
|
|
265
|
-
}, options)
|
|
265
|
+
}, options),
|
|
266
|
+
redirect: _helpers.redirects.manual
|
|
266
267
|
});
|
|
267
268
|
|
|
268
269
|
if (options.h2) {
|
package/package.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"@babel/eslint-parser": "^7.16.5",
|
|
14
14
|
"@babel/preset-env": "^7.16.11",
|
|
15
15
|
"c8": "^7.11.0",
|
|
16
|
-
"eslint": "^8.
|
|
17
|
-
"eslint-config-ultra-refined": "^2.
|
|
16
|
+
"eslint": "^8.8.0",
|
|
17
|
+
"eslint-config-ultra-refined": "^2.4.0",
|
|
18
18
|
"mocha": "^9.2.0"
|
|
19
19
|
},
|
|
20
20
|
"description": "The robust request library that humanity deserves 🌐",
|
|
@@ -56,8 +56,9 @@
|
|
|
56
56
|
"lint": "eslint . --ext .cjs,.js,.mjs",
|
|
57
57
|
"prepack": "npm run build && sh pony.sh",
|
|
58
58
|
"pretest": "rm -rf coverage && npm run cert:gen",
|
|
59
|
-
"test": "mocha
|
|
59
|
+
"test": "mocha",
|
|
60
|
+
"test:bail": "mocha --bail",
|
|
60
61
|
"test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
|
|
61
62
|
},
|
|
62
|
-
"version": "2.
|
|
63
|
+
"version": "2.4.0"
|
|
63
64
|
}
|
package/src/helpers.mjs
CHANGED
|
@@ -1,317 +1,331 @@
|
|
|
1
|
-
import { Blob } from 'buffer';
|
|
2
|
-
import http2 from 'http2';
|
|
3
|
-
import {
|
|
4
|
-
PassThrough,
|
|
5
|
-
Readable,
|
|
6
|
-
} from 'stream';
|
|
7
|
-
import {
|
|
8
|
-
promisify,
|
|
9
|
-
types,
|
|
10
|
-
} from 'util';
|
|
11
|
-
import zlib from 'zlib';
|
|
12
|
-
import { Cookies } from './cookies.mjs';
|
|
13
|
-
import { File } from './file.mjs';
|
|
14
|
-
import { FormData } from './formdata.mjs';
|
|
15
|
-
import {
|
|
16
|
-
APPLICATION_FORM_URLENCODED,
|
|
17
|
-
APPLICATION_JSON,
|
|
18
|
-
APPLICATION_OCTET_STREAM,
|
|
19
|
-
TEXT_PLAIN,
|
|
20
|
-
WILDCARD,
|
|
21
|
-
} from './mediatypes.mjs';
|
|
22
|
-
|
|
23
|
-
const {
|
|
24
|
-
HTTP2_HEADER_ACCEPT,
|
|
25
|
-
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
26
|
-
HTTP2_HEADER_AUTHORITY,
|
|
27
|
-
HTTP2_HEADER_CONTENT_ENCODING,
|
|
28
|
-
HTTP2_HEADER_CONTENT_LENGTH,
|
|
29
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
30
|
-
HTTP2_HEADER_COOKIE,
|
|
31
|
-
HTTP2_HEADER_METHOD,
|
|
32
|
-
HTTP2_HEADER_PATH,
|
|
33
|
-
HTTP2_HEADER_SCHEME,
|
|
34
|
-
HTTP2_METHOD_GET,
|
|
35
|
-
HTTP2_METHOD_HEAD,
|
|
36
|
-
} = http2.constants;
|
|
37
|
-
|
|
38
|
-
const brotliCompress = promisify(zlib.brotliCompress);
|
|
39
|
-
const brotliDecompress = promisify(zlib.brotliDecompress);
|
|
40
|
-
const gzip = promisify(zlib.gzip);
|
|
41
|
-
const gunzip = promisify(zlib.gunzip);
|
|
42
|
-
const deflate = promisify(zlib.deflate);
|
|
43
|
-
const inflate = promisify(zlib.inflate);
|
|
44
|
-
|
|
45
|
-
export const compress = (buf, encoding, { async = false } = {}) => {
|
|
46
|
-
encoding &&= encoding.match(/(?<encoding>\bbr\b|\bdeflate\b|\bgzip\b)/i)?.groups.encoding.toLowerCase();
|
|
47
|
-
const compressor = {
|
|
48
|
-
br: async ? brotliCompress : zlib.brotliCompressSync,
|
|
49
|
-
deflate: async ? deflate : zlib.deflateSync,
|
|
50
|
-
gzip: async ? gzip : zlib.gzipSync,
|
|
51
|
-
}[encoding];
|
|
52
|
-
|
|
53
|
-
return compressor?.(buf) ?? (async ? Promise.resolve(buf) : buf);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export const decompress = (buf, encoding, { async = false } = {}) => {
|
|
57
|
-
encoding &&= encoding.match(/(?<encoding>\bbr\b|\bdeflate\b|\bgzip\b)/i)?.groups.encoding.toLowerCase();
|
|
58
|
-
const decompressor = {
|
|
59
|
-
br: async ? brotliDecompress : zlib.brotliDecompressSync,
|
|
60
|
-
deflate: async ? inflate : zlib.inflateSync,
|
|
61
|
-
gzip: async ? gunzip : zlib.gunzipSync,
|
|
62
|
-
}[encoding];
|
|
63
|
-
|
|
64
|
-
return decompressor?.(buf) ?? (async ? Promise.resolve(buf) : buf);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export const dispatch = (req, { body, headers }) => {
|
|
68
|
-
if (types.isUint8Array(body)) {
|
|
69
|
-
return req.end(body);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (body === Object(body) && !Buffer.isBuffer(body)) {
|
|
73
|
-
if (body.pipe?.constructor !== Function
|
|
74
|
-
&& (Reflect.has(body, Symbol.asyncIterator) || Reflect.has(body, Symbol.iterator))) {
|
|
75
|
-
body = Readable.from(body);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const compressor = {
|
|
79
|
-
br: zlib.createBrotliCompress,
|
|
80
|
-
deflate: zlib.createDeflate,
|
|
81
|
-
gzip: zlib.createGzip,
|
|
82
|
-
}[headers[HTTP2_HEADER_CONTENT_ENCODING]] ?? PassThrough;
|
|
83
|
-
|
|
84
|
-
body.pipe(compressor()).pipe(req);
|
|
85
|
-
} else {
|
|
86
|
-
req.end(body);
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export const merge = (target = {}, ...rest) => {
|
|
91
|
-
target = JSON.parse(JSON.stringify(target));
|
|
92
|
-
if (!rest.length) {
|
|
93
|
-
return target;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
rest.filter((it) => it === Object(it)).forEach((it) => {
|
|
97
|
-
Object.entries(it).reduce((acc, [key, val]) => {
|
|
98
|
-
if ([
|
|
99
|
-
acc[key]?.constructor,
|
|
100
|
-
val?.constructor,
|
|
101
|
-
].every((it) => [
|
|
102
|
-
Array,
|
|
103
|
-
Object,
|
|
104
|
-
].includes(it))) {
|
|
105
|
-
if (acc[key]?.constructor === val.constructor) {
|
|
106
|
-
acc[key] = merge(acc[key], val);
|
|
107
|
-
} else {
|
|
108
|
-
acc[key] = val;
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
acc[key] = val;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return acc;
|
|
115
|
-
}, target);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
return target;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export const preflight = (options) => {
|
|
122
|
-
const url = options.url = new URL(options.url);
|
|
123
|
-
const { cookies, h2 = false, method = HTTP2_METHOD_GET, headers, redirected } = options;
|
|
124
|
-
|
|
125
|
-
if (h2) {
|
|
126
|
-
options.endStream = [
|
|
127
|
-
HTTP2_METHOD_GET,
|
|
128
|
-
HTTP2_METHOD_HEAD,
|
|
129
|
-
].includes(method);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (cookies !== false) {
|
|
133
|
-
let cookie = Cookies.jar.get(url.origin);
|
|
134
|
-
|
|
135
|
-
if (cookies === Object(cookies) && !redirected) {
|
|
136
|
-
if (cookie) {
|
|
137
|
-
new Cookies(cookies).forEach(function (val, key) {
|
|
138
|
-
this.set(key, val);
|
|
139
|
-
}, cookie);
|
|
140
|
-
} else {
|
|
141
|
-
cookie = new Cookies(cookies);
|
|
142
|
-
Cookies.jar.set(url.origin, cookie);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
options.headers = {
|
|
147
|
-
...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
|
|
148
|
-
...headers,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
options.digest ??= true;
|
|
153
|
-
options.follow ??= 20;
|
|
154
|
-
options.h2 ??= h2;
|
|
155
|
-
options.headers = {
|
|
156
|
-
[HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
|
|
157
|
-
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
|
|
158
|
-
...Object.entries(options.headers ?? {})
|
|
159
|
-
.reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
160
|
-
...h2 && {
|
|
161
|
-
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
162
|
-
[HTTP2_HEADER_METHOD]: method,
|
|
163
|
-
[HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
|
|
164
|
-
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
options.method ??= method;
|
|
169
|
-
options.parse ??= true;
|
|
170
|
-
options.redirect ??=
|
|
171
|
-
|
|
172
|
-
options.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
} else
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
body
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1
|
+
import { Blob } from 'buffer';
|
|
2
|
+
import http2 from 'http2';
|
|
3
|
+
import {
|
|
4
|
+
PassThrough,
|
|
5
|
+
Readable,
|
|
6
|
+
} from 'stream';
|
|
7
|
+
import {
|
|
8
|
+
promisify,
|
|
9
|
+
types,
|
|
10
|
+
} from 'util';
|
|
11
|
+
import zlib from 'zlib';
|
|
12
|
+
import { Cookies } from './cookies.mjs';
|
|
13
|
+
import { File } from './file.mjs';
|
|
14
|
+
import { FormData } from './formdata.mjs';
|
|
15
|
+
import {
|
|
16
|
+
APPLICATION_FORM_URLENCODED,
|
|
17
|
+
APPLICATION_JSON,
|
|
18
|
+
APPLICATION_OCTET_STREAM,
|
|
19
|
+
TEXT_PLAIN,
|
|
20
|
+
WILDCARD,
|
|
21
|
+
} from './mediatypes.mjs';
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
HTTP2_HEADER_ACCEPT,
|
|
25
|
+
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
26
|
+
HTTP2_HEADER_AUTHORITY,
|
|
27
|
+
HTTP2_HEADER_CONTENT_ENCODING,
|
|
28
|
+
HTTP2_HEADER_CONTENT_LENGTH,
|
|
29
|
+
HTTP2_HEADER_CONTENT_TYPE,
|
|
30
|
+
HTTP2_HEADER_COOKIE,
|
|
31
|
+
HTTP2_HEADER_METHOD,
|
|
32
|
+
HTTP2_HEADER_PATH,
|
|
33
|
+
HTTP2_HEADER_SCHEME,
|
|
34
|
+
HTTP2_METHOD_GET,
|
|
35
|
+
HTTP2_METHOD_HEAD,
|
|
36
|
+
} = http2.constants;
|
|
37
|
+
|
|
38
|
+
const brotliCompress = promisify(zlib.brotliCompress);
|
|
39
|
+
const brotliDecompress = promisify(zlib.brotliDecompress);
|
|
40
|
+
const gzip = promisify(zlib.gzip);
|
|
41
|
+
const gunzip = promisify(zlib.gunzip);
|
|
42
|
+
const deflate = promisify(zlib.deflate);
|
|
43
|
+
const inflate = promisify(zlib.inflate);
|
|
44
|
+
|
|
45
|
+
export const compress = (buf, encoding, { async = false } = {}) => {
|
|
46
|
+
encoding &&= encoding.match(/(?<encoding>\bbr\b|\bdeflate\b|\bgzip\b)/i)?.groups.encoding.toLowerCase();
|
|
47
|
+
const compressor = {
|
|
48
|
+
br: async ? brotliCompress : zlib.brotliCompressSync,
|
|
49
|
+
deflate: async ? deflate : zlib.deflateSync,
|
|
50
|
+
gzip: async ? gzip : zlib.gzipSync,
|
|
51
|
+
}[encoding];
|
|
52
|
+
|
|
53
|
+
return compressor?.(buf) ?? (async ? Promise.resolve(buf) : buf);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const decompress = (buf, encoding, { async = false } = {}) => {
|
|
57
|
+
encoding &&= encoding.match(/(?<encoding>\bbr\b|\bdeflate\b|\bgzip\b)/i)?.groups.encoding.toLowerCase();
|
|
58
|
+
const decompressor = {
|
|
59
|
+
br: async ? brotliDecompress : zlib.brotliDecompressSync,
|
|
60
|
+
deflate: async ? inflate : zlib.inflateSync,
|
|
61
|
+
gzip: async ? gunzip : zlib.gunzipSync,
|
|
62
|
+
}[encoding];
|
|
63
|
+
|
|
64
|
+
return decompressor?.(buf) ?? (async ? Promise.resolve(buf) : buf);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const dispatch = (req, { body, headers }) => {
|
|
68
|
+
if (types.isUint8Array(body)) {
|
|
69
|
+
return req.end(body);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (body === Object(body) && !Buffer.isBuffer(body)) {
|
|
73
|
+
if (body.pipe?.constructor !== Function
|
|
74
|
+
&& (Reflect.has(body, Symbol.asyncIterator) || Reflect.has(body, Symbol.iterator))) {
|
|
75
|
+
body = Readable.from(body);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const compressor = {
|
|
79
|
+
br: zlib.createBrotliCompress,
|
|
80
|
+
deflate: zlib.createDeflate,
|
|
81
|
+
gzip: zlib.createGzip,
|
|
82
|
+
}[headers[HTTP2_HEADER_CONTENT_ENCODING]] ?? PassThrough;
|
|
83
|
+
|
|
84
|
+
body.pipe(compressor()).pipe(req);
|
|
85
|
+
} else {
|
|
86
|
+
req.end(body);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const merge = (target = {}, ...rest) => {
|
|
91
|
+
target = JSON.parse(JSON.stringify(target));
|
|
92
|
+
if (!rest.length) {
|
|
93
|
+
return target;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
rest.filter((it) => it === Object(it)).forEach((it) => {
|
|
97
|
+
Object.entries(it).reduce((acc, [key, val]) => {
|
|
98
|
+
if ([
|
|
99
|
+
acc[key]?.constructor,
|
|
100
|
+
val?.constructor,
|
|
101
|
+
].every((it) => [
|
|
102
|
+
Array,
|
|
103
|
+
Object,
|
|
104
|
+
].includes(it))) {
|
|
105
|
+
if (acc[key]?.constructor === val.constructor) {
|
|
106
|
+
acc[key] = merge(acc[key], val);
|
|
107
|
+
} else {
|
|
108
|
+
acc[key] = val;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
acc[key] = val;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return acc;
|
|
115
|
+
}, target);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return target;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const preflight = (options) => {
|
|
122
|
+
const url = options.url = new URL(options.url);
|
|
123
|
+
const { cookies, h2 = false, method = HTTP2_METHOD_GET, headers, redirected } = options;
|
|
124
|
+
|
|
125
|
+
if (h2) {
|
|
126
|
+
options.endStream = [
|
|
127
|
+
HTTP2_METHOD_GET,
|
|
128
|
+
HTTP2_METHOD_HEAD,
|
|
129
|
+
].includes(method);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (cookies !== false) {
|
|
133
|
+
let cookie = Cookies.jar.get(url.origin);
|
|
134
|
+
|
|
135
|
+
if (cookies === Object(cookies) && !redirected) {
|
|
136
|
+
if (cookie) {
|
|
137
|
+
new Cookies(cookies).forEach(function (val, key) {
|
|
138
|
+
this.set(key, val);
|
|
139
|
+
}, cookie);
|
|
140
|
+
} else {
|
|
141
|
+
cookie = new Cookies(cookies);
|
|
142
|
+
Cookies.jar.set(url.origin, cookie);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
options.headers = {
|
|
147
|
+
...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
|
|
148
|
+
...headers,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
options.digest ??= true;
|
|
153
|
+
options.follow ??= 20;
|
|
154
|
+
options.h2 ??= h2;
|
|
155
|
+
options.headers = {
|
|
156
|
+
[HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
|
|
157
|
+
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
|
|
158
|
+
...Object.entries(options.headers ?? {})
|
|
159
|
+
.reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
|
|
160
|
+
...h2 && {
|
|
161
|
+
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
162
|
+
[HTTP2_HEADER_METHOD]: method,
|
|
163
|
+
[HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
|
|
164
|
+
[HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
options.method ??= method;
|
|
169
|
+
options.parse ??= true;
|
|
170
|
+
options.redirect ??= redirects.follow;
|
|
171
|
+
|
|
172
|
+
if (!Object.values(redirects).includes(options.redirect)) {
|
|
173
|
+
options.createConnection?.().destroy();
|
|
174
|
+
throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${
|
|
175
|
+
options.redirect
|
|
176
|
+
}' is not a valid enum value.`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
options.redirected ??= false;
|
|
180
|
+
options.thenable ??= false;
|
|
181
|
+
|
|
182
|
+
return options;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const premix = (res, { digest = false, parse = false } = {}) => {
|
|
186
|
+
if (!digest) {
|
|
187
|
+
Object.defineProperties(res, {
|
|
188
|
+
arrayBuffer: {
|
|
189
|
+
enumerable: true,
|
|
190
|
+
value: function () {
|
|
191
|
+
parse &&= false;
|
|
192
|
+
|
|
193
|
+
return this.body().then(({ buffer, byteLength, byteOffset }) => buffer.slice(
|
|
194
|
+
byteOffset,
|
|
195
|
+
byteOffset + byteLength,
|
|
196
|
+
));
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
blob: {
|
|
200
|
+
enumerable: true,
|
|
201
|
+
value: function () {
|
|
202
|
+
return this.arrayBuffer().then((res) => new Blob([res]));
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
json: {
|
|
206
|
+
enumerable: true,
|
|
207
|
+
value: function () {
|
|
208
|
+
return this.text().then((res) => JSON.parse(res));
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
text: {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
value: function () {
|
|
214
|
+
return this.blob().then((blob) => blob.text());
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return Object.defineProperties(res, {
|
|
221
|
+
body: {
|
|
222
|
+
enumerable: true,
|
|
223
|
+
value: async function () {
|
|
224
|
+
if (this.bodyUsed) {
|
|
225
|
+
throw new TypeError('Response stream already read.');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let spool = [];
|
|
229
|
+
|
|
230
|
+
for await (const chunk of this) {
|
|
231
|
+
spool.push(chunk);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
spool = Buffer.concat(spool);
|
|
235
|
+
|
|
236
|
+
if (spool.length) {
|
|
237
|
+
spool = await decompress(spool, this.headers[HTTP2_HEADER_CONTENT_ENCODING], { async: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (spool.length && parse) {
|
|
241
|
+
const contentType = this.headers[HTTP2_HEADER_CONTENT_TYPE] ?? '';
|
|
242
|
+
const charset = contentType.split(';')
|
|
243
|
+
.find((it) => /charset=/i.test(it))
|
|
244
|
+
?.toLowerCase()
|
|
245
|
+
.replace('charset=', '')
|
|
246
|
+
.replace('iso-8859-1', 'latin1')
|
|
247
|
+
.trim() || 'utf-8';
|
|
248
|
+
|
|
249
|
+
if (/\bjson\b/i.test(contentType)) {
|
|
250
|
+
spool = JSON.parse(spool.toString(charset));
|
|
251
|
+
} else if (/\b(text|xml)\b/i.test(contentType)) {
|
|
252
|
+
if (/\b(latin1|ucs-2|utf-(8|16le))\b/.test(charset)) {
|
|
253
|
+
spool = spool.toString(charset);
|
|
254
|
+
} else {
|
|
255
|
+
spool = new TextDecoder(charset).decode(spool);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return spool;
|
|
261
|
+
},
|
|
262
|
+
writable: true,
|
|
263
|
+
},
|
|
264
|
+
bodyUsed: {
|
|
265
|
+
enumerable: true,
|
|
266
|
+
get: function () {
|
|
267
|
+
return this.readableEnded;
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export const redirects = {
|
|
274
|
+
error: 'error',
|
|
275
|
+
follow: 'follow',
|
|
276
|
+
manual: 'manual',
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export async function* tap(value) {
|
|
280
|
+
if (Reflect.has(value, Symbol.asyncIterator)) {
|
|
281
|
+
yield* value;
|
|
282
|
+
} else if (value.stream) {
|
|
283
|
+
yield* value.stream();
|
|
284
|
+
} else {
|
|
285
|
+
yield await value.arrayBuffer();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export const transform = (body, options) => {
|
|
290
|
+
let headers = {};
|
|
291
|
+
|
|
292
|
+
if (File.alike(body)) {
|
|
293
|
+
headers = {
|
|
294
|
+
[HTTP2_HEADER_CONTENT_LENGTH]: body.size,
|
|
295
|
+
[HTTP2_HEADER_CONTENT_TYPE]: body.type || APPLICATION_OCTET_STREAM,
|
|
296
|
+
};
|
|
297
|
+
body = body.stream?.() ?? Readable.from(tap(body));
|
|
298
|
+
} else if (FormData.alike(body)) {
|
|
299
|
+
body = FormData.actuate(body);
|
|
300
|
+
headers = { [HTTP2_HEADER_CONTENT_TYPE]: body.contentType };
|
|
301
|
+
} else if (body === Object(body) && !Reflect.has(body, Symbol.asyncIterator)) {
|
|
302
|
+
if (body.constructor === URLSearchParams) {
|
|
303
|
+
headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_FORM_URLENCODED };
|
|
304
|
+
body = body.toString();
|
|
305
|
+
} else if (!Buffer.isBuffer(body)
|
|
306
|
+
&& !(!Array.isArray(body) && Reflect.has(body, Symbol.iterator))) {
|
|
307
|
+
headers = { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_JSON };
|
|
308
|
+
body = JSON.stringify(body);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (types.isUint8Array(body) || Buffer.isBuffer(body) || body !== Object(body)) {
|
|
312
|
+
if (options.headers[HTTP2_HEADER_CONTENT_ENCODING]) {
|
|
313
|
+
body = compress(body, options.headers[HTTP2_HEADER_CONTENT_ENCODING]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
headers = {
|
|
317
|
+
...headers,
|
|
318
|
+
[HTTP2_HEADER_CONTENT_LENGTH]: Buffer.byteLength(body),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
Object.assign(options.headers, {
|
|
324
|
+
...headers,
|
|
325
|
+
...options.headers[HTTP2_HEADER_CONTENT_TYPE] && {
|
|
326
|
+
[HTTP2_HEADER_CONTENT_TYPE]: options.headers[HTTP2_HEADER_CONTENT_TYPE],
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return body;
|
|
331
|
+
};
|
package/src/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
merge,
|
|
10
10
|
preflight,
|
|
11
11
|
premix,
|
|
12
|
+
redirects,
|
|
12
13
|
transform,
|
|
13
14
|
} from './helpers.mjs';
|
|
14
15
|
import { APPLICATION_OCTET_STREAM } from './mediatypes.mjs';
|
|
@@ -44,7 +45,7 @@ export default async function rekwest(url, options = {}) {
|
|
|
44
45
|
HTTP2_METHOD_GET,
|
|
45
46
|
HTTP2_METHOD_HEAD,
|
|
46
47
|
].includes(options.method)) {
|
|
47
|
-
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body
|
|
48
|
+
throw new TypeError(`Request with ${ HTTP2_METHOD_GET }/${ HTTP2_METHOD_HEAD } method cannot have body.`);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
if (!options.follow) {
|
|
@@ -122,16 +123,16 @@ export default async function rekwest(url, options = {}) {
|
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
if (follow && /^3\d{2}$/.test(res.statusCode) && res.headers[HTTP2_HEADER_LOCATION]) {
|
|
125
|
-
if (redirect ===
|
|
126
|
-
res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'
|
|
126
|
+
if (redirect === redirects.error) {
|
|
127
|
+
res.emit('error', new RequestError(`Unexpected redirect, redirect mode is set to '${ redirect }'.`));
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
if (redirect ===
|
|
130
|
+
if (redirect === redirects.follow) {
|
|
130
131
|
options.url = new URL(res.headers[HTTP2_HEADER_LOCATION], url).href;
|
|
131
132
|
|
|
132
133
|
if (res.statusCode !== HTTP_STATUS_SEE_OTHER
|
|
133
134
|
&& body === Object(body) && body.pipe?.constructor === Function) {
|
|
134
|
-
res.emit('error', new RequestError(`Unable to ${ redirect } redirect with body as readable stream
|
|
135
|
+
res.emit('error', new RequestError(`Unable to ${ redirect } redirect with body as readable stream.`));
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
options.follow--;
|
|
@@ -211,6 +212,7 @@ Reflect.defineProperty(rekwest, 'stream', {
|
|
|
211
212
|
...merge(rekwest.defaults, {
|
|
212
213
|
headers: { [HTTP2_HEADER_CONTENT_TYPE]: APPLICATION_OCTET_STREAM },
|
|
213
214
|
}, options),
|
|
215
|
+
redirect: redirects.manual,
|
|
214
216
|
});
|
|
215
217
|
|
|
216
218
|
if (options.h2) {
|