undici 7.15.0 → 7.16.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 +1 -1
- package/docs/docs/api/Agent.md +1 -0
- package/docs/docs/api/Errors.md +0 -1
- package/index-fetch.js +2 -2
- package/index.js +4 -8
- package/lib/api/api-request.js +22 -8
- package/lib/api/readable.js +7 -5
- package/lib/core/errors.js +217 -13
- package/lib/core/request.js +5 -1
- package/lib/core/util.js +32 -10
- package/lib/dispatcher/agent.js +19 -7
- package/lib/dispatcher/client-h1.js +20 -9
- package/lib/dispatcher/client-h2.js +13 -3
- package/lib/dispatcher/client.js +57 -57
- package/lib/dispatcher/dispatcher-base.js +12 -7
- package/lib/dispatcher/env-http-proxy-agent.js +12 -16
- package/lib/dispatcher/fixed-queue.js +15 -39
- package/lib/dispatcher/h2c-client.js +6 -6
- package/lib/dispatcher/pool-base.js +60 -43
- package/lib/dispatcher/pool.js +2 -2
- package/lib/dispatcher/proxy-agent.js +14 -9
- package/lib/global.js +19 -1
- package/lib/interceptor/cache.js +61 -0
- package/lib/mock/mock-agent.js +4 -4
- package/lib/mock/mock-errors.js +10 -0
- package/lib/mock/mock-utils.js +12 -10
- package/lib/util/date.js +534 -140
- package/lib/web/cookies/index.js +1 -1
- package/lib/web/eventsource/eventsource-stream.js +2 -2
- package/lib/web/eventsource/eventsource.js +34 -29
- package/lib/web/eventsource/util.js +1 -9
- package/lib/web/fetch/body.js +16 -22
- package/lib/web/fetch/index.js +14 -15
- package/lib/web/fetch/response.js +2 -4
- package/lib/web/fetch/util.js +8 -14
- package/lib/web/webidl/index.js +203 -42
- package/lib/web/websocket/connection.js +4 -3
- package/lib/web/websocket/events.js +1 -1
- package/lib/web/websocket/stream/websocketerror.js +22 -1
- package/lib/web/websocket/stream/websocketstream.js +16 -7
- package/lib/web/websocket/websocket.js +32 -42
- package/package.json +7 -6
- package/types/agent.d.ts +1 -0
- package/types/errors.d.ts +5 -15
- package/types/webidl.d.ts +82 -21
package/lib/web/cookies/index.js
CHANGED
|
@@ -236,7 +236,7 @@ class EventSourceStream extends Transform {
|
|
|
236
236
|
this.buffer = this.buffer.subarray(this.pos + 1)
|
|
237
237
|
this.pos = 0
|
|
238
238
|
if (
|
|
239
|
-
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
|
|
239
|
+
this.event.data !== undefined || this.event.event || this.event.id !== undefined || this.event.retry) {
|
|
240
240
|
this.processEvent(this.event)
|
|
241
241
|
}
|
|
242
242
|
this.clearEvent()
|
|
@@ -367,7 +367,7 @@ class EventSourceStream extends Transform {
|
|
|
367
367
|
this.state.reconnectionTime = parseInt(event.retry, 10)
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
if (event.id && isValidLastEventId(event.id)) {
|
|
370
|
+
if (event.id !== undefined && isValidLastEventId(event.id)) {
|
|
371
371
|
this.state.lastEventId = event.id
|
|
372
372
|
}
|
|
373
373
|
|
|
@@ -8,7 +8,6 @@ const { EventSourceStream } = require('./eventsource-stream')
|
|
|
8
8
|
const { parseMIMEType } = require('../fetch/data-url')
|
|
9
9
|
const { createFastMessageEvent } = require('../websocket/events')
|
|
10
10
|
const { isNetworkError } = require('../fetch/response')
|
|
11
|
-
const { delay } = require('./util')
|
|
12
11
|
const { kEnumerableProperty } = require('../../core/util')
|
|
13
12
|
const { environmentSettingsObject } = require('../fetch/util')
|
|
14
13
|
|
|
@@ -318,9 +317,9 @@ class EventSource extends EventTarget {
|
|
|
318
317
|
|
|
319
318
|
/**
|
|
320
319
|
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model
|
|
321
|
-
* @returns {
|
|
320
|
+
* @returns {void}
|
|
322
321
|
*/
|
|
323
|
-
|
|
322
|
+
#reconnect () {
|
|
324
323
|
// When a user agent is to reestablish the connection, the user agent must
|
|
325
324
|
// run the following steps. These steps are run in parallel, not as part of
|
|
326
325
|
// a task. (The tasks that it queues, of course, are run like normal tasks
|
|
@@ -338,27 +337,27 @@ class EventSource extends EventTarget {
|
|
|
338
337
|
this.dispatchEvent(new Event('error'))
|
|
339
338
|
|
|
340
339
|
// 2. Wait a delay equal to the reconnection time of the event source.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
340
|
+
setTimeout(() => {
|
|
341
|
+
// 5. Queue a task to run the following steps:
|
|
342
|
+
|
|
343
|
+
// 1. If the EventSource object's readyState attribute is not set to
|
|
344
|
+
// CONNECTING, then return.
|
|
345
|
+
if (this.#readyState !== CONNECTING) return
|
|
346
|
+
|
|
347
|
+
// 2. Let request be the EventSource object's request.
|
|
348
|
+
// 3. If the EventSource object's last event ID string is not the empty
|
|
349
|
+
// string, then:
|
|
350
|
+
// 1. Let lastEventIDValue be the EventSource object's last event ID
|
|
351
|
+
// string, encoded as UTF-8.
|
|
352
|
+
// 2. Set (`Last-Event-ID`, lastEventIDValue) in request's header
|
|
353
|
+
// list.
|
|
354
|
+
if (this.#state.lastEventId.length) {
|
|
355
|
+
this.#request.headersList.set('last-event-id', this.#state.lastEventId, true)
|
|
356
|
+
}
|
|
359
357
|
|
|
360
|
-
|
|
361
|
-
|
|
358
|
+
// 4. Fetch request and process the response obtained in this fashion, if any, as described earlier in this section.
|
|
359
|
+
this.#connect()
|
|
360
|
+
}, this.#state.reconnectionTime)?.unref()
|
|
362
361
|
}
|
|
363
362
|
|
|
364
363
|
/**
|
|
@@ -383,9 +382,11 @@ class EventSource extends EventTarget {
|
|
|
383
382
|
this.removeEventListener('open', this.#events.open)
|
|
384
383
|
}
|
|
385
384
|
|
|
386
|
-
|
|
385
|
+
const listener = webidl.converters.EventHandlerNonNull(fn)
|
|
386
|
+
|
|
387
|
+
if (listener !== null) {
|
|
388
|
+
this.addEventListener('open', listener)
|
|
387
389
|
this.#events.open = fn
|
|
388
|
-
this.addEventListener('open', fn)
|
|
389
390
|
} else {
|
|
390
391
|
this.#events.open = null
|
|
391
392
|
}
|
|
@@ -400,9 +401,11 @@ class EventSource extends EventTarget {
|
|
|
400
401
|
this.removeEventListener('message', this.#events.message)
|
|
401
402
|
}
|
|
402
403
|
|
|
403
|
-
|
|
404
|
+
const listener = webidl.converters.EventHandlerNonNull(fn)
|
|
405
|
+
|
|
406
|
+
if (listener !== null) {
|
|
407
|
+
this.addEventListener('message', listener)
|
|
404
408
|
this.#events.message = fn
|
|
405
|
-
this.addEventListener('message', fn)
|
|
406
409
|
} else {
|
|
407
410
|
this.#events.message = null
|
|
408
411
|
}
|
|
@@ -417,9 +420,11 @@ class EventSource extends EventTarget {
|
|
|
417
420
|
this.removeEventListener('error', this.#events.error)
|
|
418
421
|
}
|
|
419
422
|
|
|
420
|
-
|
|
423
|
+
const listener = webidl.converters.EventHandlerNonNull(fn)
|
|
424
|
+
|
|
425
|
+
if (listener !== null) {
|
|
426
|
+
this.addEventListener('error', listener)
|
|
421
427
|
this.#events.error = fn
|
|
422
|
-
this.addEventListener('error', fn)
|
|
423
428
|
} else {
|
|
424
429
|
this.#events.error = null
|
|
425
430
|
}
|
|
@@ -23,15 +23,7 @@ function isASCIINumber (value) {
|
|
|
23
23
|
return true
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// https://github.com/nodejs/undici/issues/2664
|
|
27
|
-
function delay (ms) {
|
|
28
|
-
return new Promise((resolve) => {
|
|
29
|
-
setTimeout(resolve, ms)
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
26
|
module.exports = {
|
|
34
27
|
isValidLastEventId,
|
|
35
|
-
isASCIINumber
|
|
36
|
-
delay
|
|
28
|
+
isASCIINumber
|
|
37
29
|
}
|
package/lib/web/fetch/body.js
CHANGED
|
@@ -60,7 +60,7 @@ function extractBody (object, keepalive = false) {
|
|
|
60
60
|
// 4. Otherwise, set stream to a new ReadableStream object, and set
|
|
61
61
|
// up stream with byte reading support.
|
|
62
62
|
stream = new ReadableStream({
|
|
63
|
-
|
|
63
|
+
pull (controller) {
|
|
64
64
|
const buffer = typeof source === 'string' ? textEncoder.encode(source) : source
|
|
65
65
|
|
|
66
66
|
if (buffer.byteLength) {
|
|
@@ -110,16 +110,10 @@ function extractBody (object, keepalive = false) {
|
|
|
110
110
|
|
|
111
111
|
// Set type to `application/x-www-form-urlencoded;charset=UTF-8`.
|
|
112
112
|
type = 'application/x-www-form-urlencoded;charset=UTF-8'
|
|
113
|
-
} else if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
source = new Uint8Array(object.slice())
|
|
118
|
-
} else if (ArrayBuffer.isView(object)) {
|
|
119
|
-
// BufferSource/ArrayBufferView
|
|
120
|
-
|
|
121
|
-
// Set source to a copy of the bytes held by object.
|
|
122
|
-
source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
|
|
113
|
+
} else if (webidl.is.BufferSource(object)) {
|
|
114
|
+
source = isArrayBuffer(object)
|
|
115
|
+
? new Uint8Array(object.slice())
|
|
116
|
+
: new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength))
|
|
123
117
|
} else if (webidl.is.FormData(object)) {
|
|
124
118
|
const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`
|
|
125
119
|
const prefix = `--${boundary}\r\nContent-Disposition: form-data`
|
|
@@ -320,12 +314,6 @@ function cloneBody (body) {
|
|
|
320
314
|
}
|
|
321
315
|
}
|
|
322
316
|
|
|
323
|
-
function throwIfAborted (state) {
|
|
324
|
-
if (state.aborted) {
|
|
325
|
-
throw new DOMException('The operation was aborted.', 'AbortError')
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
317
|
function bodyMixinMethods (instance, getInternalState) {
|
|
330
318
|
const methods = {
|
|
331
319
|
blob () {
|
|
@@ -443,24 +431,30 @@ function mixinBody (prototype, getInternalState) {
|
|
|
443
431
|
* @param {any} instance
|
|
444
432
|
* @param {(target: any) => any} getInternalState
|
|
445
433
|
*/
|
|
446
|
-
|
|
447
|
-
|
|
434
|
+
function consumeBody (object, convertBytesToJSValue, instance, getInternalState) {
|
|
435
|
+
try {
|
|
436
|
+
webidl.brandCheck(object, instance)
|
|
437
|
+
} catch (e) {
|
|
438
|
+
return Promise.reject(e)
|
|
439
|
+
}
|
|
448
440
|
|
|
449
441
|
const state = getInternalState(object)
|
|
450
442
|
|
|
451
443
|
// 1. If object is unusable, then return a promise rejected
|
|
452
444
|
// with a TypeError.
|
|
453
445
|
if (bodyUnusable(state)) {
|
|
454
|
-
|
|
446
|
+
return Promise.reject(new TypeError('Body is unusable: Body has already been read'))
|
|
455
447
|
}
|
|
456
448
|
|
|
457
|
-
|
|
449
|
+
if (state.aborted) {
|
|
450
|
+
return Promise.reject(new DOMException('The operation was aborted.', 'AbortError'))
|
|
451
|
+
}
|
|
458
452
|
|
|
459
453
|
// 2. Let promise be a new promise.
|
|
460
454
|
const promise = createDeferredPromise()
|
|
461
455
|
|
|
462
456
|
// 3. Let errorSteps given error be to reject promise with error.
|
|
463
|
-
const errorSteps =
|
|
457
|
+
const errorSteps = promise.reject
|
|
464
458
|
|
|
465
459
|
// 4. Let successSteps given a byte sequence data be to resolve
|
|
466
460
|
// promise with the result of running convertBytesToJSValue
|
package/lib/web/fetch/index.js
CHANGED
|
@@ -63,6 +63,9 @@ const { webidl } = require('../webidl')
|
|
|
63
63
|
const { STATUS_CODES } = require('node:http')
|
|
64
64
|
const { bytesMatch } = require('../subresource-integrity/subresource-integrity')
|
|
65
65
|
const { createDeferredPromise } = require('../../util/promise')
|
|
66
|
+
|
|
67
|
+
const hasZstd = typeof zlib.createZstdDecompress === 'function'
|
|
68
|
+
|
|
66
69
|
const GET_OR_HEAD = ['GET', 'HEAD']
|
|
67
70
|
|
|
68
71
|
const defaultUserAgent = typeof __UNDICI_IS_NODE__ !== 'undefined' || typeof esbuildDetection !== 'undefined'
|
|
@@ -2104,33 +2107,29 @@ async function httpNetworkFetch (
|
|
|
2104
2107
|
return false
|
|
2105
2108
|
}
|
|
2106
2109
|
|
|
2107
|
-
/** @type {string[]} */
|
|
2108
|
-
let codings = []
|
|
2109
|
-
|
|
2110
2110
|
const headersList = new HeadersList()
|
|
2111
2111
|
|
|
2112
2112
|
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
2113
2113
|
headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true)
|
|
2114
2114
|
}
|
|
2115
|
-
const contentEncoding = headersList.get('content-encoding', true)
|
|
2116
|
-
if (contentEncoding) {
|
|
2117
|
-
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
|
|
2118
|
-
// "All content-coding values are case-insensitive..."
|
|
2119
|
-
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
|
|
2120
|
-
}
|
|
2121
2115
|
const location = headersList.get('location', true)
|
|
2122
2116
|
|
|
2123
2117
|
this.body = new Readable({ read: resume })
|
|
2124
2118
|
|
|
2125
|
-
const decoders = []
|
|
2126
|
-
|
|
2127
2119
|
const willFollow = location && request.redirect === 'follow' &&
|
|
2128
2120
|
redirectStatusSet.has(status)
|
|
2129
2121
|
|
|
2122
|
+
const decoders = []
|
|
2123
|
+
|
|
2130
2124
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
|
2131
|
-
if (
|
|
2125
|
+
if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
|
|
2126
|
+
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
|
|
2127
|
+
const contentEncoding = headersList.get('content-encoding', true)
|
|
2128
|
+
// "All content-coding values are case-insensitive..."
|
|
2129
|
+
/** @type {string[]} */
|
|
2130
|
+
const codings = contentEncoding ? contentEncoding.toLowerCase().split(',') : []
|
|
2132
2131
|
for (let i = codings.length - 1; i >= 0; --i) {
|
|
2133
|
-
const coding = codings[i]
|
|
2132
|
+
const coding = codings[i].trim()
|
|
2134
2133
|
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
|
|
2135
2134
|
if (coding === 'x-gzip' || coding === 'gzip') {
|
|
2136
2135
|
decoders.push(zlib.createGunzip({
|
|
@@ -2151,8 +2150,8 @@ async function httpNetworkFetch (
|
|
|
2151
2150
|
flush: zlib.constants.BROTLI_OPERATION_FLUSH,
|
|
2152
2151
|
finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH
|
|
2153
2152
|
}))
|
|
2154
|
-
} else if (coding === 'zstd' &&
|
|
2155
|
-
|
|
2153
|
+
} else if (coding === 'zstd' && hasZstd) {
|
|
2154
|
+
// Node.js v23.8.0+ and v22.15.0+ supports Zstandard
|
|
2156
2155
|
decoders.push(zlib.createZstdDecompress({
|
|
2157
2156
|
flush: zlib.constants.ZSTD_e_continue,
|
|
2158
2157
|
finishFlush: zlib.constants.ZSTD_e_end
|
|
@@ -23,8 +23,6 @@ const { URLSerializer } = require('./data-url')
|
|
|
23
23
|
const { kConstruct } = require('../../core/symbols')
|
|
24
24
|
const assert = require('node:assert')
|
|
25
25
|
|
|
26
|
-
const { isArrayBuffer } = nodeUtil.types
|
|
27
|
-
|
|
28
26
|
const textEncoder = new TextEncoder('utf-8')
|
|
29
27
|
|
|
30
28
|
// https://fetch.spec.whatwg.org/#response-class
|
|
@@ -120,7 +118,7 @@ class Response {
|
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
if (body !== null) {
|
|
123
|
-
body = webidl.converters.BodyInit(body)
|
|
121
|
+
body = webidl.converters.BodyInit(body, 'Response', 'body')
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
init = webidl.converters.ResponseInit(init)
|
|
@@ -580,7 +578,7 @@ webidl.converters.XMLHttpRequestBodyInit = function (V, prefix, name) {
|
|
|
580
578
|
return V
|
|
581
579
|
}
|
|
582
580
|
|
|
583
|
-
if (
|
|
581
|
+
if (webidl.is.BufferSource(V)) {
|
|
584
582
|
return V
|
|
585
583
|
}
|
|
586
584
|
|
package/lib/web/fetch/util.js
CHANGED
|
@@ -502,8 +502,8 @@ function determineRequestsReferrer (request) {
|
|
|
502
502
|
if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
|
|
503
503
|
return 'no-referrer'
|
|
504
504
|
}
|
|
505
|
-
// 2. Return
|
|
506
|
-
return
|
|
505
|
+
// 2. Return referrerURL.
|
|
506
|
+
return referrerURL
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
}
|
|
@@ -554,17 +554,11 @@ function stripURLForReferrer (url, originOnly = false) {
|
|
|
554
554
|
return url
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
'(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){2}' +
|
|
560
|
-
'(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[1-9])' +
|
|
561
|
-
')$')
|
|
557
|
+
const isPotentialleTrustworthyIPv4 = RegExp.prototype.test
|
|
558
|
+
.bind(/^127\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){2}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)$/)
|
|
562
559
|
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
'(?:(?:0{1,4}):){1,6}(?::(?:0{0,3}1))|' +
|
|
566
|
-
'(?:::(?:0{0,3}1))|' +
|
|
567
|
-
')$')
|
|
560
|
+
const isPotentiallyTrustworthyIPv6 = RegExp.prototype.test
|
|
561
|
+
.bind(/^(?:(?:0{1,4}:){7}|(?:0{1,4}:){1,6}:|::)0{0,3}1$/)
|
|
568
562
|
|
|
569
563
|
/**
|
|
570
564
|
* Check if host matches one of the CIDR notations 127.0.0.0/8 or ::1/128.
|
|
@@ -579,11 +573,11 @@ function isOriginIPPotentiallyTrustworthy (origin) {
|
|
|
579
573
|
if (origin[0] === '[' && origin[origin.length - 1] === ']') {
|
|
580
574
|
origin = origin.slice(1, -1)
|
|
581
575
|
}
|
|
582
|
-
return
|
|
576
|
+
return isPotentiallyTrustworthyIPv6(origin)
|
|
583
577
|
}
|
|
584
578
|
|
|
585
579
|
// IPv4
|
|
586
|
-
return
|
|
580
|
+
return isPotentialleTrustworthyIPv4(origin)
|
|
587
581
|
}
|
|
588
582
|
|
|
589
583
|
/**
|