rekwest 7.2.4 → 7.2.5
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 +61 -55
- package/dist/ackn.cjs +3 -3
- package/dist/formdata.cjs +59 -60
- package/dist/transfer.cjs +3 -5
- package/dist/transform.cjs +3 -3
- package/dist/utils.cjs +18 -21
- package/package.json +6 -5
- package/src/ackn.js +3 -3
- package/src/formdata.js +80 -89
- package/src/transfer.js +1 -5
- package/src/transform.js +8 -5
- package/src/utils.js +13 -24
package/README.md
CHANGED
|
@@ -48,10 +48,11 @@ 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
|
-
/**
|
|
51
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // Enables: body encoding
|
|
52
|
+
/**
|
|
53
|
+
* [HTTP2_HEADER_CONTENT_TYPE] is undue for
|
|
53
54
|
* Array/Blob/File/FormData/Object/URLSearchParams body types
|
|
54
|
-
* and will be set automatically, with an option to override it here
|
|
55
|
+
* and will be set automatically, with an option to override it here.
|
|
55
56
|
*/
|
|
56
57
|
},
|
|
57
58
|
method: HTTP2_METHOD_POST,
|
|
@@ -82,16 +83,21 @@ const {
|
|
|
82
83
|
|
|
83
84
|
const blob = new Blob(['bits']);
|
|
84
85
|
const file = new File(['bits'], 'file.xyz');
|
|
85
|
-
const
|
|
86
|
+
const rbl = Readable.from('bits');
|
|
87
|
+
const rds = ReadableStream.from('bits');
|
|
86
88
|
|
|
87
89
|
const fd = new FormData({
|
|
88
|
-
aux: Date
|
|
90
|
+
aux: new Date(), // Either [[key, value]] or kv sequenceable
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
fd.append('celestial', 'payload');
|
|
92
94
|
fd.append('blob', blob, 'blob.xyz');
|
|
93
95
|
fd.append('file', file);
|
|
94
|
-
fd.append('
|
|
96
|
+
fd.append('rbl', rbl, 'rbl.xyz');
|
|
97
|
+
fd.append('rds', rds, 'rds.xyz');
|
|
98
|
+
/**
|
|
99
|
+
* Streamable entries are consumed on request submittion.
|
|
100
|
+
*/
|
|
95
101
|
|
|
96
102
|
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
97
103
|
|
|
@@ -99,7 +105,7 @@ const res = await rekwest(url, {
|
|
|
99
105
|
body: fd,
|
|
100
106
|
headers: {
|
|
101
107
|
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
102
|
-
[HTTP2_HEADER_CONTENT_ENCODING]: 'zstd',
|
|
108
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'zstd', // Enables: body encoding
|
|
103
109
|
},
|
|
104
110
|
method: HTTP2_METHOD_POST,
|
|
105
111
|
});
|
|
@@ -113,70 +119,70 @@ console.log(res.body);
|
|
|
113
119
|
|
|
114
120
|
#### `rekwest(url[, options])`
|
|
115
121
|
|
|
116
|
-
* `url` **{string | URL}** The URL to send the request to
|
|
122
|
+
* `url` **{string | URL}** The URL to send the request to.
|
|
117
123
|
* `options` **{Object}**
|
|
118
124
|
Extends [http(s).RequestOptions](https://nodejs.org/api/https.html#httpsrequesturl-options-callback) along with
|
|
119
125
|
extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2connectauthority-options-listener)
|
|
120
126
|
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options)
|
|
121
127
|
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)
|
|
122
|
-
for HTTP/2 attunes
|
|
123
|
-
* `allowDowngrade` **{boolean}** `Default: false` Controls whether `https:` redirects to `http:` are allowed
|
|
124
|
-
* `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL
|
|
128
|
+
for HTTP/2 attunes.
|
|
129
|
+
* `allowDowngrade` **{boolean}** `Default: false` Controls whether `https:` redirects to `http:` are allowed.
|
|
130
|
+
* `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL.
|
|
125
131
|
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
|
|
126
132
|
FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
|
|
127
|
-
with the request
|
|
133
|
+
with the request.
|
|
128
134
|
* `bufferBody` **{boolean}** `Default: false` Toggles the buffering of the streamable request bodies for redirects and
|
|
129
|
-
retries
|
|
135
|
+
retries.
|
|
130
136
|
* `cookies` **{boolean | string | string[] | [k, v][] | Cookies | Object | URLSearchParams}** `Default: true` The
|
|
131
137
|
cookies to add to the request. Manually set `cookie` header to override.
|
|
132
|
-
* `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache
|
|
138
|
+
* `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache.
|
|
133
139
|
* `credentials` **{include | omit | same-origin}** `Default: same-origin` Controls credentials in case of cross-origin
|
|
134
|
-
redirects
|
|
135
|
-
* `decodersOptions` **{Object}** Configures decoders options, e.g.: `brotli`, `zlib`, `zstd
|
|
136
|
-
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or add a mixin
|
|
137
|
-
* `encodersOptions` **{Object}** Configures encoders options, e.g.: `brotli`, `zlib`, `zstd
|
|
138
|
-
* `follow` **{number}** `Default: 20` The number of redirects to follow
|
|
139
|
-
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
|
|
140
|
-
* `headers` **{Object}** The headers to add to the request
|
|
141
|
-
* `params` **{Object}** The search params to add to the `url
|
|
142
|
-
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or return a buffer
|
|
143
|
-
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
|
|
144
|
-
* `retry` **{Object}** Represents the retry options
|
|
145
|
-
* `attempts` **{number}** `Default: 0` The number of retry attempts
|
|
140
|
+
redirects.
|
|
141
|
+
* `decodersOptions` **{Object}** Configures decoders options, e.g.: `brotli`, `zlib`, `zstd`.
|
|
142
|
+
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or add a mixin.
|
|
143
|
+
* `encodersOptions` **{Object}** Configures encoders options, e.g.: `brotli`, `zlib`, `zstd`.
|
|
144
|
+
* `follow` **{number}** `Default: 20` The number of redirects to follow.
|
|
145
|
+
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol.
|
|
146
|
+
* `headers` **{Object}** The headers to add to the request.
|
|
147
|
+
* `params` **{Object}** The search params to add to the `url`.
|
|
148
|
+
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or return a buffer.
|
|
149
|
+
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows.
|
|
150
|
+
* `retry` **{Object}** Represents the retry options.
|
|
151
|
+
* `attempts` **{number}** `Default: 0` The number of retry attempts.
|
|
146
152
|
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
|
|
147
153
|
The backoff strategy uses a log-uniform algorithm. To fix the interval, set the value to `interval * 1`.
|
|
148
154
|
* `errorCodes` **{string[]}**
|
|
149
155
|
`Default: ['ECONNREFUSED', 'ECONNRESET', 'EHOSTDOWN', 'EHOSTUNREACH', 'ENETDOWN', 'ENETUNREACH', 'ENOTFOUND', 'ERR_HTTP2_STREAM_ERROR']`
|
|
150
|
-
The list of error codes to retry on
|
|
151
|
-
* `interval` **{number}** `Default: 1e3` The initial retry interval
|
|
152
|
-
* `maxRetryAfter` **{number}** `Default: 3e5` The maximum `retry-after` limit in milliseconds
|
|
153
|
-
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
|
|
154
|
-
* `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on
|
|
155
|
-
* `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL
|
|
156
|
-
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions
|
|
157
|
-
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
|
|
158
|
-
* `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes within the URL
|
|
156
|
+
The list of error codes to retry on.
|
|
157
|
+
* `interval` **{number}** `Default: 1e3` The initial retry interval.
|
|
158
|
+
* `maxRetryAfter` **{number}** `Default: 3e5` The maximum `retry-after` limit in milliseconds.
|
|
159
|
+
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness.
|
|
160
|
+
* `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on.
|
|
161
|
+
* `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL.
|
|
162
|
+
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions.
|
|
163
|
+
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination.
|
|
164
|
+
* `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes within the URL.
|
|
159
165
|
* **Returns:** Promise that resolves to
|
|
160
166
|
extended [http.IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage)
|
|
161
167
|
or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#class-clienthttp2stream) which is respectively
|
|
162
|
-
readable and duplex streams
|
|
168
|
+
readable and duplex streams.
|
|
163
169
|
* if `digest: true` & `parse: true`
|
|
164
|
-
* `body` **{string | Array | Buffer | Object}** The body based on its content type
|
|
170
|
+
* `body` **{string | Array | Buffer | Object}** The body based on its content type.
|
|
165
171
|
* if `digest: false`
|
|
166
|
-
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer
|
|
167
|
-
* `blob` **{AsyncFunction}** Reads the response and returns **Blob
|
|
168
|
-
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false
|
|
169
|
-
* `bytes` **{AsyncFunction}** Reads the response and returns **Uint8Array
|
|
170
|
-
* `json` **{AsyncFunction}** Reads the response and returns **Object
|
|
171
|
-
* `text` **{AsyncFunction}** Reads the response and returns **String
|
|
172
|
-
* `bodyUsed` **{boolean}** Indicates whether the response was read or not
|
|
173
|
-
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response
|
|
174
|
-
* `headers` **{Object}** The headers received with the response
|
|
175
|
-
* `httpVersion` **{string}** Indicates a protocol version negotiated with the server
|
|
176
|
-
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
|
|
177
|
-
* `redirected` **{boolean}** Indicates if the response is the result of a redirect
|
|
178
|
-
* `statusCode` **{number}** Indicates the status code of the response
|
|
179
|
-
* `trailers` **{undefined | Object}** The trailer headers received with the response
|
|
172
|
+
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**.
|
|
173
|
+
* `blob` **{AsyncFunction}** Reads the response and returns **Blob**.
|
|
174
|
+
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`.
|
|
175
|
+
* `bytes` **{AsyncFunction}** Reads the response and returns **Uint8Array**.
|
|
176
|
+
* `json` **{AsyncFunction}** Reads the response and returns **Object**.
|
|
177
|
+
* `text` **{AsyncFunction}** Reads the response and returns **String**.
|
|
178
|
+
* `bodyUsed` **{boolean}** Indicates whether the response was read or not.
|
|
179
|
+
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response.
|
|
180
|
+
* `headers` **{Object}** The headers received with the response.
|
|
181
|
+
* `httpVersion` **{string}** Indicates a protocol version negotiated with the server.
|
|
182
|
+
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**).
|
|
183
|
+
* `redirected` **{boolean}** Indicates if the response is the result of a redirect.
|
|
184
|
+
* `statusCode` **{number}** Indicates the status code of the response.
|
|
185
|
+
* `trailers` **{undefined | Object}** The trailer headers received with the response.
|
|
180
186
|
|
|
181
187
|
---
|
|
182
188
|
|
|
@@ -204,7 +210,7 @@ const rk = rekwest.extend({
|
|
|
204
210
|
const params = {
|
|
205
211
|
id: '[uid]',
|
|
206
212
|
signature: '[code]',
|
|
207
|
-
variant: '
|
|
213
|
+
variant: '[any]',
|
|
208
214
|
};
|
|
209
215
|
const signal = AbortSignal.timeout(3e4);
|
|
210
216
|
const url = '/somewhat/endpoint';
|
|
@@ -225,9 +231,9 @@ console.log(res.body);
|
|
|
225
231
|
|
|
226
232
|
The method with limited functionality to use with streams and/or pipes.
|
|
227
233
|
|
|
228
|
-
* No automata (redirects & retries)
|
|
229
|
-
* Pass `h2: true` in options to use HTTP/2 protocol
|
|
230
|
-
* Use `ackn({ url: URL })` method in advance to check the available protocols
|
|
234
|
+
* No automata (redirects & retries).
|
|
235
|
+
* Pass `h2: true` in options to use HTTP/2 protocol.
|
|
236
|
+
* Use `ackn({ url: URL })` method in advance to check the available protocols.
|
|
231
237
|
|
|
232
238
|
```javascript
|
|
233
239
|
import fs from 'node:fs';
|
package/dist/ackn.cjs
CHANGED
|
@@ -27,11 +27,11 @@ const ackn = options => new Promise((resolve, reject) => {
|
|
|
27
27
|
createConnection() {
|
|
28
28
|
return socket;
|
|
29
29
|
},
|
|
30
|
-
h2: /
|
|
30
|
+
h2: /\bh2\b/i.test(alpnProtocol),
|
|
31
31
|
protocol: url.protocol
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
|
-
socket.
|
|
35
|
-
socket.
|
|
34
|
+
socket.once('error', reject);
|
|
35
|
+
socket.once('timeout', reject);
|
|
36
36
|
});
|
|
37
37
|
exports.ackn = ackn;
|
package/dist/formdata.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.FormData = void 0;
|
|
6
|
+
exports.parseFormData = exports.isFormData = exports.fdToAsyncIterable = exports.FormData = void 0;
|
|
7
7
|
var _nodeBuffer = require("node:buffer");
|
|
8
8
|
var _nodeCrypto = require("node:crypto");
|
|
9
9
|
var _nodeHttp = _interopRequireDefault(require("node:http2"));
|
|
@@ -16,40 +16,24 @@ const {
|
|
|
16
16
|
HTTP2_HEADER_CONTENT_TYPE
|
|
17
17
|
} = _nodeHttp.default.constants;
|
|
18
18
|
class FormData {
|
|
19
|
-
static
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
contentType,
|
|
27
|
-
async *[Symbol.asyncIterator]() {
|
|
28
|
-
const encoder = new TextEncoder();
|
|
29
|
-
for (const [name, val] of fd) {
|
|
30
|
-
if (val.constructor === String) {
|
|
31
|
-
yield encoder.encode(`${prefix}; name="${escape(redress(name))}"${CRLF.repeat(2)}${redress(val)}${CRLF}`);
|
|
32
|
-
} else {
|
|
33
|
-
yield encoder.encode(`${prefix}; name="${escape(redress(name))}"${val.name ? `; filename="${escape(val.name)}"` : ''}${CRLF}${HTTP2_HEADER_CONTENT_TYPE}: ${val.type || _mediatypes.APPLICATION_OCTET_STREAM}${CRLF.repeat(2)}`);
|
|
34
|
-
yield* (0, _utils.tap)(val);
|
|
35
|
-
yield new Uint8Array([13, 10]);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
yield encoder.encode(`--${boundary}--`);
|
|
19
|
+
static #ensureArgs(args, expect, method) {
|
|
20
|
+
if (args.length < expect) {
|
|
21
|
+
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': ${expect} arguments required, but only ${args.length} present`);
|
|
22
|
+
}
|
|
23
|
+
if (method === 'forEach') {
|
|
24
|
+
if (args[0]?.constructor !== Function) {
|
|
25
|
+
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expect} is not of type 'Function'`);
|
|
39
26
|
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
static alike(val) {
|
|
43
|
-
return FormData.name === val?.[Symbol.toStringTag];
|
|
27
|
+
}
|
|
44
28
|
}
|
|
45
|
-
static #
|
|
29
|
+
static #formEntry(name, value, filename) {
|
|
46
30
|
name = String(name).toWellFormed();
|
|
47
31
|
filename &&= String(filename).toWellFormed();
|
|
48
|
-
if ((0, _utils.
|
|
49
|
-
filename ??= value.name
|
|
32
|
+
if ((0, _utils.isBlobLike)(value)) {
|
|
33
|
+
filename ??= String(value.name ?? 'blob').toWellFormed();
|
|
50
34
|
value = new _nodeBuffer.File([value], filename, value);
|
|
51
|
-
} else if (
|
|
52
|
-
value.name = filename;
|
|
35
|
+
} else if ((0, _utils.isPipeStream)(value) || (0, _utils.isReadableStream)(value)) {
|
|
36
|
+
value.name = filename ?? 'blob';
|
|
53
37
|
} else {
|
|
54
38
|
value = String(value).toWellFormed();
|
|
55
39
|
}
|
|
@@ -58,9 +42,6 @@ class FormData {
|
|
|
58
42
|
value
|
|
59
43
|
};
|
|
60
44
|
}
|
|
61
|
-
static #ensureInstance(val) {
|
|
62
|
-
return (0, _utils.isFileLike)(val) || Object(val) === val && Reflect.has(val, Symbol.asyncIterator);
|
|
63
|
-
}
|
|
64
45
|
#entries = [];
|
|
65
46
|
get [Symbol.toStringTag]() {
|
|
66
47
|
return this.constructor.name;
|
|
@@ -70,10 +51,10 @@ class FormData {
|
|
|
70
51
|
if (Array.isArray(input)) {
|
|
71
52
|
if (!input.every(it => Array.isArray(it))) {
|
|
72
53
|
throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': The provided value cannot be converted to a sequence`);
|
|
73
|
-
}
|
|
54
|
+
}
|
|
55
|
+
if (!input.every(it => it.length === 2)) {
|
|
74
56
|
throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': Sequence initializer must only contain pair elements`);
|
|
75
57
|
}
|
|
76
|
-
input = Array.from(input);
|
|
77
58
|
} else if (!Reflect.has(input, Symbol.iterator)) {
|
|
78
59
|
input = Object.entries(input);
|
|
79
60
|
}
|
|
@@ -82,35 +63,20 @@ class FormData {
|
|
|
82
63
|
}
|
|
83
64
|
}
|
|
84
65
|
}
|
|
85
|
-
#ensureArgs(args, expected, method) {
|
|
86
|
-
if (args.length < expected) {
|
|
87
|
-
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': ${expected} arguments required, but only ${args.length} present`);
|
|
88
|
-
}
|
|
89
|
-
if (['append', 'set'].includes(method)) {
|
|
90
|
-
if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
|
|
91
|
-
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Blob'`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (method === 'forEach') {
|
|
95
|
-
if (args[0]?.constructor !== Function) {
|
|
96
|
-
throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Function'`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
66
|
append(...args) {
|
|
101
67
|
(0, _utils.brandCheck)(this, FormData);
|
|
102
|
-
this.#ensureArgs(args, 2,
|
|
103
|
-
this.#entries.push(this.constructor.#
|
|
68
|
+
this.constructor.#ensureArgs(args, 2, this.append.name);
|
|
69
|
+
this.#entries.push(this.constructor.#formEntry(...args));
|
|
104
70
|
}
|
|
105
71
|
delete(...args) {
|
|
106
72
|
(0, _utils.brandCheck)(this, FormData);
|
|
107
|
-
this.#ensureArgs(args, 1,
|
|
73
|
+
this.constructor.#ensureArgs(args, 1, this.delete.name);
|
|
108
74
|
const name = String(args[0]).toWellFormed();
|
|
109
75
|
this.#entries = this.#entries.filter(it => it.name !== name);
|
|
110
76
|
}
|
|
111
77
|
forEach(...args) {
|
|
112
78
|
(0, _utils.brandCheck)(this, FormData);
|
|
113
|
-
this.#ensureArgs(args, 1,
|
|
79
|
+
this.constructor.#ensureArgs(args, 1, this.forEach.name);
|
|
114
80
|
const [callback, thisArg] = args;
|
|
115
81
|
for (const entry of this) {
|
|
116
82
|
Reflect.apply(callback, thisArg, [...entry.reverse(), this]);
|
|
@@ -118,26 +84,26 @@ class FormData {
|
|
|
118
84
|
}
|
|
119
85
|
get(...args) {
|
|
120
86
|
(0, _utils.brandCheck)(this, FormData);
|
|
121
|
-
this.#ensureArgs(args, 1,
|
|
87
|
+
this.constructor.#ensureArgs(args, 1, this.get.name);
|
|
122
88
|
const name = String(args[0]).toWellFormed();
|
|
123
89
|
return this.#entries.find(it => it.name === name)?.value ?? null;
|
|
124
90
|
}
|
|
125
91
|
getAll(...args) {
|
|
126
92
|
(0, _utils.brandCheck)(this, FormData);
|
|
127
|
-
this.#ensureArgs(args, 1,
|
|
93
|
+
this.constructor.#ensureArgs(args, 1, this.getAll.name);
|
|
128
94
|
const name = String(args[0]).toWellFormed();
|
|
129
95
|
return this.#entries.filter(it => it.name === name).map(it => it.value);
|
|
130
96
|
}
|
|
131
97
|
has(...args) {
|
|
132
98
|
(0, _utils.brandCheck)(this, FormData);
|
|
133
|
-
this.#ensureArgs(args, 1,
|
|
99
|
+
this.constructor.#ensureArgs(args, 1, this.has.name);
|
|
134
100
|
const name = String(args[0]).toWellFormed();
|
|
135
101
|
return !!this.#entries.find(it => it.name === name);
|
|
136
102
|
}
|
|
137
103
|
set(...args) {
|
|
138
104
|
(0, _utils.brandCheck)(this, FormData);
|
|
139
|
-
this.#ensureArgs(args, 2,
|
|
140
|
-
const entry = this.constructor.#
|
|
105
|
+
this.constructor.#ensureArgs(args, 2, this.set.name);
|
|
106
|
+
const entry = this.constructor.#formEntry(...args);
|
|
141
107
|
const idx = this.#entries.findIndex(it => it.name === entry.name);
|
|
142
108
|
if (idx !== -1) {
|
|
143
109
|
this.#entries.splice(idx, 1, entry);
|
|
@@ -171,4 +137,37 @@ class FormData {
|
|
|
171
137
|
return this.entries();
|
|
172
138
|
}
|
|
173
139
|
}
|
|
174
|
-
exports.FormData = FormData;
|
|
140
|
+
exports.FormData = FormData;
|
|
141
|
+
const fdToAsyncIterable = fd => {
|
|
142
|
+
const boundary = (0, _nodeCrypto.randomBytes)(32).toString('hex');
|
|
143
|
+
const contentType = `${_mediatypes.MULTIPART_FORM_DATA}; boundary=${boundary}`;
|
|
144
|
+
const prefix = `--${boundary}${CRLF}${HTTP2_HEADER_CONTENT_DISPOSITION}: form-data`;
|
|
145
|
+
const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
|
|
146
|
+
const normalize = str => str.replace(/\r?\n|\r/g, CRLF);
|
|
147
|
+
return {
|
|
148
|
+
contentType,
|
|
149
|
+
async *[Symbol.asyncIterator]() {
|
|
150
|
+
const encoder = new TextEncoder();
|
|
151
|
+
for (const [name, val] of fd) {
|
|
152
|
+
if (val.constructor === String) {
|
|
153
|
+
yield encoder.encode(`${prefix}; name="${escape(normalize(name))}"${CRLF.repeat(2)}${normalize(val)}${CRLF}`);
|
|
154
|
+
} else {
|
|
155
|
+
yield encoder.encode(`${prefix}; name="${escape(normalize(name))}"${val.name ? `; filename="${escape(val.name)}"` : ''}${CRLF}${HTTP2_HEADER_CONTENT_TYPE}: ${val.type || _mediatypes.APPLICATION_OCTET_STREAM}${CRLF.repeat(2)}`);
|
|
156
|
+
yield* (0, _utils.tap)(val);
|
|
157
|
+
yield new Uint8Array([13, 10]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
yield encoder.encode(`--${boundary}--${CRLF}`);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
exports.fdToAsyncIterable = fdToAsyncIterable;
|
|
165
|
+
const isFormData = val => FormData.name === val?.[Symbol.toStringTag];
|
|
166
|
+
exports.isFormData = isFormData;
|
|
167
|
+
const parseFormData = str => {
|
|
168
|
+
const rex = /^-+[^\r\n]+\r?\ncontent-disposition:\s*form-data;\s*name="(?<name>[^"]+)"(?:;\s*filename="(?<filename>[^"]+)")?(?:\r?\n[^\r\n:]+:[^\r\n]*)*\r?\n\r?\n(?<content>.*?)(?=\r?\n-+[^\r\n]+)/gims;
|
|
169
|
+
return [...str.matchAll(rex)].map(({
|
|
170
|
+
groups
|
|
171
|
+
}) => structuredClone(groups));
|
|
172
|
+
};
|
|
173
|
+
exports.parseFormData = parseFormData;
|
package/dist/transfer.cjs
CHANGED
|
@@ -53,11 +53,9 @@ const transfer = async options => {
|
|
|
53
53
|
} = url.protocol === 'http:' ? _nodeHttp.default : _nodeHttps.default;
|
|
54
54
|
req = request(url, options);
|
|
55
55
|
}
|
|
56
|
-
(0, _utils.snoop)(client, req, options
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
req.once('frameError', reject);
|
|
60
|
-
req.once('goaway', reject);
|
|
56
|
+
(0, _utils.snoop)(client, req, options, {
|
|
57
|
+
reject
|
|
58
|
+
});
|
|
61
59
|
req.once('response', res => (0, _postflight.postflight)(req, res, options, {
|
|
62
60
|
reject,
|
|
63
61
|
resolve
|
package/dist/transform.cjs
CHANGED
|
@@ -28,7 +28,7 @@ const transform = async options => {
|
|
|
28
28
|
}
|
|
29
29
|
if (!Buffer.isBuffer(body)) {
|
|
30
30
|
switch (true) {
|
|
31
|
-
case (0, _utils.
|
|
31
|
+
case (0, _utils.isBlobLike)(body):
|
|
32
32
|
{
|
|
33
33
|
headers = {
|
|
34
34
|
[HTTP2_HEADER_CONTENT_LENGTH]: body.size,
|
|
@@ -37,9 +37,9 @@ const transform = async options => {
|
|
|
37
37
|
body = body.stream();
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
40
|
-
case _formdata.
|
|
40
|
+
case (0, _formdata.isFormData)(body):
|
|
41
41
|
{
|
|
42
|
-
body = _formdata.
|
|
42
|
+
body = (0, _formdata.fdToAsyncIterable)(body);
|
|
43
43
|
headers = {
|
|
44
44
|
[HTTP2_HEADER_CONTENT_TYPE]: body.contentType
|
|
45
45
|
};
|
package/dist/utils.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.stripHeaders = exports.snoop = exports.sameOrigin = exports.normalizeHeaders = exports.normalize = exports.isReadableStream = exports.isPipeStream = exports.isLikelyH2cPrefaceError = exports.
|
|
6
|
+
exports.stripHeaders = exports.snoop = exports.sameOrigin = exports.normalizeHeaders = exports.normalize = exports.isReadableStream = exports.isPipeStream = exports.isLikelyH2cPrefaceError = exports.isBlobLike = exports.dispatch = exports.deepMerge = exports.cloneWith = exports.brandCheck = exports.augment = exports.addSearchParams = void 0;
|
|
7
7
|
exports.tap = tap;
|
|
8
8
|
exports.unwind = exports.toCamelCase = void 0;
|
|
9
9
|
var _nodeBuffer = require("node:buffer");
|
|
@@ -31,9 +31,7 @@ const addSearchParams = (url, params = {}) => {
|
|
|
31
31
|
};
|
|
32
32
|
exports.addSearchParams = addSearchParams;
|
|
33
33
|
const augment = (res, headers, options) => {
|
|
34
|
-
const
|
|
35
|
-
h2
|
|
36
|
-
} = options;
|
|
34
|
+
const h2 = /\bh2c?\b/i.test(res.session?.alpnProtocol);
|
|
37
35
|
if (h2) {
|
|
38
36
|
Reflect.defineProperty(res, 'headers', {
|
|
39
37
|
enumerable: true,
|
|
@@ -92,27 +90,20 @@ const dispatch = (req, {
|
|
|
92
90
|
body
|
|
93
91
|
}) => {
|
|
94
92
|
if ((0, _nodeStream.isReadable)(body)) {
|
|
93
|
+
body.once('error', err => req.session && req.emit('error', err) && req.destroy() || req.destroy(err));
|
|
95
94
|
body.pipe(req);
|
|
96
95
|
} else {
|
|
97
96
|
req.end(body);
|
|
98
97
|
}
|
|
99
98
|
};
|
|
100
99
|
exports.dispatch = dispatch;
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
exports.isFileLike = isFileLike;
|
|
105
|
-
const isLikelyH2cPrefaceError = err => {
|
|
106
|
-
return err.code === 'HPE_INVALID_CONSTANT';
|
|
107
|
-
};
|
|
100
|
+
const isBlobLike = val => val instanceof _nodeBuffer.Blob;
|
|
101
|
+
exports.isBlobLike = isBlobLike;
|
|
102
|
+
const isLikelyH2cPrefaceError = err => err.code === 'HPE_INVALID_CONSTANT';
|
|
108
103
|
exports.isLikelyH2cPrefaceError = isLikelyH2cPrefaceError;
|
|
109
|
-
const isPipeStream = val =>
|
|
110
|
-
return val instanceof _nodeStream.Readable;
|
|
111
|
-
};
|
|
104
|
+
const isPipeStream = val => val instanceof _nodeStream.Readable;
|
|
112
105
|
exports.isPipeStream = isPipeStream;
|
|
113
|
-
const isReadableStream = val =>
|
|
114
|
-
return val instanceof ReadableStream;
|
|
115
|
-
};
|
|
106
|
+
const isReadableStream = val => val instanceof ReadableStream;
|
|
116
107
|
exports.isReadableStream = isReadableStream;
|
|
117
108
|
const normalize = (url, options = {}) => {
|
|
118
109
|
if (!options.redirected) {
|
|
@@ -120,7 +111,7 @@ const normalize = (url, options = {}) => {
|
|
|
120
111
|
}
|
|
121
112
|
return Object.assign(options, {
|
|
122
113
|
headers: normalizeHeaders(options.headers),
|
|
123
|
-
method: options.method
|
|
114
|
+
method: options.method?.toUpperCase(),
|
|
124
115
|
url: addSearchParams(normalizeUrl(new URL(url, options.baseURL), options), options.params)
|
|
125
116
|
});
|
|
126
117
|
};
|
|
@@ -156,9 +147,17 @@ function normalizeUrl(url, {
|
|
|
156
147
|
}
|
|
157
148
|
const sameOrigin = (a, b) => a.origin === b.origin;
|
|
158
149
|
exports.sameOrigin = sameOrigin;
|
|
159
|
-
const snoop = (client, req, options
|
|
150
|
+
const snoop = (client, req, options, {
|
|
151
|
+
reject
|
|
152
|
+
} = {
|
|
153
|
+
reject: () => void 0
|
|
154
|
+
}) => {
|
|
155
|
+
req.once('aborted', reject);
|
|
160
156
|
req.once('close', () => client?.close());
|
|
161
157
|
req.once('end', () => client?.close());
|
|
158
|
+
req.once('error', reject);
|
|
159
|
+
req.once('frameError', reject);
|
|
160
|
+
req.once('goaway', reject);
|
|
162
161
|
req.once('timeout', () => req.destroy(new _errors.TimeoutError(`Timed out after ${options.timeout} ms`)));
|
|
163
162
|
req.once('trailers', trailers => {
|
|
164
163
|
Reflect.defineProperty(req, 'trailers', {
|
|
@@ -178,8 +177,6 @@ async function* tap(val) {
|
|
|
178
177
|
yield* val;
|
|
179
178
|
} else if (val.stream) {
|
|
180
179
|
yield* val.stream();
|
|
181
|
-
} else {
|
|
182
|
-
yield await val.arrayBuffer();
|
|
183
180
|
}
|
|
184
181
|
}
|
|
185
182
|
const toCamelCase = str => str?.toLowerCase().replace(/\p{Punctuation}.|\p{White_Space}./gu, val => val.replace(/\p{Punctuation}+|\p{White_Space}+/gu, '').toUpperCase());
|
package/package.json
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
"@babel/cli": "^7.28.6",
|
|
13
13
|
"@babel/core": "^7.29.0",
|
|
14
14
|
"@babel/preset-env": "^7.29.0",
|
|
15
|
+
"@eslint/markdown": "^7.5.1",
|
|
15
16
|
"c8": "^10.1.3",
|
|
16
|
-
"eslint": "^10.0.
|
|
17
|
-
"eslint-config-ultra-refined": "^4.
|
|
17
|
+
"eslint": "^10.0.1",
|
|
18
|
+
"eslint-config-ultra-refined": "^4.1.2",
|
|
18
19
|
"mocha": "^11.7.5"
|
|
19
20
|
},
|
|
20
21
|
"engines": {
|
|
@@ -60,16 +61,16 @@
|
|
|
60
61
|
"url": "git+https://github.com/bricss/rekwest.git"
|
|
61
62
|
},
|
|
62
63
|
"scripts": {
|
|
63
|
-
"build": "rm -rf dist && npx babel src --out-dir dist --out-file-extension .cjs",
|
|
64
|
+
"build": "rm -rf dist && npx babel src --out-dir dist --out-file-extension .cjs && sh misc.sh",
|
|
64
65
|
"cert:gen": "openssl req -days 365 -keyout localhost.key -newkey ec -nodes -pkeyopt ec_paramgen_curve:prime256v1 -subj //SKIP=1/CN=localhost -out localhost.cert -x509",
|
|
65
66
|
"cert:ken": "openssl x509 -in localhost.cert -noout -text",
|
|
66
67
|
"lint": "eslint --concurrency=auto",
|
|
67
|
-
"prepack": "npm run build &&
|
|
68
|
+
"prepack": "npm run build && npm run lint",
|
|
68
69
|
"pretest": "rm -rf coverage && npm run cert:gen",
|
|
69
70
|
"test": "mocha",
|
|
70
71
|
"test:bail": "mocha --bail",
|
|
71
72
|
"test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
|
|
72
73
|
},
|
|
73
74
|
"type": "module",
|
|
74
|
-
"version": "7.2.
|
|
75
|
+
"version": "7.2.5"
|
|
75
76
|
}
|
package/src/ackn.js
CHANGED
|
@@ -23,11 +23,11 @@ export const ackn = (options) => new Promise((resolve, reject) => {
|
|
|
23
23
|
createConnection() {
|
|
24
24
|
return socket;
|
|
25
25
|
},
|
|
26
|
-
h2: /
|
|
26
|
+
h2: /\bh2\b/i.test(alpnProtocol),
|
|
27
27
|
protocol: url.protocol,
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
socket.
|
|
32
|
-
socket.
|
|
31
|
+
socket.once('error', reject);
|
|
32
|
+
socket.once('timeout', reject);
|
|
33
33
|
});
|
package/src/formdata.js
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
} from './mediatypes.js';
|
|
8
8
|
import {
|
|
9
9
|
brandCheck,
|
|
10
|
-
|
|
10
|
+
isBlobLike,
|
|
11
|
+
isPipeStream,
|
|
12
|
+
isReadableStream,
|
|
11
13
|
tap,
|
|
12
14
|
} from './utils.js';
|
|
13
15
|
|
|
@@ -19,58 +21,31 @@ const {
|
|
|
19
21
|
|
|
20
22
|
export class FormData {
|
|
21
23
|
|
|
22
|
-
static
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const redress = (str) => str.replace(/\r?\n|\r/g, CRLF);
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
contentType,
|
|
32
|
-
async* [Symbol.asyncIterator]() {
|
|
33
|
-
const encoder = new TextEncoder();
|
|
34
|
-
|
|
35
|
-
for (const [name, val] of fd) {
|
|
36
|
-
if (val.constructor === String) {
|
|
37
|
-
yield encoder.encode(`${ prefix }; name="${
|
|
38
|
-
escape(redress(name))
|
|
39
|
-
}"${ CRLF.repeat(2) }${ redress(val) }${ CRLF }`);
|
|
40
|
-
} else {
|
|
41
|
-
yield encoder.encode(`${ prefix }; name="${
|
|
42
|
-
escape(redress(name))
|
|
43
|
-
}"${ val.name ? `; filename="${ escape(val.name) }"` : '' }${ CRLF }${
|
|
44
|
-
HTTP2_HEADER_CONTENT_TYPE
|
|
45
|
-
}: ${
|
|
46
|
-
val.type || APPLICATION_OCTET_STREAM
|
|
47
|
-
}${ CRLF.repeat(2) }`);
|
|
48
|
-
yield* tap(val);
|
|
49
|
-
yield new Uint8Array([
|
|
50
|
-
13,
|
|
51
|
-
10,
|
|
52
|
-
]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
yield encoder.encode(`--${ boundary }--`);
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|
|
24
|
+
static #ensureArgs(args, expect, method) {
|
|
25
|
+
if (args.length < expect) {
|
|
26
|
+
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
27
|
+
this[Symbol.toStringTag]
|
|
28
|
+
}': ${ expect } arguments required, but only ${ args.length } present`);
|
|
29
|
+
}
|
|
60
30
|
|
|
61
|
-
|
|
62
|
-
|
|
31
|
+
if (method === 'forEach') {
|
|
32
|
+
if (args[0]?.constructor !== Function) {
|
|
33
|
+
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
34
|
+
this[Symbol.toStringTag]
|
|
35
|
+
}': parameter ${ expect } is not of type 'Function'`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
63
38
|
}
|
|
64
39
|
|
|
65
|
-
static #
|
|
40
|
+
static #formEntry(name, value, filename) {
|
|
66
41
|
name = String(name).toWellFormed();
|
|
67
42
|
filename &&= String(filename).toWellFormed();
|
|
68
43
|
|
|
69
|
-
if (
|
|
70
|
-
filename ??= value.name
|
|
44
|
+
if (isBlobLike(value)) {
|
|
45
|
+
filename ??= String(value.name ?? 'blob').toWellFormed();
|
|
71
46
|
value = new File([value], filename, value);
|
|
72
|
-
} else if (
|
|
73
|
-
value.name = filename;
|
|
47
|
+
} else if (isPipeStream(value) || isReadableStream(value)) {
|
|
48
|
+
value.name = filename ?? 'blob';
|
|
74
49
|
} else {
|
|
75
50
|
value = String(value).toWellFormed();
|
|
76
51
|
}
|
|
@@ -81,10 +56,6 @@ export class FormData {
|
|
|
81
56
|
};
|
|
82
57
|
}
|
|
83
58
|
|
|
84
|
-
static #ensureInstance(val) {
|
|
85
|
-
return isFileLike(val) || (Object(val) === val && Reflect.has(val, Symbol.asyncIterator));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
59
|
#entries = [];
|
|
89
60
|
|
|
90
61
|
get [Symbol.toStringTag]() {
|
|
@@ -98,13 +69,13 @@ export class FormData {
|
|
|
98
69
|
throw new TypeError(`Failed to construct '${
|
|
99
70
|
this[Symbol.toStringTag]
|
|
100
71
|
}': The provided value cannot be converted to a sequence`);
|
|
101
|
-
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!input.every((it) => it.length === 2)) {
|
|
102
75
|
throw new TypeError(`Failed to construct '${
|
|
103
76
|
this[Symbol.toStringTag]
|
|
104
77
|
}': Sequence initializer must only contain pair elements`);
|
|
105
78
|
}
|
|
106
|
-
|
|
107
|
-
input = Array.from(input);
|
|
108
79
|
} else if (!Reflect.has(input, Symbol.iterator)) {
|
|
109
80
|
input = Object.entries(input);
|
|
110
81
|
}
|
|
@@ -115,42 +86,15 @@ export class FormData {
|
|
|
115
86
|
}
|
|
116
87
|
}
|
|
117
88
|
|
|
118
|
-
#ensureArgs(args, expected, method) {
|
|
119
|
-
if (args.length < expected) {
|
|
120
|
-
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
121
|
-
this[Symbol.toStringTag]
|
|
122
|
-
}': ${ expected } arguments required, but only ${ args.length } present`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if ([
|
|
126
|
-
'append',
|
|
127
|
-
'set',
|
|
128
|
-
].includes(method)) {
|
|
129
|
-
if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
|
|
130
|
-
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
131
|
-
this[Symbol.toStringTag]
|
|
132
|
-
}': parameter ${ expected } is not of type 'Blob'`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (method === 'forEach') {
|
|
137
|
-
if (args[0]?.constructor !== Function) {
|
|
138
|
-
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
139
|
-
this[Symbol.toStringTag]
|
|
140
|
-
}': parameter ${ expected } is not of type 'Function'`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
89
|
append(...args) {
|
|
146
90
|
brandCheck(this, FormData);
|
|
147
|
-
this.#ensureArgs(args, 2,
|
|
148
|
-
this.#entries.push(this.constructor.#
|
|
91
|
+
this.constructor.#ensureArgs(args, 2, this.append.name);
|
|
92
|
+
this.#entries.push(this.constructor.#formEntry(...args));
|
|
149
93
|
}
|
|
150
94
|
|
|
151
95
|
delete(...args) {
|
|
152
96
|
brandCheck(this, FormData);
|
|
153
|
-
this.#ensureArgs(args, 1,
|
|
97
|
+
this.constructor.#ensureArgs(args, 1, this.delete.name);
|
|
154
98
|
const name = String(args[0]).toWellFormed();
|
|
155
99
|
|
|
156
100
|
this.#entries = this.#entries.filter((it) => it.name !== name);
|
|
@@ -158,7 +102,7 @@ export class FormData {
|
|
|
158
102
|
|
|
159
103
|
forEach(...args) {
|
|
160
104
|
brandCheck(this, FormData);
|
|
161
|
-
this.#ensureArgs(args, 1,
|
|
105
|
+
this.constructor.#ensureArgs(args, 1, this.forEach.name);
|
|
162
106
|
const [callback, thisArg] = args;
|
|
163
107
|
|
|
164
108
|
for (const entry of this) {
|
|
@@ -171,7 +115,7 @@ export class FormData {
|
|
|
171
115
|
|
|
172
116
|
get(...args) {
|
|
173
117
|
brandCheck(this, FormData);
|
|
174
|
-
this.#ensureArgs(args, 1,
|
|
118
|
+
this.constructor.#ensureArgs(args, 1, this.get.name);
|
|
175
119
|
const name = String(args[0]).toWellFormed();
|
|
176
120
|
|
|
177
121
|
return this.#entries.find((it) => it.name === name)?.value ?? null;
|
|
@@ -179,7 +123,7 @@ export class FormData {
|
|
|
179
123
|
|
|
180
124
|
getAll(...args) {
|
|
181
125
|
brandCheck(this, FormData);
|
|
182
|
-
this.#ensureArgs(args, 1,
|
|
126
|
+
this.constructor.#ensureArgs(args, 1, this.getAll.name);
|
|
183
127
|
const name = String(args[0]).toWellFormed();
|
|
184
128
|
|
|
185
129
|
return this.#entries.filter((it) => it.name === name).map((it) => it.value);
|
|
@@ -187,7 +131,7 @@ export class FormData {
|
|
|
187
131
|
|
|
188
132
|
has(...args) {
|
|
189
133
|
brandCheck(this, FormData);
|
|
190
|
-
this.#ensureArgs(args, 1,
|
|
134
|
+
this.constructor.#ensureArgs(args, 1, this.has.name);
|
|
191
135
|
const name = String(args[0]).toWellFormed();
|
|
192
136
|
|
|
193
137
|
return !!this.#entries.find((it) => it.name === name);
|
|
@@ -195,8 +139,8 @@ export class FormData {
|
|
|
195
139
|
|
|
196
140
|
set(...args) {
|
|
197
141
|
brandCheck(this, FormData);
|
|
198
|
-
this.#ensureArgs(args, 2,
|
|
199
|
-
const entry = this.constructor.#
|
|
142
|
+
this.constructor.#ensureArgs(args, 2, this.set.name);
|
|
143
|
+
const entry = this.constructor.#formEntry(...args);
|
|
200
144
|
const idx = this.#entries.findIndex((it) => it.name === entry.name);
|
|
201
145
|
|
|
202
146
|
if (idx !== -1) {
|
|
@@ -237,3 +181,50 @@ export class FormData {
|
|
|
237
181
|
}
|
|
238
182
|
|
|
239
183
|
}
|
|
184
|
+
|
|
185
|
+
export const fdToAsyncIterable = (fd) => {
|
|
186
|
+
const boundary = randomBytes(32).toString('hex');
|
|
187
|
+
const contentType = `${ MULTIPART_FORM_DATA }; boundary=${ boundary }`;
|
|
188
|
+
const prefix = `--${ boundary }${ CRLF }${ HTTP2_HEADER_CONTENT_DISPOSITION }: form-data`;
|
|
189
|
+
|
|
190
|
+
const escape = (str) => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
|
|
191
|
+
const normalize = (str) => str.replace(/\r?\n|\r/g, CRLF);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
contentType,
|
|
195
|
+
async* [Symbol.asyncIterator]() {
|
|
196
|
+
const encoder = new TextEncoder();
|
|
197
|
+
|
|
198
|
+
for (const [name, val] of fd) {
|
|
199
|
+
if (val.constructor === String) {
|
|
200
|
+
yield encoder.encode(`${ prefix }; name="${
|
|
201
|
+
escape(normalize(name))
|
|
202
|
+
}"${ CRLF.repeat(2) }${ normalize(val) }${ CRLF }`);
|
|
203
|
+
} else {
|
|
204
|
+
yield encoder.encode(`${ prefix }; name="${
|
|
205
|
+
escape(normalize(name))
|
|
206
|
+
}"${ val.name ? `; filename="${ escape(val.name) }"` : '' }${ CRLF }${
|
|
207
|
+
HTTP2_HEADER_CONTENT_TYPE
|
|
208
|
+
}: ${
|
|
209
|
+
val.type || APPLICATION_OCTET_STREAM
|
|
210
|
+
}${ CRLF.repeat(2) }`);
|
|
211
|
+
yield* tap(val);
|
|
212
|
+
yield new Uint8Array([
|
|
213
|
+
13,
|
|
214
|
+
10,
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
yield encoder.encode(`--${ boundary }--${ CRLF }`);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const isFormData = (val) => FormData.name === val?.[Symbol.toStringTag];
|
|
225
|
+
|
|
226
|
+
export const parseFormData = (str) => {
|
|
227
|
+
const rex = /^-+[^\r\n]+\r?\ncontent-disposition:\s*form-data;\s*name="(?<name>[^"]+)"(?:;\s*filename="(?<filename>[^"]+)")?(?:\r?\n[^\r\n:]+:[^\r\n]*)*\r?\n\r?\n(?<content>.*?)(?=\r?\n-+[^\r\n]+)/gims;
|
|
228
|
+
|
|
229
|
+
return [...str.matchAll(rex)].map(({ groups }) => structuredClone(groups));
|
|
230
|
+
};
|
package/src/transfer.js
CHANGED
|
@@ -53,12 +53,8 @@ export const transfer = async (options) => {
|
|
|
53
53
|
req = request(url, options);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
snoop(client, req, options);
|
|
56
|
+
snoop(client, req, options, { reject });
|
|
57
57
|
|
|
58
|
-
req.once('aborted', reject);
|
|
59
|
-
req.once('error', reject);
|
|
60
|
-
req.once('frameError', reject);
|
|
61
|
-
req.once('goaway', reject);
|
|
62
58
|
req.once('response', (res) => postflight(req, res, options, {
|
|
63
59
|
reject, resolve,
|
|
64
60
|
}));
|
package/src/transform.js
CHANGED
|
@@ -6,14 +6,17 @@ import {
|
|
|
6
6
|
import { buffer } from 'node:stream/consumers';
|
|
7
7
|
import { types } from 'node:util';
|
|
8
8
|
import { encode } from './codecs.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
fdToAsyncIterable,
|
|
11
|
+
isFormData,
|
|
12
|
+
} from './formdata.js';
|
|
10
13
|
import {
|
|
11
14
|
APPLICATION_FORM_URLENCODED,
|
|
12
15
|
APPLICATION_JSON,
|
|
13
16
|
APPLICATION_OCTET_STREAM,
|
|
14
17
|
} from './mediatypes.js';
|
|
15
18
|
import {
|
|
16
|
-
|
|
19
|
+
isBlobLike,
|
|
17
20
|
isReadableStream,
|
|
18
21
|
} from './utils.js';
|
|
19
22
|
|
|
@@ -32,7 +35,7 @@ export const transform = async (options) => {
|
|
|
32
35
|
|
|
33
36
|
if (!Buffer.isBuffer(body)) {
|
|
34
37
|
switch (true) {
|
|
35
|
-
case
|
|
38
|
+
case isBlobLike(body): {
|
|
36
39
|
headers = {
|
|
37
40
|
[HTTP2_HEADER_CONTENT_LENGTH]: body.size,
|
|
38
41
|
[HTTP2_HEADER_CONTENT_TYPE]: body.type || APPLICATION_OCTET_STREAM,
|
|
@@ -41,8 +44,8 @@ export const transform = async (options) => {
|
|
|
41
44
|
break;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
case
|
|
45
|
-
body =
|
|
47
|
+
case isFormData(body): {
|
|
48
|
+
body = fdToAsyncIterable(body);
|
|
46
49
|
headers = { [HTTP2_HEADER_CONTENT_TYPE]: body.contentType };
|
|
47
50
|
break;
|
|
48
51
|
}
|
package/src/utils.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Blob,
|
|
3
|
-
File,
|
|
4
|
-
} from 'node:buffer';
|
|
1
|
+
import { Blob } from 'node:buffer';
|
|
5
2
|
import http2 from 'node:http2';
|
|
6
3
|
import {
|
|
7
4
|
isReadable,
|
|
@@ -30,7 +27,7 @@ export const addSearchParams = (url, params = {}) => {
|
|
|
30
27
|
};
|
|
31
28
|
|
|
32
29
|
export const augment = (res, headers, options) => {
|
|
33
|
-
const
|
|
30
|
+
const h2 = /\bh2c?\b/i.test(res.session?.alpnProtocol);
|
|
34
31
|
|
|
35
32
|
if (h2) {
|
|
36
33
|
Reflect.defineProperty(res, 'headers', {
|
|
@@ -96,30 +93,20 @@ export const deepMerge = (target, ...rest) => {
|
|
|
96
93
|
|
|
97
94
|
export const dispatch = (req, { body }) => {
|
|
98
95
|
if (isReadable(body)) {
|
|
96
|
+
body.once('error', (err) => (req.session && req.emit('error', err) && req.destroy()) || req.destroy(err));
|
|
99
97
|
body.pipe(req);
|
|
100
98
|
} else {
|
|
101
99
|
req.end(body);
|
|
102
100
|
}
|
|
103
101
|
};
|
|
104
102
|
|
|
105
|
-
export const
|
|
106
|
-
return [
|
|
107
|
-
Blob,
|
|
108
|
-
File,
|
|
109
|
-
].some((it) => val instanceof it);
|
|
110
|
-
};
|
|
103
|
+
export const isBlobLike = (val) => val instanceof Blob;
|
|
111
104
|
|
|
112
|
-
export const isLikelyH2cPrefaceError = (err) =>
|
|
113
|
-
return err.code === 'HPE_INVALID_CONSTANT';
|
|
114
|
-
};
|
|
105
|
+
export const isLikelyH2cPrefaceError = (err) => err.code === 'HPE_INVALID_CONSTANT';
|
|
115
106
|
|
|
116
|
-
export const isPipeStream = (val) =>
|
|
117
|
-
return val instanceof Readable;
|
|
118
|
-
};
|
|
107
|
+
export const isPipeStream = (val) => val instanceof Readable;
|
|
119
108
|
|
|
120
|
-
export const isReadableStream = (val) =>
|
|
121
|
-
return val instanceof ReadableStream;
|
|
122
|
-
};
|
|
109
|
+
export const isReadableStream = (val) => val instanceof ReadableStream;
|
|
123
110
|
|
|
124
111
|
export const normalize = (url, options = {}) => {
|
|
125
112
|
if (!options.redirected) {
|
|
@@ -128,7 +115,7 @@ export const normalize = (url, options = {}) => {
|
|
|
128
115
|
|
|
129
116
|
return Object.assign(options, {
|
|
130
117
|
headers: normalizeHeaders(options.headers),
|
|
131
|
-
method: options.method
|
|
118
|
+
method: options.method?.toUpperCase(),
|
|
132
119
|
url: addSearchParams(normalizeUrl(new URL(url, options.baseURL), options), options.params),
|
|
133
120
|
});
|
|
134
121
|
};
|
|
@@ -169,9 +156,13 @@ function normalizeUrl(url, { trimTrailingSlashes, stripTrailingSlash } = {}) {
|
|
|
169
156
|
|
|
170
157
|
export const sameOrigin = (a, b) => a.origin === b.origin;
|
|
171
158
|
|
|
172
|
-
export const snoop = (client, req, options) => {
|
|
159
|
+
export const snoop = (client, req, options, { reject } = { reject: () => void 0 }) => {
|
|
160
|
+
req.once('aborted', reject);
|
|
173
161
|
req.once('close', () => client?.close());
|
|
174
162
|
req.once('end', () => client?.close());
|
|
163
|
+
req.once('error', reject);
|
|
164
|
+
req.once('frameError', reject);
|
|
165
|
+
req.once('goaway', reject);
|
|
175
166
|
req.once('timeout', () => req.destroy(new TimeoutError(`Timed out after ${ options.timeout } ms`)));
|
|
176
167
|
req.once('trailers', (trailers) => {
|
|
177
168
|
Reflect.defineProperty(req, 'trailers', {
|
|
@@ -192,8 +183,6 @@ export async function* tap(val) {
|
|
|
192
183
|
yield* val;
|
|
193
184
|
} else if (val.stream) {
|
|
194
185
|
yield* val.stream();
|
|
195
|
-
} else {
|
|
196
|
-
yield await val.arrayBuffer();
|
|
197
186
|
}
|
|
198
187
|
}
|
|
199
188
|
|