undici 5.5.1 → 5.7.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 +8 -1
- package/docs/api/Dispatcher.md +6 -4
- package/docs/api/MockAgent.md +2 -2
- package/index.js +1 -1
- package/lib/api/api-connect.js +3 -19
- package/lib/api/api-request.js +31 -3
- package/lib/client.js +3 -5
- package/lib/core/errors.js +2 -10
- package/lib/core/request.js +2 -2
- package/lib/fetch/body.js +149 -65
- package/lib/fetch/constants.js +12 -0
- package/lib/fetch/file.js +131 -11
- package/lib/fetch/formdata.js +85 -71
- package/lib/fetch/headers.js +226 -81
- package/lib/fetch/index.js +29 -26
- package/lib/fetch/request.js +118 -18
- package/lib/fetch/response.js +111 -35
- package/lib/fetch/util.js +52 -1
- package/lib/fetch/webidl.js +595 -0
- package/lib/handler/redirect.js +1 -1
- package/lib/llhttp/llhttp.wasm +0 -0
- package/lib/llhttp/llhttp.wasm.js +1 -1
- package/lib/llhttp/llhttp_simd.wasm +0 -0
- package/lib/llhttp/llhttp_simd.wasm.js +1 -1
- package/lib/mock/mock-interceptor.js +3 -3
- package/lib/proxy-agent.js +55 -45
- package/package.json +3 -3
- package/types/diagnostics-channel.d.ts +1 -0
- package/types/errors.d.ts +11 -0
- package/types/file.d.ts +8 -3
- package/types/mock-interceptor.d.ts +2 -2
package/README.md
CHANGED
|
@@ -176,7 +176,7 @@ Implements [fetch](https://fetch.spec.whatwg.org/#fetch-method).
|
|
|
176
176
|
* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
|
|
177
177
|
* https://fetch.spec.whatwg.org/#fetch-method
|
|
178
178
|
|
|
179
|
-
Only supported on Node 16.
|
|
179
|
+
Only supported on Node 16.8+.
|
|
180
180
|
|
|
181
181
|
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard.
|
|
182
182
|
We plan to ship breaking changes to this feature until it is out of experimental.
|
|
@@ -283,6 +283,13 @@ const headers = await fetch(url)
|
|
|
283
283
|
.then(res => res.headers)
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
However, if you want to get only headers, it might be better to use `HEAD` request method. Usage of this method will obviate the need for consumption or cancelling of the response body. See [MDN - HTTP - HTTP request methods - HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) for more details.
|
|
287
|
+
|
|
288
|
+
```js
|
|
289
|
+
const headers = await fetch(url, { method: 'HEAD' })
|
|
290
|
+
.then(res => res.headers)
|
|
291
|
+
```
|
|
292
|
+
|
|
286
293
|
##### Forbidden and Safelisted Header Names
|
|
287
294
|
|
|
288
295
|
* https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
|
package/docs/api/Dispatcher.md
CHANGED
|
@@ -461,14 +461,14 @@ Arguments:
|
|
|
461
461
|
* **options** `RequestOptions`
|
|
462
462
|
* **callback** `(error: Error | null, data: ResponseData) => void` (optional)
|
|
463
463
|
|
|
464
|
-
Returns: `void | Promise<ResponseData>` - Only returns a `Promise` if no `callback` argument was passed
|
|
464
|
+
Returns: `void | Promise<ResponseData>` - Only returns a `Promise` if no `callback` argument was passed.
|
|
465
465
|
|
|
466
466
|
#### Parameter: `RequestOptions`
|
|
467
467
|
|
|
468
468
|
Extends: [`DispatchOptions`](#parameter-dispatchoptions)
|
|
469
469
|
|
|
470
|
-
* **opaque** `unknown` (optional) - Default: `null` - Used for passing through context to `ResponseData
|
|
471
|
-
* **signal** `AbortSignal | events.EventEmitter | null` (optional) - Default: `null
|
|
470
|
+
* **opaque** `unknown` (optional) - Default: `null` - Used for passing through context to `ResponseData`.
|
|
471
|
+
* **signal** `AbortSignal | events.EventEmitter | null` (optional) - Default: `null`.
|
|
472
472
|
* **onInfo** `({statusCode: number, headers: Record<string, string | string[]>}) => void | null` (optional) - Default: `null` - Callback collecting all the info headers (HTTP 100-199) received.
|
|
473
473
|
|
|
474
474
|
The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
@@ -476,7 +476,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
|
476
476
|
#### Parameter: `ResponseData`
|
|
477
477
|
|
|
478
478
|
* **statusCode** `number`
|
|
479
|
-
* **headers** `http.IncomingHttpHeaders`
|
|
479
|
+
* **headers** `http.IncomingHttpHeaders` - Note that all header keys are lower-cased, e. g. `content-type`.
|
|
480
480
|
* **body** `stream.Readable` which also implements [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
|
|
481
481
|
* **trailers** `Record<string, string>` - This object starts out
|
|
482
482
|
as empty and will be mutated to contain trailers after `body` has emitted `'end'`.
|
|
@@ -497,6 +497,8 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
|
497
497
|
|
|
498
498
|
- `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default: 262144.
|
|
499
499
|
|
|
500
|
+
Note that body will still be a `Readable` even if it is empty, but attempting to deserialize it with `json()` will result in an exception. Recommended way to ensure there is a body to deserialize is to check if status code is not 204, and `content-type` header starts with `application/json`.
|
|
501
|
+
|
|
500
502
|
#### Example 1 - Basic GET Request
|
|
501
503
|
|
|
502
504
|
```js
|
package/docs/api/MockAgent.md
CHANGED
|
@@ -465,7 +465,7 @@ agent.disableNetConnect()
|
|
|
465
465
|
agent
|
|
466
466
|
.get('https://example.com')
|
|
467
467
|
.intercept({ method: 'GET', path: '/' })
|
|
468
|
-
.reply(200
|
|
468
|
+
.reply(200)
|
|
469
469
|
|
|
470
470
|
const pendingInterceptors = agent.pendingInterceptors()
|
|
471
471
|
// Returns [
|
|
@@ -508,7 +508,7 @@ agent.disableNetConnect()
|
|
|
508
508
|
agent
|
|
509
509
|
.get('https://example.com')
|
|
510
510
|
.intercept({ method: 'GET', path: '/' })
|
|
511
|
-
.reply(200
|
|
511
|
+
.reply(200)
|
|
512
512
|
|
|
513
513
|
agent.assertNoPendingInterceptors()
|
|
514
514
|
// Throws an UndiciError with the following message:
|
package/index.js
CHANGED
|
@@ -80,7 +80,7 @@ function makeDispatcher (fn) {
|
|
|
80
80
|
module.exports.setGlobalDispatcher = setGlobalDispatcher
|
|
81
81
|
module.exports.getGlobalDispatcher = getGlobalDispatcher
|
|
82
82
|
|
|
83
|
-
if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >=
|
|
83
|
+
if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 8)) {
|
|
84
84
|
let fetchImpl = null
|
|
85
85
|
module.exports.fetch = async function fetch (resource) {
|
|
86
86
|
if (!fetchImpl) {
|
package/lib/api/api-connect.js
CHANGED
|
@@ -15,7 +15,7 @@ class ConnectHandler extends AsyncResource {
|
|
|
15
15
|
throw new InvalidArgumentError('invalid callback')
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { signal, opaque, responseHeaders
|
|
18
|
+
const { signal, opaque, responseHeaders } = opts
|
|
19
19
|
|
|
20
20
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
21
21
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -27,7 +27,6 @@ class ConnectHandler extends AsyncResource {
|
|
|
27
27
|
this.responseHeaders = responseHeaders || null
|
|
28
28
|
this.callback = callback
|
|
29
29
|
this.abort = null
|
|
30
|
-
this.httpTunnel = httpTunnel
|
|
31
30
|
|
|
32
31
|
addSignal(this, signal)
|
|
33
32
|
}
|
|
@@ -41,23 +40,8 @@ class ConnectHandler extends AsyncResource {
|
|
|
41
40
|
this.context = context
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
onHeaders (
|
|
45
|
-
|
|
46
|
-
if (this.httpTunnel) {
|
|
47
|
-
const { callback, opaque } = this
|
|
48
|
-
if (statusCode !== 200) {
|
|
49
|
-
if (callback) {
|
|
50
|
-
this.callback = null
|
|
51
|
-
const err = new RequestAbortedError('Proxy response !== 200 when HTTP Tunneling')
|
|
52
|
-
queueMicrotask(() => {
|
|
53
|
-
this.runInAsyncScope(callback, null, err, { opaque })
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
return 1
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
throw new SocketError('bad connect', null)
|
|
60
|
-
}
|
|
43
|
+
onHeaders () {
|
|
44
|
+
throw new SocketError('bad connect', null)
|
|
61
45
|
}
|
|
62
46
|
|
|
63
47
|
onUpgrade (statusCode, rawHeaders, socket) {
|
package/lib/api/api-request.js
CHANGED
|
@@ -84,7 +84,8 @@ class RequestHandler extends AsyncResource {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const parsedHeaders = util.parseHeaders(rawHeaders)
|
|
87
|
-
const
|
|
87
|
+
const contentType = parsedHeaders['content-type']
|
|
88
|
+
const body = new Readable(resume, abort, contentType)
|
|
88
89
|
|
|
89
90
|
this.callback = null
|
|
90
91
|
this.res = body
|
|
@@ -92,8 +93,8 @@ class RequestHandler extends AsyncResource {
|
|
|
92
93
|
|
|
93
94
|
if (callback !== null) {
|
|
94
95
|
if (this.throwOnError && statusCode >= 400) {
|
|
95
|
-
this.runInAsyncScope(
|
|
96
|
-
|
|
96
|
+
this.runInAsyncScope(getResolveErrorBodyCallback, null,
|
|
97
|
+
{ callback, body, contentType, statusCode, statusMessage, headers }
|
|
97
98
|
)
|
|
98
99
|
return
|
|
99
100
|
}
|
|
@@ -152,6 +153,33 @@ class RequestHandler extends AsyncResource {
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
async function getResolveErrorBodyCallback ({ callback, body, contentType, statusCode, statusMessage, headers }) {
|
|
157
|
+
if (statusCode === 204 || !contentType) {
|
|
158
|
+
body.dump()
|
|
159
|
+
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers))
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
if (contentType.startsWith('application/json')) {
|
|
165
|
+
const payload = await body.json()
|
|
166
|
+
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload))
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (contentType.startsWith('text/')) {
|
|
171
|
+
const payload = await body.text()
|
|
172
|
+
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload))
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
// Process in a fallback if error
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
body.dump()
|
|
180
|
+
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers))
|
|
181
|
+
}
|
|
182
|
+
|
|
155
183
|
function request (opts, callback) {
|
|
156
184
|
if (callback === undefined) {
|
|
157
185
|
return new Promise((resolve, reject) => {
|
package/lib/client.js
CHANGED
|
@@ -720,7 +720,7 @@ class Parser {
|
|
|
720
720
|
}
|
|
721
721
|
}
|
|
722
722
|
|
|
723
|
-
if (request.method === 'CONNECT'
|
|
723
|
+
if (request.method === 'CONNECT') {
|
|
724
724
|
assert(client[kRunning] === 1)
|
|
725
725
|
this.upgrade = true
|
|
726
726
|
return 2
|
|
@@ -889,10 +889,8 @@ function onParserTimeout (parser) {
|
|
|
889
889
|
|
|
890
890
|
/* istanbul ignore else */
|
|
891
891
|
if (timeoutType === TIMEOUT_HEADERS) {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
util.destroy(socket, new HeadersTimeoutError())
|
|
895
|
-
}
|
|
892
|
+
assert(!parser.paused, 'cannot be paused while waiting for headers')
|
|
893
|
+
util.destroy(socket, new HeadersTimeoutError())
|
|
896
894
|
} else if (timeoutType === TIMEOUT_BODY) {
|
|
897
895
|
if (!parser.paused) {
|
|
898
896
|
util.destroy(socket, new BodyTimeoutError())
|
package/lib/core/errors.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
class AbortError extends Error {
|
|
4
|
-
constructor () {
|
|
5
|
-
super('The operation was aborted')
|
|
6
|
-
this.code = 'ABORT_ERR'
|
|
7
|
-
this.name = 'AbortError'
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
3
|
class UndiciError extends Error {
|
|
12
4
|
constructor (message) {
|
|
13
5
|
super(message)
|
|
@@ -57,12 +49,13 @@ class BodyTimeoutError extends UndiciError {
|
|
|
57
49
|
}
|
|
58
50
|
|
|
59
51
|
class ResponseStatusCodeError extends UndiciError {
|
|
60
|
-
constructor (message, statusCode, headers) {
|
|
52
|
+
constructor (message, statusCode, headers, body) {
|
|
61
53
|
super(message)
|
|
62
54
|
Error.captureStackTrace(this, ResponseStatusCodeError)
|
|
63
55
|
this.name = 'ResponseStatusCodeError'
|
|
64
56
|
this.message = message || 'Response Status Code Error'
|
|
65
57
|
this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
|
|
58
|
+
this.body = body
|
|
66
59
|
this.status = statusCode
|
|
67
60
|
this.statusCode = statusCode
|
|
68
61
|
this.headers = headers
|
|
@@ -191,7 +184,6 @@ class HTTPParserError extends Error {
|
|
|
191
184
|
}
|
|
192
185
|
|
|
193
186
|
module.exports = {
|
|
194
|
-
AbortError,
|
|
195
187
|
HTTPParserError,
|
|
196
188
|
UndiciError,
|
|
197
189
|
HeadersTimeoutError,
|
package/lib/core/request.js
CHANGED
|
@@ -140,8 +140,8 @@ class Request {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
if (util.isFormDataLike(this.body)) {
|
|
143
|
-
if (nodeMajor < 16 || (nodeMajor === 16 && nodeMinor <
|
|
144
|
-
throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.
|
|
143
|
+
if (nodeMajor < 16 || (nodeMajor === 16 && nodeMinor < 8)) {
|
|
144
|
+
throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.')
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
if (!extractBody) {
|
package/lib/fetch/body.js
CHANGED
|
@@ -4,6 +4,7 @@ const util = require('../core/util')
|
|
|
4
4
|
const { ReadableStreamFrom, toUSVString, isBlobLike } = require('./util')
|
|
5
5
|
const { FormData } = require('./formdata')
|
|
6
6
|
const { kState } = require('./symbols')
|
|
7
|
+
const { webidl } = require('./webidl')
|
|
7
8
|
const { Blob } = require('buffer')
|
|
8
9
|
const { kBodyUsed } = require('../core/symbols')
|
|
9
10
|
const assert = require('assert')
|
|
@@ -14,12 +15,7 @@ const { isUint8Array, isArrayBuffer } = require('util/types')
|
|
|
14
15
|
let ReadableStream
|
|
15
16
|
|
|
16
17
|
async function * blobGen (blob) {
|
|
17
|
-
|
|
18
|
-
yield * blob.stream()
|
|
19
|
-
} else {
|
|
20
|
-
// istanbul ignore next: node < 16.7
|
|
21
|
-
yield await blob.arrayBuffer()
|
|
22
|
-
}
|
|
18
|
+
yield * blob.stream()
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
|
@@ -262,100 +258,188 @@ function cloneBody (body) {
|
|
|
262
258
|
}
|
|
263
259
|
}
|
|
264
260
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
async function * consumeBody (body) {
|
|
262
|
+
if (body) {
|
|
263
|
+
if (isUint8Array(body)) {
|
|
264
|
+
yield body
|
|
265
|
+
} else {
|
|
266
|
+
const stream = body.stream
|
|
268
267
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
} else {
|
|
273
|
-
const stream = this[kState].body.stream
|
|
268
|
+
if (util.isDisturbed(stream)) {
|
|
269
|
+
throw new TypeError('disturbed')
|
|
270
|
+
}
|
|
274
271
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
if (stream.locked) {
|
|
273
|
+
throw new TypeError('locked')
|
|
274
|
+
}
|
|
278
275
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
// Compat.
|
|
277
|
+
stream[kBodyUsed] = true
|
|
278
|
+
|
|
279
|
+
yield * stream
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function bodyMixinMethods (instance) {
|
|
285
|
+
const methods = {
|
|
286
|
+
async blob () {
|
|
287
|
+
if (!(this instanceof instance)) {
|
|
288
|
+
throw new TypeError('Illegal invocation')
|
|
289
|
+
}
|
|
282
290
|
|
|
283
|
-
|
|
284
|
-
stream[kBodyUsed] = true
|
|
291
|
+
const chunks = []
|
|
285
292
|
|
|
286
|
-
|
|
287
|
-
|
|
293
|
+
for await (const chunk of consumeBody(this[kState].body)) {
|
|
294
|
+
// Assemble one final large blob with Uint8Array's can exhaust memory.
|
|
295
|
+
// That's why we create create multiple blob's and using references
|
|
296
|
+
chunks.push(new Blob([chunk]))
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return new Blob(chunks, { type: this.headers.get('Content-Type') || '' })
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
async arrayBuffer () {
|
|
303
|
+
if (!(this instanceof instance)) {
|
|
304
|
+
throw new TypeError('Illegal invocation')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const contentLength = this.headers.get('content-length')
|
|
308
|
+
const encoded = this.headers.has('content-encoding')
|
|
309
|
+
|
|
310
|
+
// if we have content length and no encoding, then we can
|
|
311
|
+
// pre allocate the buffer and just read the data into it
|
|
312
|
+
if (!encoded && contentLength) {
|
|
313
|
+
const buffer = new Uint8Array(contentLength)
|
|
314
|
+
let offset = 0
|
|
315
|
+
|
|
316
|
+
for await (const chunk of consumeBody(this[kState].body)) {
|
|
317
|
+
buffer.set(chunk, offset)
|
|
318
|
+
offset += chunk.length
|
|
288
319
|
}
|
|
320
|
+
|
|
321
|
+
return buffer.buffer
|
|
289
322
|
}
|
|
290
|
-
}
|
|
291
323
|
|
|
292
|
-
|
|
293
|
-
|
|
324
|
+
// if we don't have content length, then we have to allocate 2x the
|
|
325
|
+
// size of the body, once for consumed data, and once for the final buffer
|
|
294
326
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return await blob.arrayBuffer()
|
|
298
|
-
},
|
|
327
|
+
// This could be optimized by using growable ArrayBuffer, but it's not
|
|
328
|
+
// implemented yet. https://github.com/tc39/proposal-resizablearraybuffer
|
|
299
329
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return toUSVString(await blob.text())
|
|
303
|
-
},
|
|
330
|
+
const chunks = []
|
|
331
|
+
let size = 0
|
|
304
332
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
333
|
+
for await (const chunk of consumeBody(this[kState].body)) {
|
|
334
|
+
chunks.push(chunk)
|
|
335
|
+
size += chunk.byteLength
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const buffer = new Uint8Array(size)
|
|
339
|
+
let offset = 0
|
|
308
340
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// If mimeType’s essence is "multipart/form-data", then:
|
|
313
|
-
if (/multipart\/form-data/.test(contentType)) {
|
|
314
|
-
throw new NotSupportedError('multipart/form-data not supported')
|
|
315
|
-
} else if (/application\/x-www-form-urlencoded/.test(contentType)) {
|
|
316
|
-
// Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
|
|
317
|
-
|
|
318
|
-
// 1. Let entries be the result of parsing bytes.
|
|
319
|
-
let entries
|
|
320
|
-
try {
|
|
321
|
-
entries = new URLSearchParams(await this.text())
|
|
322
|
-
} catch (err) {
|
|
323
|
-
// istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
|
|
324
|
-
// 2. If entries is failure, then throw a TypeError.
|
|
325
|
-
throw Object.assign(new TypeError(), { cause: err })
|
|
341
|
+
for (const chunk of chunks) {
|
|
342
|
+
buffer.set(chunk, offset)
|
|
343
|
+
offset += chunk.byteLength
|
|
326
344
|
}
|
|
327
345
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
346
|
+
return buffer.buffer
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
async text () {
|
|
350
|
+
if (!(this instanceof instance)) {
|
|
351
|
+
throw new TypeError('Illegal invocation')
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let result = ''
|
|
355
|
+
const textDecoder = new TextDecoder()
|
|
356
|
+
|
|
357
|
+
for await (const chunk of consumeBody(this[kState].body)) {
|
|
358
|
+
result += textDecoder.decode(chunk, { stream: true })
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// flush
|
|
362
|
+
result += textDecoder.decode()
|
|
363
|
+
|
|
364
|
+
return result
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
async json () {
|
|
368
|
+
if (!(this instanceof instance)) {
|
|
369
|
+
throw new TypeError('Illegal invocation')
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return JSON.parse(await this.text())
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
async formData () {
|
|
376
|
+
if (!(this instanceof instance)) {
|
|
377
|
+
throw new TypeError('Illegal invocation')
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const contentType = this.headers.get('Content-Type')
|
|
381
|
+
|
|
382
|
+
// If mimeType’s essence is "multipart/form-data", then:
|
|
383
|
+
if (/multipart\/form-data/.test(contentType)) {
|
|
384
|
+
throw new NotSupportedError('multipart/form-data not supported')
|
|
385
|
+
} else if (/application\/x-www-form-urlencoded/.test(contentType)) {
|
|
386
|
+
// Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
|
|
387
|
+
|
|
388
|
+
// 1. Let entries be the result of parsing bytes.
|
|
389
|
+
let entries
|
|
390
|
+
try {
|
|
391
|
+
entries = new URLSearchParams(await this.text())
|
|
392
|
+
} catch (err) {
|
|
393
|
+
// istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
|
|
394
|
+
// 2. If entries is failure, then throw a TypeError.
|
|
395
|
+
throw Object.assign(new TypeError(), { cause: err })
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 3. Return a new FormData object whose entries are entries.
|
|
399
|
+
const formData = new FormData()
|
|
400
|
+
for (const [name, value] of entries) {
|
|
401
|
+
formData.append(name, value)
|
|
402
|
+
}
|
|
403
|
+
return formData
|
|
404
|
+
} else {
|
|
405
|
+
// Otherwise, throw a TypeError.
|
|
406
|
+
webidl.errors.exception({
|
|
407
|
+
header: `${instance.name}.formData`,
|
|
408
|
+
value: 'Could not parse content as FormData.'
|
|
409
|
+
})
|
|
332
410
|
}
|
|
333
|
-
return formData
|
|
334
|
-
} else {
|
|
335
|
-
// Otherwise, throw a TypeError.
|
|
336
|
-
throw new TypeError()
|
|
337
411
|
}
|
|
338
412
|
}
|
|
413
|
+
|
|
414
|
+
return methods
|
|
339
415
|
}
|
|
340
416
|
|
|
341
417
|
const properties = {
|
|
342
418
|
body: {
|
|
343
419
|
enumerable: true,
|
|
344
420
|
get () {
|
|
421
|
+
if (!this || !this[kState]) {
|
|
422
|
+
throw new TypeError('Illegal invocation')
|
|
423
|
+
}
|
|
424
|
+
|
|
345
425
|
return this[kState].body ? this[kState].body.stream : null
|
|
346
426
|
}
|
|
347
427
|
},
|
|
348
428
|
bodyUsed: {
|
|
349
429
|
enumerable: true,
|
|
350
430
|
get () {
|
|
431
|
+
if (!this || !this[kState]) {
|
|
432
|
+
throw new TypeError('Illegal invocation')
|
|
433
|
+
}
|
|
434
|
+
|
|
351
435
|
return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
|
|
352
436
|
}
|
|
353
437
|
}
|
|
354
438
|
}
|
|
355
439
|
|
|
356
440
|
function mixinBody (prototype) {
|
|
357
|
-
Object.assign(prototype,
|
|
358
|
-
Object.defineProperties(prototype, properties)
|
|
441
|
+
Object.assign(prototype.prototype, bodyMixinMethods(prototype))
|
|
442
|
+
Object.defineProperties(prototype.prototype, properties)
|
|
359
443
|
}
|
|
360
444
|
|
|
361
445
|
module.exports = {
|
package/lib/fetch/constants.js
CHANGED
|
@@ -60,7 +60,19 @@ const subresource = [
|
|
|
60
60
|
''
|
|
61
61
|
]
|
|
62
62
|
|
|
63
|
+
/** @type {globalThis['DOMException']} */
|
|
64
|
+
const DOMException = globalThis.DOMException ?? (() => {
|
|
65
|
+
// DOMException was only made a global in Node v17.0.0,
|
|
66
|
+
// but fetch supports >= v16.8.
|
|
67
|
+
try {
|
|
68
|
+
atob('~')
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return Object.getPrototypeOf(err).constructor
|
|
71
|
+
}
|
|
72
|
+
})()
|
|
73
|
+
|
|
63
74
|
module.exports = {
|
|
75
|
+
DOMException,
|
|
64
76
|
subresource,
|
|
65
77
|
forbiddenMethods,
|
|
66
78
|
requestBodyHeader,
|