rekwest 2.3.5 → 3.0.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/src/formdata.mjs CHANGED
@@ -1,224 +1,224 @@
1
- import { randomBytes } from 'crypto';
2
- import http2 from 'http2';
3
- import { toUSVString } from 'util';
4
- import { File } from './file.mjs';
5
- import { tap } from './helpers.mjs';
6
- import {
7
- APPLICATION_OCTET_STREAM,
8
- MULTIPART_FORM_DATA,
9
- } from './mediatypes.mjs';
10
-
11
- const CRLF = '\r\n';
12
- const {
13
- HTTP2_HEADER_CONTENT_DISPOSITION,
14
- HTTP2_HEADER_CONTENT_TYPE,
15
- } = http2.constants;
16
-
17
- export class FormData {
18
-
19
- static actuate(fd) {
20
- const boundary = randomBytes(24).toString('hex');
21
- const contentType = `${ MULTIPART_FORM_DATA }; boundary=${ boundary }`;
22
- const prefix = `--${ boundary }${ CRLF }${ HTTP2_HEADER_CONTENT_DISPOSITION }: form-data`;
23
-
24
- const escape = (str) => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
25
- const normalize = (value) => value.replace(/\r?\n|\r/g, CRLF);
26
-
27
- return {
28
- contentType,
29
- async* [Symbol.asyncIterator]() {
30
- const encoder = new TextEncoder();
31
-
32
- for (const [name, value] of fd) {
33
- if (value.constructor === String) {
34
- yield encoder.encode(`${ prefix }; name="${
35
- escape(normalize(name))
36
- }"${ CRLF.repeat(2) }${ normalize(value) }${ CRLF }`);
37
- } else {
38
- yield encoder.encode(`${ prefix }; name="${
39
- escape(normalize(name))
40
- }"${ value.name ? `; filename="${ escape(value.name) }"` : '' }${ CRLF }${
41
- HTTP2_HEADER_CONTENT_TYPE
42
- }: ${
43
- value.type || APPLICATION_OCTET_STREAM
44
- }${ CRLF.repeat(2) }`);
45
- yield* tap(value);
46
- yield encoder.encode(CRLF);
47
- }
48
- }
49
-
50
- yield encoder.encode(`--${ boundary }--`);
51
- },
52
- };
53
- }
54
-
55
- static alike(instance) {
56
- return instance?.constructor.name === FormData.name;
57
- }
58
-
59
- static #enfoldEntry(name, value, filename) {
60
- name = toUSVString(name);
61
- filename &&= toUSVString(filename);
62
-
63
- if (File.alike(value)) {
64
- value = new File([value], filename, value);
65
- } else if (this.#ensureInstance(value)) {
66
- value.name = filename || value.name;
67
- } else {
68
- value = toUSVString(value);
69
- }
70
-
71
- return {
72
- name,
73
- value,
74
- };
75
- }
76
-
77
- static #ensureInstance(value) {
78
- return File.alike(value) || (value === Object(value) && Reflect.has(value, Symbol.asyncIterator));
79
- }
80
-
81
- #entries = [];
82
-
83
- get [Symbol.toStringTag]() {
84
- return this.constructor.name;
85
- }
86
-
87
- constructor(input) {
88
- if (input === Object(input)
89
- && (input?.constructor === Object || Reflect.has(input, Symbol.iterator))) {
90
-
91
- if (input.constructor !== Object) {
92
- input = Array.from(input);
93
- }
94
-
95
- if (Array.isArray(input)) {
96
- if (!input.every((it) => Array.isArray(it))) {
97
- throw new TypeError(`Failed to construct '${
98
- this[Symbol.toStringTag]
99
- }': The provided value cannot be converted to a sequence.`);
100
- } else if (!input.every((it) => it.length === 2)) {
101
- throw new TypeError(`Failed to construct '${
102
- this[Symbol.toStringTag]
103
- }': Sequence initializer must only contain pair elements.`);
104
- }
105
- }
106
-
107
- if (input.constructor === Object) {
108
- input = Object.entries(input);
109
- }
110
-
111
- input.forEach(([key, value]) => this.append(key, value));
112
- }
113
- }
114
-
115
- #ensureArgs(args, expected, method) {
116
- if (args.length < expected) {
117
- throw new TypeError(`Failed to execute '${ method }' on '${
118
- this[Symbol.toStringTag]
119
- }': ${ expected } arguments required, but only ${ args.length } present.`);
120
- }
121
-
122
- if ([
123
- 'append',
124
- 'set',
125
- ].includes(method)) {
126
- if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
127
- throw new TypeError(`Failed to execute '${ method }' on '${
128
- this[Symbol.toStringTag]
129
- }': parameter ${ expected } is not of type 'Blob', 'File' or async iterable.`);
130
- }
131
- }
132
-
133
- if (method === 'forEach') {
134
- if (args[0]?.constructor !== Function) {
135
- throw new TypeError(`Failed to execute '${ method }' on '${
136
- this[Symbol.toStringTag]
137
- }': parameter ${ expected } is not of type 'Function'.`);
138
- }
139
- }
140
- }
141
-
142
- append(...args) {
143
- this.#ensureArgs(args, 2, 'append');
144
- this.#entries.push(this.constructor.#enfoldEntry(...args));
145
- }
146
-
147
- delete(...args) {
148
- this.#ensureArgs(args, 1, 'delete');
149
- const name = toUSVString(args[0]);
150
-
151
- this.#entries = this.#entries.filter((it) => it.name !== name);
152
- }
153
-
154
- forEach(...args) {
155
- this.#ensureArgs(args, 1, 'forEach');
156
- const [callback, thisArg] = args;
157
-
158
- for (const entry of this) {
159
- Reflect.apply(callback, thisArg, [
160
- ...(entry.reverse()),
161
- this,
162
- ]);
163
- }
164
- }
165
-
166
- get(...args) {
167
- this.#ensureArgs(args, 1, 'get');
168
- const name = toUSVString(args[0]);
169
-
170
- return (this.#entries.find((it) => it.name === name) ?? {}).value ?? null;
171
- }
172
-
173
- getAll(...args) {
174
- this.#ensureArgs(args, 1, 'getAll');
175
- const name = toUSVString(args[0]);
176
-
177
- return this.#entries.filter((it) => it.name === name).map((it) => it.value);
178
- }
179
-
180
- has(...args) {
181
- this.#ensureArgs(args, 1, 'has');
182
- const name = toUSVString(args[0]);
183
-
184
- return !!this.#entries.find((it) => it.name === name);
185
- }
186
-
187
- set(...args) {
188
- this.#ensureArgs(args, 2, 'set');
189
- const entry = this.constructor.#enfoldEntry(...args);
190
- const idx = this.#entries.findIndex((it) => it.name === entry.name);
191
-
192
- if (idx !== -1) {
193
- this.#entries.splice(idx, 1, entry);
194
- } else {
195
- this.#entries.push(entry);
196
- }
197
- }
198
-
199
- * entries() {
200
- for (const { name, value } of this.#entries) {
201
- yield [
202
- name,
203
- value,
204
- ];
205
- }
206
- }
207
-
208
- * keys() {
209
- for (const [name] of this) {
210
- yield name;
211
- }
212
- }
213
-
214
- * values() {
215
- for (const [, value] of this) {
216
- yield value;
217
- }
218
- }
219
-
220
- [Symbol.iterator]() {
221
- return this.entries();
222
- }
223
-
224
- }
1
+ import { randomBytes } from 'crypto';
2
+ import http2 from 'http2';
3
+ import { toUSVString } from 'util';
4
+ import { File } from './file.mjs';
5
+ import { tap } from './helpers.mjs';
6
+ import {
7
+ APPLICATION_OCTET_STREAM,
8
+ MULTIPART_FORM_DATA,
9
+ } from './mediatypes.mjs';
10
+
11
+ const CRLF = '\r\n';
12
+ const {
13
+ HTTP2_HEADER_CONTENT_DISPOSITION,
14
+ HTTP2_HEADER_CONTENT_TYPE,
15
+ } = http2.constants;
16
+
17
+ export class FormData {
18
+
19
+ static actuate(fd) {
20
+ const boundary = randomBytes(24).toString('hex');
21
+ const contentType = `${ MULTIPART_FORM_DATA }; boundary=${ boundary }`;
22
+ const prefix = `--${ boundary }${ CRLF }${ HTTP2_HEADER_CONTENT_DISPOSITION }: form-data`;
23
+
24
+ const escape = (str) => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
25
+ const normalize = (value) => value.replace(/\r?\n|\r/g, CRLF);
26
+
27
+ return {
28
+ contentType,
29
+ async* [Symbol.asyncIterator]() {
30
+ const encoder = new TextEncoder();
31
+
32
+ for (const [name, value] of fd) {
33
+ if (value.constructor === String) {
34
+ yield encoder.encode(`${ prefix }; name="${
35
+ escape(normalize(name))
36
+ }"${ CRLF.repeat(2) }${ normalize(value) }${ CRLF }`);
37
+ } else {
38
+ yield encoder.encode(`${ prefix }; name="${
39
+ escape(normalize(name))
40
+ }"${ value.name ? `; filename="${ escape(value.name) }"` : '' }${ CRLF }${
41
+ HTTP2_HEADER_CONTENT_TYPE
42
+ }: ${
43
+ value.type || APPLICATION_OCTET_STREAM
44
+ }${ CRLF.repeat(2) }`);
45
+ yield* tap(value);
46
+ yield encoder.encode(CRLF);
47
+ }
48
+ }
49
+
50
+ yield encoder.encode(`--${ boundary }--`);
51
+ },
52
+ };
53
+ }
54
+
55
+ static alike(instance) {
56
+ return instance?.constructor.name === FormData.name;
57
+ }
58
+
59
+ static #enfoldEntry(name, value, filename) {
60
+ name = toUSVString(name);
61
+ filename &&= toUSVString(filename);
62
+
63
+ if (File.alike(value)) {
64
+ value = new File([value], filename, value);
65
+ } else if (this.#ensureInstance(value)) {
66
+ value.name = filename || value.name;
67
+ } else {
68
+ value = toUSVString(value);
69
+ }
70
+
71
+ return {
72
+ name,
73
+ value,
74
+ };
75
+ }
76
+
77
+ static #ensureInstance(value) {
78
+ return File.alike(value) || (value === Object(value) && Reflect.has(value, Symbol.asyncIterator));
79
+ }
80
+
81
+ #entries = [];
82
+
83
+ get [Symbol.toStringTag]() {
84
+ return this.constructor.name;
85
+ }
86
+
87
+ constructor(input) {
88
+ if (input === Object(input)
89
+ && (input?.constructor === Object || Reflect.has(input, Symbol.iterator))) {
90
+
91
+ if (input.constructor !== Object) {
92
+ input = Array.from(input);
93
+ }
94
+
95
+ if (Array.isArray(input)) {
96
+ if (!input.every((it) => Array.isArray(it))) {
97
+ throw new TypeError(`Failed to construct '${
98
+ this[Symbol.toStringTag]
99
+ }': The provided value cannot be converted to a sequence.`);
100
+ } else if (!input.every((it) => it.length === 2)) {
101
+ throw new TypeError(`Failed to construct '${
102
+ this[Symbol.toStringTag]
103
+ }': Sequence initializer must only contain pair elements.`);
104
+ }
105
+ }
106
+
107
+ if (input.constructor === Object) {
108
+ input = Object.entries(input);
109
+ }
110
+
111
+ input.forEach(([key, value]) => this.append(key, value));
112
+ }
113
+ }
114
+
115
+ #ensureArgs(args, expected, method) {
116
+ if (args.length < expected) {
117
+ throw new TypeError(`Failed to execute '${ method }' on '${
118
+ this[Symbol.toStringTag]
119
+ }': ${ expected } arguments required, but only ${ args.length } present.`);
120
+ }
121
+
122
+ if ([
123
+ 'append',
124
+ 'set',
125
+ ].includes(method)) {
126
+ if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
127
+ throw new TypeError(`Failed to execute '${ method }' on '${
128
+ this[Symbol.toStringTag]
129
+ }': parameter ${ expected } is not of type 'Blob', 'File' or async iterable.`);
130
+ }
131
+ }
132
+
133
+ if (method === 'forEach') {
134
+ if (args[0]?.constructor !== Function) {
135
+ throw new TypeError(`Failed to execute '${ method }' on '${
136
+ this[Symbol.toStringTag]
137
+ }': parameter ${ expected } is not of type 'Function'.`);
138
+ }
139
+ }
140
+ }
141
+
142
+ append(...args) {
143
+ this.#ensureArgs(args, 2, 'append');
144
+ this.#entries.push(this.constructor.#enfoldEntry(...args));
145
+ }
146
+
147
+ delete(...args) {
148
+ this.#ensureArgs(args, 1, 'delete');
149
+ const name = toUSVString(args[0]);
150
+
151
+ this.#entries = this.#entries.filter((it) => it.name !== name);
152
+ }
153
+
154
+ forEach(...args) {
155
+ this.#ensureArgs(args, 1, 'forEach');
156
+ const [callback, thisArg] = args;
157
+
158
+ for (const entry of this) {
159
+ Reflect.apply(callback, thisArg, [
160
+ ...(entry.reverse()),
161
+ this,
162
+ ]);
163
+ }
164
+ }
165
+
166
+ get(...args) {
167
+ this.#ensureArgs(args, 1, 'get');
168
+ const name = toUSVString(args[0]);
169
+
170
+ return (this.#entries.find((it) => it.name === name) ?? {}).value ?? null;
171
+ }
172
+
173
+ getAll(...args) {
174
+ this.#ensureArgs(args, 1, 'getAll');
175
+ const name = toUSVString(args[0]);
176
+
177
+ return this.#entries.filter((it) => it.name === name).map((it) => it.value);
178
+ }
179
+
180
+ has(...args) {
181
+ this.#ensureArgs(args, 1, 'has');
182
+ const name = toUSVString(args[0]);
183
+
184
+ return !!this.#entries.find((it) => it.name === name);
185
+ }
186
+
187
+ set(...args) {
188
+ this.#ensureArgs(args, 2, 'set');
189
+ const entry = this.constructor.#enfoldEntry(...args);
190
+ const idx = this.#entries.findIndex((it) => it.name === entry.name);
191
+
192
+ if (idx !== -1) {
193
+ this.#entries.splice(idx, 1, entry);
194
+ } else {
195
+ this.#entries.push(entry);
196
+ }
197
+ }
198
+
199
+ * entries() {
200
+ for (const { name, value } of this.#entries) {
201
+ yield [
202
+ name,
203
+ value,
204
+ ];
205
+ }
206
+ }
207
+
208
+ * keys() {
209
+ for (const [name] of this) {
210
+ yield name;
211
+ }
212
+ }
213
+
214
+ * values() {
215
+ for (const [, value] of this) {
216
+ yield value;
217
+ }
218
+ }
219
+
220
+ [Symbol.iterator]() {
221
+ return this.entries();
222
+ }
223
+
224
+ }
package/src/helpers.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Blob } from 'buffer';
2
- import { globalAgent } from 'http';
3
2
  import http2 from 'http2';
4
3
  import {
5
4
  PassThrough,
@@ -11,6 +10,7 @@ import {
11
10
  } from 'util';
12
11
  import zlib from 'zlib';
13
12
  import { Cookies } from './cookies.mjs';
13
+ import { TimeoutError } from './errors.mjs';
14
14
  import { File } from './file.mjs';
15
15
  import { FormData } from './formdata.mjs';
16
16
  import {
@@ -32,6 +32,7 @@ const {
32
32
  HTTP2_HEADER_METHOD,
33
33
  HTTP2_HEADER_PATH,
34
34
  HTTP2_HEADER_SCHEME,
35
+ HTTP2_HEADER_STATUS,
35
36
  HTTP2_METHOD_GET,
36
37
  HTTP2_METHOD_HEAD,
37
38
  } = http2.constants;
@@ -43,6 +44,48 @@ const gunzip = promisify(zlib.gunzip);
43
44
  const deflate = promisify(zlib.deflate);
44
45
  const inflate = promisify(zlib.inflate);
45
46
 
47
+ export const admix = (res, headers, options) => {
48
+ const { h2 } = options;
49
+
50
+ if (h2) {
51
+ Reflect.defineProperty(res, 'headers', {
52
+ enumerable: true,
53
+ value: headers,
54
+ });
55
+
56
+ Reflect.defineProperty(res, 'httpVersion', {
57
+ enumerable: true,
58
+ value: `${ h2 + 1 }.0`,
59
+ });
60
+
61
+ Reflect.defineProperty(res, 'statusCode', {
62
+ enumerable: true,
63
+ value: headers[HTTP2_HEADER_STATUS],
64
+ });
65
+ }
66
+
67
+ Reflect.defineProperty(res, 'ok', {
68
+ enumerable: true,
69
+ value: /^2\d{2}$/.test(res.statusCode),
70
+ });
71
+
72
+ Reflect.defineProperty(res, 'redirected', {
73
+ enumerable: true,
74
+ value: !!options.redirected,
75
+ });
76
+ };
77
+
78
+ export const affix = (client, req, options) => {
79
+ req.once('end', () => client?.close());
80
+ req.once('timeout', () => req.destroy(new TimeoutError(`Timed out after ${ options.timeout } ms.`)));
81
+ req.once('trailers', (trailers) => {
82
+ Reflect.defineProperty(req, 'trailers', {
83
+ enumerable: true,
84
+ value: trailers,
85
+ });
86
+ });
87
+ };
88
+
46
89
  export const compress = (buf, encoding, { async = false } = {}) => {
47
90
  encoding &&= encoding.match(/(?<encoding>\bbr\b|\bdeflate\b|\bgzip\b)/i)?.groups.encoding.toLowerCase();
48
91
  const compressor = {
@@ -119,65 +162,7 @@ export const merge = (target = {}, ...rest) => {
119
162
  return target;
120
163
  };
121
164
 
122
- export const preflight = (options) => {
123
- const url = options.url = new URL(options.url);
124
- const { cookies, h2 = false, method = HTTP2_METHOD_GET, headers, redirected } = options;
125
-
126
- if (!h2) {
127
- options.agent ??= url.protocol === 'http:' ? globalAgent : void 0;
128
- } else {
129
- options.endStream = [
130
- HTTP2_METHOD_GET,
131
- HTTP2_METHOD_HEAD,
132
- ].includes(method);
133
- }
134
-
135
- if (cookies !== false) {
136
- let cookie = Cookies.jar.get(url.origin);
137
-
138
- if (cookies === Object(cookies) && !redirected) {
139
- if (cookie) {
140
- new Cookies(cookies).forEach(function (val, key) {
141
- this.set(key, val);
142
- }, cookie);
143
- } else {
144
- cookie = new Cookies(cookies);
145
- Cookies.jar.set(url.origin, cookie);
146
- }
147
- }
148
-
149
- options.headers = {
150
- ...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
151
- ...headers,
152
- };
153
- }
154
-
155
- options.digest ??= true;
156
- options.follow ??= 20;
157
- options.h2 ??= h2;
158
- options.headers = {
159
- [HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
160
- [HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
161
- ...Object.entries(options.headers ?? {})
162
- .reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
163
- ...h2 && {
164
- [HTTP2_HEADER_AUTHORITY]: url.host,
165
- [HTTP2_HEADER_METHOD]: method,
166
- [HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
167
- [HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
168
- },
169
- };
170
-
171
- options.method ??= method;
172
- options.parse ??= true;
173
- options.redirect ??= 'follow';
174
- options.redirected ??= false;
175
- options.thenable ??= false;
176
-
177
- return options;
178
- };
179
-
180
- export const premix = (res, { digest = false, parse = false } = {}) => {
165
+ export const mixin = (res, { digest = false, parse = false } = {}) => {
181
166
  if (!digest) {
182
167
  Object.defineProperties(res, {
183
168
  arrayBuffer: {
@@ -217,7 +202,7 @@ export const premix = (res, { digest = false, parse = false } = {}) => {
217
202
  enumerable: true,
218
203
  value: async function () {
219
204
  if (this.bodyUsed) {
220
- throw new TypeError('Response stream already read');
205
+ throw new TypeError('Response stream already read.');
221
206
  }
222
207
 
223
208
  let spool = [];
@@ -265,6 +250,76 @@ export const premix = (res, { digest = false, parse = false } = {}) => {
265
250
  });
266
251
  };
267
252
 
253
+ export const preflight = (options) => {
254
+ const url = options.url = new URL(options.url);
255
+ const { cookies, h2 = false, method = HTTP2_METHOD_GET, headers, redirected } = options;
256
+
257
+ if (h2) {
258
+ options.endStream = [
259
+ HTTP2_METHOD_GET,
260
+ HTTP2_METHOD_HEAD,
261
+ ].includes(method);
262
+ }
263
+
264
+ if (cookies !== false) {
265
+ let cookie = Cookies.jar.get(url.origin);
266
+
267
+ if (cookies === Object(cookies) && !redirected) {
268
+ if (cookie) {
269
+ new Cookies(cookies).forEach(function (val, key) {
270
+ this.set(key, val);
271
+ }, cookie);
272
+ } else {
273
+ cookie = new Cookies(cookies);
274
+ Cookies.jar.set(url.origin, cookie);
275
+ }
276
+ }
277
+
278
+ options.headers = {
279
+ ...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
280
+ ...headers,
281
+ };
282
+ }
283
+
284
+ options.digest ??= true;
285
+ options.follow ??= 20;
286
+ options.h2 ??= h2;
287
+ options.headers = {
288
+ [HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
289
+ [HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
290
+ ...Object.entries(options.headers ?? {})
291
+ .reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
292
+ ...h2 && {
293
+ [HTTP2_HEADER_AUTHORITY]: url.host,
294
+ [HTTP2_HEADER_METHOD]: method,
295
+ [HTTP2_HEADER_PATH]: `${ url.pathname }${ url.search }`,
296
+ [HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, ''),
297
+ },
298
+ };
299
+
300
+ options.method ??= method;
301
+ options.parse ??= true;
302
+ options.redirect ??= redirects.follow;
303
+
304
+ if (!Object.values(redirects).includes(options.redirect)) {
305
+ options.createConnection?.().destroy();
306
+ throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${
307
+ options.redirect
308
+ }' is not a valid enum value.`);
309
+ }
310
+
311
+ options.redirected ??= false;
312
+ options.thenable ??= false;
313
+
314
+ return options;
315
+ };
316
+
317
+ export const redirects = {
318
+ error: 'error',
319
+ follow: 'follow',
320
+ manual: 'manual',
321
+ };
322
+
268
323
  export async function* tap(value) {
269
324
  if (Reflect.has(value, Symbol.asyncIterator)) {
270
325
  yield* value;