From e7bce4b50a08fd4c12ef9e9bb65bfd8c12241923 Mon Sep 17 00:00:00 2001 From: mcollina <52195+mcollina@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:38:34 +0000 Subject: [PATCH] deps: update undici to 7.21.0 --- deps/undici/src/docs/docs/api/Client.md | 1 + deps/undici/src/docs/docs/api/Dispatcher.md | 3 +- deps/undici/src/docs/docs/api/H2CClient.md | 1 + deps/undici/src/docs/docs/api/MockPool.md | 3 +- deps/undici/src/index-fetch.js | 34 ++- deps/undici/src/index.js | 34 ++- deps/undici/src/lib/api/api-request.js | 1 + deps/undici/src/lib/core/symbols.js | 1 + deps/undici/src/lib/dispatcher/agent.js | 4 +- deps/undici/src/lib/dispatcher/client-h1.js | 9 +- deps/undici/src/lib/dispatcher/client-h2.js | 43 +++- deps/undici/src/lib/dispatcher/client.js | 14 +- deps/undici/src/lib/dispatcher/pool-base.js | 14 +- deps/undici/src/lib/llhttp/wasm_build_env.txt | 2 +- deps/undici/src/lib/mock/mock-utils.js | 37 ++- deps/undici/src/lib/web/fetch/body.js | 94 +++----- deps/undici/src/lib/web/fetch/index.js | 20 +- deps/undici/src/lib/web/fetch/response.js | 3 +- deps/undici/src/lib/web/webidl/index.js | 52 +++++ deps/undici/src/package-lock.json | 185 +++++++-------- deps/undici/src/package.json | 36 +-- deps/undici/src/types/client.d.ts | 5 + deps/undici/src/types/dispatcher.d.ts | 1 + deps/undici/src/types/webidl.d.ts | 6 + deps/undici/src/types/websocket.d.ts | 2 + deps/undici/undici.js | 221 ++++++++++++------ src/undici_version.h | 2 +- 27 files changed, 554 insertions(+), 274 deletions(-) diff --git a/deps/undici/src/docs/docs/api/Client.md b/deps/undici/src/docs/docs/api/Client.md index abc02d87d17bb3..680375d1479e97 100644 --- a/deps/undici/src/docs/docs/api/Client.md +++ b/deps/undici/src/docs/docs/api/Client.md @@ -34,6 +34,7 @@ Returns: `Client` * **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame. * **initialWindowSize**: `number` (optional) - Default: `262144` (256KB). Sets the HTTP/2 stream-level flow-control window size (SETTINGS_INITIAL_WINDOW_SIZE). Must be a positive integer greater than 0. This default is higher than Node.js core's default (65535 bytes) to improve throughput, Node's choice is very conservative for current high-bandwith networks. See [RFC 7540 Section 6.9.2](https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2) for more details. * **connectionWindowSize**: `number` (optional) - Default `524288` (512KB). Sets the HTTP/2 connection-level flow-control window size using `ClientHttp2Session.setLocalWindowSize()`. Must be a positive integer greater than 0. This provides better flow control for the entire connection across multiple streams. See [Node.js HTTP/2 documentation](https://nodejs.org/api/http2.html#clienthttp2sessionsetlocalwindowsize) for more details. +* **pingInterval**: `number` - Default: `60e3`. The time interval in milliseconds between PING frames sent to the server. Set to `0` to disable PING frames. This is only applicable for HTTP/2 connections. This will emit a `ping` event on the client with the duration of the ping in milliseconds. > **Notes about HTTP/2** > - It only works under TLS connections. h2c is not supported. diff --git a/deps/undici/src/docs/docs/api/Dispatcher.md b/deps/undici/src/docs/docs/api/Dispatcher.md index f8639267825238..70fc5c268265c8 100644 --- a/deps/undici/src/docs/docs/api/Dispatcher.md +++ b/deps/undici/src/docs/docs/api/Dispatcher.md @@ -476,6 +476,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`. #### Parameter: `ResponseData` * **statusCode** `number` +* **statusText** `string` - The status message from the response (e.g., "OK", "Not Found"). * **headers** `Record` - Note that all header keys are lower-cased, e.g. `content-type`. * **body** `stream.Readable` which also implements [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin). * **trailers** `Record` - This object starts out @@ -517,7 +518,7 @@ await once(server, 'listening') const client = new Client(`http://localhost:${server.address().port}`) try { - const { body, headers, statusCode, trailers } = await client.request({ + const { body, headers, statusCode, statusText, trailers } = await client.request({ path: '/', method: 'GET' }) diff --git a/deps/undici/src/docs/docs/api/H2CClient.md b/deps/undici/src/docs/docs/api/H2CClient.md index d9ba3089135c23..6558ad5f4a4895 100644 --- a/deps/undici/src/docs/docs/api/H2CClient.md +++ b/deps/undici/src/docs/docs/api/H2CClient.md @@ -48,6 +48,7 @@ Returns: `H2CClient` - **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable. - **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame. - **pipelining** `number | null` (optional) - Default to `maxConcurrentStreams` - The amount of concurrent requests sent over a single HTTP/2 session in accordance with [RFC-7540](https://httpwg.org/specs/rfc7540.html#StreamsLayer) Stream specification. Streams can be closed up by remote server at any time. +- **pingInterval**: `number` - Default: `60e3`. The time interval in milliseconds between PING frames sent to the server. Set to `0` to disable PING frames. This is only applicable for HTTP/2 connections. - **connect** `ConnectOptions | null` (optional) - Default: `null`. - **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. **Security Warning:** Disabling this option can expose your application to HTTP Request Smuggling attacks, where mismatched content-length headers cause servers and proxies to interpret request boundaries differently. This can lead to cache poisoning, credential hijacking, and bypassing security controls. Only disable this in controlled environments where you fully trust the request source. - **autoSelectFamily**: `boolean` (optional) - Default: depends on local Node version, on Node 18.13.0 and above is `false`. Enables a family autodetection algorithm that loosely implements section 5 of [RFC 8305](https://tools.ietf.org/html/rfc8305#section-5). See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. This option is ignored if not supported by the current Node version. diff --git a/deps/undici/src/docs/docs/api/MockPool.md b/deps/undici/src/docs/docs/api/MockPool.md index 6656b95d834c05..64a7044b8050d7 100644 --- a/deps/undici/src/docs/docs/api/MockPool.md +++ b/deps/undici/src/docs/docs/api/MockPool.md @@ -323,7 +323,8 @@ try { method: 'GET' }) } catch (error) { - console.error(error) // Error: kaboom + console.error(error) // TypeError: fetch failed + console.error(error.cause) // Error: kaboom } ``` diff --git a/deps/undici/src/index-fetch.js b/deps/undici/src/index-fetch.js index 8f5bb6ceae2ffb..3d1b811daa4938 100644 --- a/deps/undici/src/index-fetch.js +++ b/deps/undici/src/index-fetch.js @@ -4,10 +4,40 @@ const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global') const EnvHttpProxyAgent = require('./lib/dispatcher/env-http-proxy-agent') const fetchImpl = require('./lib/web/fetch').fetch +// Capture __filename at module load time for stack trace augmentation. +// This may be undefined when bundled in environments like Node.js internals. +const currentFilename = typeof __filename !== 'undefined' ? __filename : undefined + +function appendFetchStackTrace (err, filename) { + if (!err || typeof err !== 'object') { + return + } + + const stack = typeof err.stack === 'string' ? err.stack : '' + const normalizedFilename = filename.replace(/\\/g, '/') + + if (stack && (stack.includes(filename) || stack.includes(normalizedFilename))) { + return + } + + const capture = {} + Error.captureStackTrace(capture, appendFetchStackTrace) + + if (!capture.stack) { + return + } + + const captureLines = capture.stack.split('\n').slice(1).join('\n') + + err.stack = stack ? `${stack}\n${captureLines}` : capture.stack +} + module.exports.fetch = function fetch (init, options = undefined) { return fetchImpl(init, options).catch(err => { - if (err && typeof err === 'object') { - Error.captureStackTrace(err) + if (currentFilename) { + appendFetchStackTrace(err, currentFilename) + } else if (err && typeof err === 'object') { + Error.captureStackTrace(err, module.exports.fetch) } throw err }) diff --git a/deps/undici/src/index.js b/deps/undici/src/index.js index 14f439a2334707..271c64e8ec3a09 100644 --- a/deps/undici/src/index.js +++ b/deps/undici/src/index.js @@ -121,10 +121,40 @@ module.exports.getGlobalDispatcher = getGlobalDispatcher const fetchImpl = require('./lib/web/fetch').fetch +// Capture __filename at module load time for stack trace augmentation. +// This may be undefined when bundled in environments like Node.js internals. +const currentFilename = typeof __filename !== 'undefined' ? __filename : undefined + +function appendFetchStackTrace (err, filename) { + if (!err || typeof err !== 'object') { + return + } + + const stack = typeof err.stack === 'string' ? err.stack : '' + const normalizedFilename = filename.replace(/\\/g, '/') + + if (stack && (stack.includes(filename) || stack.includes(normalizedFilename))) { + return + } + + const capture = {} + Error.captureStackTrace(capture, appendFetchStackTrace) + + if (!capture.stack) { + return + } + + const captureLines = capture.stack.split('\n').slice(1).join('\n') + + err.stack = stack ? `${stack}\n${captureLines}` : capture.stack +} + module.exports.fetch = function fetch (init, options = undefined) { return fetchImpl(init, options).catch(err => { - if (err && typeof err === 'object') { - Error.captureStackTrace(err) + if (currentFilename) { + appendFetchStackTrace(err, currentFilename) + } else if (err && typeof err === 'object') { + Error.captureStackTrace(err, module.exports.fetch) } throw err }) diff --git a/deps/undici/src/lib/api/api-request.js b/deps/undici/src/lib/api/api-request.js index c3461b23c84b42..f6d15f75b0ecaa 100644 --- a/deps/undici/src/lib/api/api-request.js +++ b/deps/undici/src/lib/api/api-request.js @@ -121,6 +121,7 @@ class RequestHandler extends AsyncResource { try { this.runInAsyncScope(callback, null, null, { statusCode, + statusText: statusMessage, headers, trailers: this.trailers, opaque, diff --git a/deps/undici/src/lib/core/symbols.js b/deps/undici/src/lib/core/symbols.js index ec45d7951efbcf..fd7af0c10e1a88 100644 --- a/deps/undici/src/lib/core/symbols.js +++ b/deps/undici/src/lib/core/symbols.js @@ -67,6 +67,7 @@ module.exports = { kEnableConnectProtocol: Symbol('http2session connect protocol'), kRemoteSettings: Symbol('http2session remote settings'), kHTTP2Stream: Symbol('http2session client stream'), + kPingInterval: Symbol('ping interval'), kNoProxyAgent: Symbol('no proxy agent'), kHttpProxyAgent: Symbol('http proxy agent'), kHttpsProxyAgent: Symbol('https proxy agent') diff --git a/deps/undici/src/lib/dispatcher/agent.js b/deps/undici/src/lib/dispatcher/agent.js index 781bc8cf0d130a..939b0ad55d3901 100644 --- a/deps/undici/src/lib/dispatcher/agent.js +++ b/deps/undici/src/lib/dispatcher/agent.js @@ -92,7 +92,9 @@ class Agent extends DispatcherBase { if (connected) result.count -= 1 if (result.count <= 0) { this[kClients].delete(key) - result.dispatcher.close() + if (!result.dispatcher.destroyed) { + result.dispatcher.close() + } } this[kOrigins].delete(key) } diff --git a/deps/undici/src/lib/dispatcher/client-h1.js b/deps/undici/src/lib/dispatcher/client-h1.js index 09d1a7599c4e04..ce6b4eedbd336d 100644 --- a/deps/undici/src/lib/dispatcher/client-h1.js +++ b/deps/undici/src/lib/dispatcher/client-h1.js @@ -735,8 +735,13 @@ class Parser { } } -function onParserTimeout (parser) { - const { socket, timeoutType, client, paused } = parser.deref() +function onParserTimeout (parserWeakRef) { + const parser = parserWeakRef.deref() + if (!parser) { + return + } + + const { socket, timeoutType, client, paused } = parser if (timeoutType === TIMEOUT_HEADERS) { if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) { diff --git a/deps/undici/src/lib/dispatcher/client-h2.js b/deps/undici/src/lib/dispatcher/client-h2.js index c9aec504af12c6..0969108911a6c0 100644 --- a/deps/undici/src/lib/dispatcher/client-h2.js +++ b/deps/undici/src/lib/dispatcher/client-h2.js @@ -24,6 +24,7 @@ const { kStrictContentLength, kOnError, kMaxConcurrentStreams, + kPingInterval, kHTTP2Session, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, @@ -34,7 +35,8 @@ const { kBodyTimeout, kEnableConnectProtocol, kRemoteSettings, - kHTTP2Stream + kHTTP2Stream, + kHTTP2SessionState } = require('../core/symbols.js') const { channels } = require('../core/diagnostics.js') @@ -102,10 +104,15 @@ function connectH2 (client, socket) { } }) + client[kSocket] = socket session[kOpenStreams] = 0 session[kClient] = client session[kSocket] = socket - session[kHTTP2Session] = null + session[kHTTP2SessionState] = { + ping: { + interval: client[kPingInterval] === 0 ? null : setInterval(onHttp2SendPing, client[kPingInterval], session).unref() + } + } // We set it to true by default in a best-effort; however once connected to an H2 server // we will check if extended CONNECT protocol is supported or not // and set this value accordingly. @@ -253,6 +260,31 @@ function onHttp2RemoteSettings (settings) { this[kClient][kResume]() } +function onHttp2SendPing (session) { + const state = session[kHTTP2SessionState] + if ((session.closed || session.destroyed) && state.ping.interval != null) { + clearInterval(state.ping.interval) + state.ping.interval = null + return + } + + // If no ping sent, do nothing + session.ping(onPing.bind(session)) + + function onPing (err, duration) { + const client = this[kClient] + const socket = this[kClient] + + if (err != null) { + const error = new InformationalError(`HTTP/2: "PING" errored - type ${err.message}`) + socket[kError] = error + client[kOnError](error) + } else { + client.emit('ping', duration) + } + } +} + function onHttp2SessionError (err) { assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') @@ -316,7 +348,7 @@ function onHttp2SessionGoAway (errorCode) { } function onHttp2SessionClose () { - const { [kClient]: client } = this + const { [kClient]: client, [kHTTP2SessionState]: state } = this const { [kSocket]: socket } = client const err = this[kSocket][kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket)) @@ -324,6 +356,11 @@ function onHttp2SessionClose () { client[kSocket] = null client[kHTTPContext] = null + if (state.ping.interval != null) { + clearInterval(state.ping.interval) + state.ping.interval = null + } + if (client.destroyed) { assert(client[kPending] === 0) diff --git a/deps/undici/src/lib/dispatcher/client.js b/deps/undici/src/lib/dispatcher/client.js index e603cfebd393e2..101acb6012378d 100644 --- a/deps/undici/src/lib/dispatcher/client.js +++ b/deps/undici/src/lib/dispatcher/client.js @@ -54,7 +54,8 @@ const { kMaxConcurrentStreams, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, - kResume + kResume, + kPingInterval } = require('../core/symbols.js') const connectH1 = require('./client-h1.js') const connectH2 = require('./client-h2.js') @@ -112,7 +113,8 @@ class Client extends DispatcherBase { allowH2, useH2c, initialWindowSize, - connectionWindowSize + connectionWindowSize, + pingInterval } = {}) { if (keepAlive !== undefined) { throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') @@ -216,6 +218,10 @@ class Client extends DispatcherBase { throw new InvalidArgumentError('connectionWindowSize must be a positive integer, greater than 0') } + if (pingInterval != null && (typeof pingInterval !== 'number' || !Number.isInteger(pingInterval) || pingInterval < 0)) { + throw new InvalidArgumentError('pingInterval must be a positive integer, greater or equal to 0') + } + super() if (typeof connect !== 'function') { @@ -250,6 +256,8 @@ class Client extends DispatcherBase { this[kMaxRequests] = maxRequestsPerClient this[kClosedResolve] = null this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1 + this[kHTTPContext] = null + // h2 this[kMaxConcurrentStreams] = maxConcurrentStreams != null ? maxConcurrentStreams : 100 // Max peerConcurrentStreams for a Node h2 server // HTTP/2 window sizes are set to higher defaults than Node.js core for better performance: // - initialWindowSize: 262144 (256KB) vs Node.js default 65535 (64KB - 1) @@ -259,7 +267,7 @@ class Client extends DispatcherBase { // Provides better flow control for the entire connection across multiple streams. this[kHTTP2InitialWindowSize] = initialWindowSize != null ? initialWindowSize : 262144 this[kHTTP2ConnectionWindowSize] = connectionWindowSize != null ? connectionWindowSize : 524288 - this[kHTTPContext] = null + this[kPingInterval] = pingInterval != null ? pingInterval : 60e3 // Default ping interval for h2 - 1 minute // kQueue is built up of 3 sections separated by // the kRunningIdx and kPendingIdx indices. diff --git a/deps/undici/src/lib/dispatcher/pool-base.js b/deps/undici/src/lib/dispatcher/pool-base.js index 4de14f920e866e..6c1f2388766f84 100644 --- a/deps/undici/src/lib/dispatcher/pool-base.js +++ b/deps/undici/src/lib/dispatcher/pool-base.js @@ -48,9 +48,12 @@ class PoolBase extends DispatcherBase { } if (this[kClosedResolve] && queue.isEmpty()) { - const closeAll = new Array(this[kClients].length) + const closeAll = [] for (let i = 0; i < this[kClients].length; i++) { - closeAll[i] = this[kClients][i].close() + const client = this[kClients][i] + if (!client.destroyed) { + closeAll.push(client.close()) + } } return Promise.all(closeAll) .then(this[kClosedResolve]) @@ -119,9 +122,12 @@ class PoolBase extends DispatcherBase { [kClose] () { if (this[kQueue].isEmpty()) { - const closeAll = new Array(this[kClients].length) + const closeAll = [] for (let i = 0; i < this[kClients].length; i++) { - closeAll[i] = this[kClients][i].close() + const client = this[kClients][i] + if (!client.destroyed) { + closeAll.push(client.close()) + } } return Promise.all(closeAll) } else { diff --git a/deps/undici/src/lib/llhttp/wasm_build_env.txt b/deps/undici/src/lib/llhttp/wasm_build_env.txt index 96c7c9c0dbb124..79a11c8ce14fae 100644 --- a/deps/undici/src/lib/llhttp/wasm_build_env.txt +++ b/deps/undici/src/lib/llhttp/wasm_build_env.txt @@ -1,5 +1,5 @@ -> undici@7.19.2 build:wasm +> undici@7.21.0 build:wasm > node build/wasm.js --docker > docker run --rm --platform=linux/x86_64 --user 1001:1001 --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/lib/llhttp,target=/home/node/build/lib/llhttp --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/build,target=/home/node/build/build --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/deps,target=/home/node/build/deps -t ghcr.io/nodejs/wasm-builder@sha256:975f391d907e42a75b8c72eb77c782181e941608687d4d8694c3e9df415a0970 node build/wasm.js diff --git a/deps/undici/src/lib/mock/mock-utils.js b/deps/undici/src/lib/mock/mock-utils.js index e1e3f04064305c..291a85753be4e7 100644 --- a/deps/undici/src/lib/mock/mock-utils.js +++ b/deps/undici/src/lib/mock/mock-utils.js @@ -312,9 +312,33 @@ function mockDispatch (opts, handler) { return true } + // Track whether the request has been aborted + let aborted = false + let timer = null + + function abort (err) { + if (aborted) { + return + } + aborted = true + + // Clear the pending delayed response if any + if (timer !== null) { + clearTimeout(timer) + timer = null + } + + // Notify the handler of the abort + handler.onError(err) + } + + // Call onConnect to allow the handler to register the abort callback + handler.onConnect?.(abort, null) + // Handle the request with a delay if necessary if (typeof delay === 'number' && delay > 0) { - setTimeout(() => { + timer = setTimeout(() => { + timer = null handleReply(this[kDispatches]) }, delay) } else { @@ -322,6 +346,11 @@ function mockDispatch (opts, handler) { } function handleReply (mockDispatches, _data = data) { + // Don't send response if the request was aborted + if (aborted) { + return + } + // fetch's HeadersList is a 1D string array const optsHeaders = Array.isArray(opts.headers) ? buildHeadersFromArray(opts.headers) @@ -340,11 +369,15 @@ function mockDispatch (opts, handler) { return body.then((newData) => handleReply(mockDispatches, newData)) } + // Check again if aborted after async body resolution + if (aborted) { + return + } + const responseData = getResponseData(body) const responseHeaders = generateKeyValues(headers) const responseTrailers = generateKeyValues(trailers) - handler.onConnect?.(err => handler.onError(err), null) handler.onHeaders?.(statusCode, responseHeaders, resume, getStatusText(statusCode)) handler.onData?.(Buffer.from(responseData)) handler.onComplete?.(responseTrailers) diff --git a/deps/undici/src/lib/web/fetch/body.js b/deps/undici/src/lib/web/fetch/body.js index 619bc783a4cd85..5d1724965272a7 100644 --- a/deps/undici/src/lib/web/fetch/body.js +++ b/deps/undici/src/lib/web/fetch/body.js @@ -11,7 +11,7 @@ const { FormData, setFormDataState } = require('./formdata') const { webidl } = require('../webidl') const assert = require('node:assert') const { isErrored, isDisturbed } = require('node:stream') -const { isArrayBuffer } = require('node:util/types') +const { isUint8Array } = require('node:util/types') const { serializeAMimeType } = require('./data-url') const { multipartFormDataParser } = require('./formdata-parser') const { createDeferredPromise } = require('../../util/promise') @@ -45,6 +45,7 @@ const streamRegistry = new FinalizationRegistry((weakRef) => { function extractBody (object, keepalive = false) { // 1. Let stream be null. let stream = null + let controller = null // 2. If object is a ReadableStream object, then set stream to object. if (webidl.is.ReadableStream(object)) { @@ -57,16 +58,11 @@ function extractBody (object, keepalive = false) { // 4. Otherwise, set stream to a new ReadableStream object, and set // up stream with byte reading support. stream = new ReadableStream({ - pull (controller) { - const buffer = typeof source === 'string' ? textEncoder.encode(source) : source - - if (buffer.byteLength) { - controller.enqueue(buffer) - } - - queueMicrotask(() => readableStreamClose(controller)) + pull () {}, + start (c) { + controller = c }, - start () {}, + cancel () {}, type: 'bytes' }) } @@ -108,9 +104,8 @@ function extractBody (object, keepalive = false) { // Set type to `application/x-www-form-urlencoded;charset=UTF-8`. type = 'application/x-www-form-urlencoded;charset=UTF-8' } else if (webidl.is.BufferSource(object)) { - source = isArrayBuffer(object) - ? new Uint8Array(object.slice()) - : new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) + // Set source to a copy of the bytes held by object. + source = webidl.util.getCopyOfBytesHeldByBufferSource(object) } else if (webidl.is.FormData(object)) { const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` const prefix = `--${boundary}\r\nContent-Disposition: form-data` @@ -213,45 +208,36 @@ function extractBody (object, keepalive = false) { // 11. If source is a byte sequence, then set action to a // step that returns source and length to source’s length. - if (typeof source === 'string' || util.isBuffer(source)) { - length = Buffer.byteLength(source) + if (typeof source === 'string' || isUint8Array(source)) { + action = () => { + length = typeof source === 'string' ? Buffer.byteLength(source) : source.length + return source + } } - // 12. If action is non-null, then run these steps in in parallel: + // 12. If action is non-null, then run these steps in parallel: if (action != null) { - // Run action. - let iterator - stream = new ReadableStream({ - start () { - iterator = action(object)[Symbol.asyncIterator]() - }, - pull (controller) { - return iterator.next().then(({ value, done }) => { - if (done) { - // When running action is done, close stream. - queueMicrotask(() => { - controller.close() - controller.byobRequest?.respond(0) - }) - } else { - // Whenever one or more bytes are available and stream is not errored, - // enqueue a Uint8Array wrapping an ArrayBuffer containing the available - // bytes into stream. - if (!isErrored(stream)) { - const buffer = new Uint8Array(value) - if (buffer.byteLength) { - controller.enqueue(buffer) - } - } + ;(async () => { + // 1. Run action. + const result = action() + + // 2. Whenever one or more bytes are available and stream is not errored, + // enqueue the result of creating a Uint8Array from the available bytes into stream. + const iterator = result?.[Symbol.asyncIterator]?.() + if (iterator) { + for await (const bytes of iterator) { + if (isErrored(stream)) break + if (bytes.length) { + controller.enqueue(new Uint8Array(bytes)) } - return controller.desiredSize > 0 - }) - }, - cancel (reason) { - return iterator.return() - }, - type: 'bytes' - }) + } + } else if (result?.length && !isErrored(stream)) { + controller.enqueue(typeof result === 'string' ? textEncoder.encode(result) : new Uint8Array(result)) + } + + // 3. When running action is done, close stream. + queueMicrotask(() => readableStreamClose(controller)) + })() } // 13. Let body be a body whose stream is stream, source is source, @@ -436,18 +422,14 @@ function consumeBody (object, convertBytesToJSValue, instance, getInternalState) return Promise.reject(e) } - const state = getInternalState(object) + object = getInternalState(object) // 1. If object is unusable, then return a promise rejected // with a TypeError. - if (bodyUnusable(state)) { + if (bodyUnusable(object)) { return Promise.reject(new TypeError('Body is unusable: Body has already been read')) } - if (state.aborted) { - return Promise.reject(new DOMException('The operation was aborted.', 'AbortError')) - } - // 2. Let promise be a new promise. const promise = createDeferredPromise() @@ -468,14 +450,14 @@ function consumeBody (object, convertBytesToJSValue, instance, getInternalState) // 5. If object’s body is null, then run successSteps with an // empty byte sequence. - if (state.body == null) { + if (object.body == null) { successSteps(Buffer.allocUnsafe(0)) return promise.promise } // 6. Otherwise, fully read object’s body given successSteps, // errorSteps, and object’s relevant global object. - fullyReadBody(state.body, successSteps, errorSteps) + fullyReadBody(object.body, successSteps, errorSteps) // 7. Return promise. return promise.promise diff --git a/deps/undici/src/lib/web/fetch/index.js b/deps/undici/src/lib/web/fetch/index.js index bb33e8d77e8e11..47d0a1fde0945b 100644 --- a/deps/undici/src/lib/web/fetch/index.js +++ b/deps/undici/src/lib/web/fetch/index.js @@ -157,7 +157,7 @@ function fetch (input, init = undefined) { if (requestObject.signal.aborted) { // 1. Abort the fetch() call with p, request, null, and // requestObject’s signal’s abort reason. - abortFetch(p, request, null, requestObject.signal.reason) + abortFetch(p, request, null, requestObject.signal.reason, null) // 2. Return p. return p.promise @@ -200,7 +200,7 @@ function fetch (input, init = undefined) { // 4. Abort the fetch() call with p, request, responseObject, // and requestObject’s signal’s abort reason. - abortFetch(p, request, realResponse, requestObject.signal.reason) + abortFetch(p, request, realResponse, requestObject.signal.reason, controller.controller) } ) @@ -227,7 +227,7 @@ function fetch (input, init = undefined) { // 2. Abort the fetch() call with p, request, responseObject, and // deserializedError. - abortFetch(p, request, responseObject, controller.serializedAbortReason) + abortFetch(p, request, responseObject, controller.serializedAbortReason, controller.controller) return } @@ -327,7 +327,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') { const markResourceTiming = performance.markResourceTiming // https://fetch.spec.whatwg.org/#abort-fetch -function abortFetch (p, request, responseObject, error) { +function abortFetch (p, request, responseObject, error, controller /* undici-specific */) { // 1. Reject promise with error. if (p) { // We might have already resolved the promise at this stage @@ -357,13 +357,7 @@ function abortFetch (p, request, responseObject, error) { // 5. If response’s body is not null and is readable, then error response’s // body with error. if (response.body?.stream != null && isReadable(response.body.stream)) { - response.body.stream.cancel(error).catch((err) => { - if (err.code === 'ERR_INVALID_STATE') { - // Node bug? - return - } - throw err - }) + controller.error(error) } } @@ -1321,8 +1315,8 @@ function httpRedirectFetch (fetchParams, response) { request.headersList.delete('host', true) } - // 14. If request’s body is non-null, then set request’s body to the first return - // value of safely extracting request’s body’s source. + // 14. If request's body is non-null, then set request's body to the first return + // value of safely extracting request's body's source. if (request.body != null) { assert(request.body.source != null) request.body = safelyExtractBody(request.body.source)[0] diff --git a/deps/undici/src/lib/web/fetch/response.js b/deps/undici/src/lib/web/fetch/response.js index 6b954afaab35de..ffb7ce1407488a 100644 --- a/deps/undici/src/lib/web/fetch/response.js +++ b/deps/undici/src/lib/web/fetch/response.js @@ -242,7 +242,8 @@ class Response { const clonedResponse = cloneResponse(this.#state) // Note: To re-register because of a new stream. - if (this.#state.body?.stream) { + // Don't set finalizers other than for fetch responses. + if (this.#state.urlList.length !== 0 && this.#state.body?.stream) { streamRegistry.register(this, new WeakRef(this.#state.body.stream)) } diff --git a/deps/undici/src/lib/web/webidl/index.js b/deps/undici/src/lib/web/webidl/index.js index 542571559739de..c7834328f87223 100644 --- a/deps/undici/src/lib/web/webidl/index.js +++ b/deps/undici/src/lib/web/webidl/index.js @@ -1,5 +1,6 @@ 'use strict' +const assert = require('node:assert') const { types, inspect } = require('node:util') const { runtimeFeatures } = require('../../util/runtime-features') @@ -542,6 +543,57 @@ webidl.is.BufferSource = function (V) { ) } +// https://webidl.spec.whatwg.org/#dfn-get-buffer-source-copy +webidl.util.getCopyOfBytesHeldByBufferSource = function (bufferSource) { + // 1. Let jsBufferSource be the result of converting bufferSource to a JavaScript value. + const jsBufferSource = bufferSource + + // 2. Let jsArrayBuffer be jsBufferSource. + let jsArrayBuffer = jsBufferSource + + // 3. Let offset be 0. + let offset = 0 + + // 4. Let length be 0. + let length = 0 + + // 5. If jsBufferSource has a [[ViewedArrayBuffer]] internal slot, then: + if (types.isTypedArray(jsBufferSource) || types.isDataView(jsBufferSource)) { + // 5.1. Set jsArrayBuffer to jsBufferSource.[[ViewedArrayBuffer]]. + jsArrayBuffer = jsBufferSource.buffer + + // 5.2. Set offset to jsBufferSource.[[ByteOffset]]. + offset = jsBufferSource.byteOffset + + // 5.3. Set length to jsBufferSource.[[ByteLength]]. + length = jsBufferSource.byteLength + } else { + // 6. Otherwise: + + // 6.1. Assert: jsBufferSource is an ArrayBuffer or SharedArrayBuffer object. + assert(types.isAnyArrayBuffer(jsBufferSource)) + + // 6.2. Set length to jsBufferSource.[[ArrayBufferByteLength]]. + length = jsBufferSource.byteLength + } + + // 7. If IsDetachedBuffer(jsArrayBuffer) is true, then return the empty byte sequence. + if (jsArrayBuffer.detached) { + return new Uint8Array(0) + } + + // 8. Let bytes be a new byte sequence of length equal to length. + const bytes = new Uint8Array(length) + + // 9. For i in the range offset to offset + length − 1, inclusive, + // set bytes[i − offset] to GetValueFromBuffer(jsArrayBuffer, i, Uint8, true, Unordered). + const view = new Uint8Array(jsArrayBuffer, offset, length) + bytes.set(view) + + // 10. Return bytes. + return bytes +} + // https://webidl.spec.whatwg.org/#es-DOMString webidl.converters.DOMString = function (V, prefix, argument, flags) { // 1. If V is null and the conversion is to an IDL type diff --git a/deps/undici/src/package-lock.json b/deps/undici/src/package-lock.json index 60330a63604a0a..681f2baf2efacb 100644 --- a/deps/undici/src/package-lock.json +++ b/deps/undici/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "undici", - "version": "7.19.2", + "version": "7.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "undici", - "version": "7.19.2", + "version": "7.21.0", "license": "MIT", "devDependencies": { "@fastify/busboy": "3.2.0", @@ -76,9 +76,9 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -91,9 +91,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -101,21 +101,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -132,14 +132,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -262,13 +262,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -532,18 +532,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -551,9 +551,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -1289,9 +1289,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2185,9 +2185,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", - "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "version": "20.19.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", + "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", "dev": true, "license": "MIT", "dependencies": { @@ -2440,9 +2440,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3233,7 +3233,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -3621,9 +3621,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "dev": true, "funding": [ { @@ -3669,9 +3669,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -4123,9 +4123,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.279", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", - "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true, "license": "ISC" }, @@ -4150,14 +4150,14 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -4492,9 +4492,9 @@ } }, "node_modules/eslint-compat-utils/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -4692,13 +4692,13 @@ } }, "node_modules/eslint-plugin-import-x/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "@isaacs/brace-expansion": "^5.0.1" }, "engines": { "node": "20 || >=22" @@ -4708,9 +4708,9 @@ } }, "node_modules/eslint-plugin-import-x/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -4761,9 +4761,9 @@ } }, "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -5450,9 +5450,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.13.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.5.tgz", + "integrity": "sha512-v4/4xAEpBRp6SvCkWhnGCaLkJf9IwWzrsygJPxD/+p2/xPE3C5m2fA9FD0Ry9tG+Rqqq3gBzHSl6y1/T9V/tMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5466,6 +5466,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -5970,9 +5971,9 @@ } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -6404,9 +6405,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7073,9 +7074,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7450,9 +7451,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7826,9 +7827,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -9820,9 +9821,9 @@ } }, "node_modules/tsd/node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, "license": "MIT" }, diff --git a/deps/undici/src/package.json b/deps/undici/src/package.json index bdfe7896b191a6..71267e0c075a0f 100644 --- a/deps/undici/src/package.json +++ b/deps/undici/src/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "7.19.2", + "version": "7.21.0", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": { @@ -71,29 +71,29 @@ "test:javascript": "npm run test:javascript:no-jest && npm run test:jest", "test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:fetch && npm run test:node-fetch && npm run test:infra && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:cookies && npm run test:eventsource && npm run test:subresource-integrity && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests", "test:javascript:without-intl": "npm run test:javascript:no-jest", - "test:busboy": "borp -p \"test/busboy/*.js\"", - "test:cache": "borp -p \"test/cache/*.js\"", - "test:cache-interceptor": "borp -p \"test/cache-interceptor/*.js\"", + "test:busboy": "borp --timeout 180000 -p \"test/busboy/*.js\"", + "test:cache": "borp --timeout 180000 -p \"test/cache/*.js\"", + "test:cache-interceptor": "borp --timeout 180000 -p \"test/cache-interceptor/*.js\"", "test:cache-interceptor:sqlite": "cross-env NODE_OPTIONS=--experimental-sqlite npm run test:cache-interceptor", - "test:cookies": "borp -p \"test/cookie/*.js\"", - "test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"", + "test:cookies": "borp --timeout 180000 -p \"test/cookie/*.js\"", + "test:eventsource": "npm run build:node && borp --timeout 180000 --expose-gc -p \"test/eventsource/*.js\"", "test:fuzzing": "node test/fuzzing/fuzzing.test.js", "test:fetch": "npm run build:node && borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy", - "test:subresource-integrity": "borp -p \"test/subresource-integrity/*.js\"", + "test:subresource-integrity": "borp --timeout 180000 -p \"test/subresource-integrity/*.js\"", "test:h2": "npm run test:h2:core && npm run test:h2:fetch", - "test:h2:core": "borp -p \"test/+(http2|h2)*.js\"", - "test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"", - "test:infra": "borp -p \"test/infra/*.js\"", - "test:interceptors": "borp -p \"test/interceptors/*.js\"", + "test:h2:core": "borp --timeout 180000 -p \"test/+(http2|h2)*.js\"", + "test:h2:fetch": "npm run build:node && borp --timeout 180000 -p \"test/fetch/http2*.js\"", + "test:infra": "borp --timeout 180000 -p \"test/infra/*.js\"", + "test:interceptors": "borp --timeout 180000 -p \"test/interceptors/*.js\"", "test:jest": "cross-env NODE_V8_COVERAGE= jest", - "test:unit": "borp --expose-gc -p \"test/*.js\"", - "test:node-fetch": "borp -p \"test/node-fetch/**/*.js\"", - "test:node-test": "borp -p \"test/node-test/**/*.js\"", - "test:tdd": "borp --expose-gc -p \"test/*.js\"", - "test:tdd:node-test": "borp -p \"test/node-test/**/*.js\" -w", + "test:unit": "borp --timeout 180000 --expose-gc -p \"test/*.js\"", + "test:node-fetch": "borp --timeout 180000 -p \"test/node-fetch/**/*.js\"", + "test:node-test": "borp --timeout 180000 -p \"test/node-test/**/*.js\"", + "test:tdd": "borp --timeout 180000 --expose-gc -p \"test/*.js\"", + "test:tdd:node-test": "borp --timeout 180000 -p \"test/node-test/**/*.js\" -w", "test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types --noEmit && tsc ./types/*.d.ts --noEmit --typeRoots ./types", - "test:webidl": "borp -p \"test/webidl/*.js\"", - "test:websocket": "borp -p \"test/websocket/**/*.js\"", + "test:webidl": "borp --timeout 180000 -p \"test/webidl/*.js\"", + "test:websocket": "borp --timeout 180000 -p \"test/websocket/**/*.js\"", "test:websocket:autobahn": "node test/autobahn/client.js", "test:websocket:autobahn:report": "node test/autobahn/report.js", "test:wpt:setup": "node test/web-platform-tests/wpt-runner.mjs setup", diff --git a/deps/undici/src/types/client.d.ts b/deps/undici/src/types/client.d.ts index f5ccde2be436af..a6e20221f68c40 100644 --- a/deps/undici/src/types/client.d.ts +++ b/deps/undici/src/types/client.d.ts @@ -102,6 +102,11 @@ export declare namespace Client { * @default 524288 */ connectionWindowSize?: number; + /** + * @description Time interval between PING frames dispatch + * @default 60000 + */ + pingInterval?: number; } export interface SocketInfo { localAddress?: string diff --git a/deps/undici/src/types/dispatcher.d.ts b/deps/undici/src/types/dispatcher.d.ts index 13b33ececc8412..684ca439c9a3ff 100644 --- a/deps/undici/src/types/dispatcher.d.ts +++ b/deps/undici/src/types/dispatcher.d.ts @@ -181,6 +181,7 @@ declare namespace Dispatcher { } export interface ResponseData { statusCode: number; + statusText: string; headers: IncomingHttpHeaders; body: BodyReadable & BodyMixin; trailers: Record; diff --git a/deps/undici/src/types/webidl.d.ts b/deps/undici/src/types/webidl.d.ts index d2a8eb9c39a93d..f95d9b567378c1 100644 --- a/deps/undici/src/types/webidl.d.ts +++ b/deps/undici/src/types/webidl.d.ts @@ -1,4 +1,5 @@ // These types are not exported, and are only used internally +import { BufferSource } from 'node:stream/web' import * as undici from './index' /** @@ -93,6 +94,11 @@ interface WebidlUtil { IsResizableArrayBuffer (V: ArrayBufferLike): boolean HasFlag (flag: number, attributes: number): boolean + + /** + * @see https://webidl.spec.whatwg.org/#dfn-get-buffer-source-copy + */ + getCopyOfBytesHeldByBufferSource (bufferSource: BufferSource): Uint8Array } interface WebidlConverters { diff --git a/deps/undici/src/types/websocket.d.ts b/deps/undici/src/types/websocket.d.ts index 6d81a2553b2aab..d48b9dadc0129a 100644 --- a/deps/undici/src/types/websocket.d.ts +++ b/deps/undici/src/types/websocket.d.ts @@ -169,6 +169,8 @@ interface WebSocketStream { writable: WritableStream }> url: string + + close(options?: Partial): void } export declare const WebSocketStream: { diff --git a/deps/undici/undici.js b/deps/undici/undici.js index 8ea790e4860045..52148e215f336f 100644 --- a/deps/undici/undici.js +++ b/deps/undici/undici.js @@ -530,6 +530,7 @@ var require_symbols = __commonJS({ kEnableConnectProtocol: Symbol("http2session connect protocol"), kRemoteSettings: Symbol("http2session remote settings"), kHTTP2Stream: Symbol("http2session client stream"), + kPingInterval: Symbol("ping interval"), kNoProxyAgent: Symbol("no proxy agent"), kHttpProxyAgent: Symbol("http proxy agent"), kHttpsProxyAgent: Symbol("https proxy agent") @@ -2405,9 +2406,12 @@ var require_pool_base = __commonJS({ this.emit("drain", origin, [this, ...targets]); } if (this[kClosedResolve] && queue.isEmpty()) { - const closeAll = new Array(this[kClients].length); + const closeAll = []; for (let i = 0; i < this[kClients].length; i++) { - closeAll[i] = this[kClients][i].close(); + const client2 = this[kClients][i]; + if (!client2.destroyed) { + closeAll.push(client2.close()); + } } return Promise.all(closeAll).then(this[kClosedResolve]); } @@ -2464,9 +2468,12 @@ var require_pool_base = __commonJS({ } [kClose]() { if (this[kQueue].isEmpty()) { - const closeAll = new Array(this[kClients].length); + const closeAll = []; for (let i = 0; i < this[kClients].length; i++) { - closeAll[i] = this[kClients][i].close(); + const client = this[kClients][i]; + if (!client.destroyed) { + closeAll.push(client.close()); + } } return Promise.all(closeAll); } else { @@ -4691,6 +4698,7 @@ var require_runtime_features = __commonJS({ var require_webidl = __commonJS({ "lib/web/webidl/index.js"(exports2, module2) { "use strict"; + var assert = require("node:assert"); var { types, inspect } = require("node:util"); var { runtimeFeatures } = require_runtime_features(); var UNDEFINED = 1; @@ -5035,6 +5043,27 @@ var require_webidl = __commonJS({ webidl.is.BufferSource = function(V) { return types.isArrayBuffer(V) || ArrayBuffer.isView(V) && types.isArrayBuffer(V.buffer); }; + webidl.util.getCopyOfBytesHeldByBufferSource = function(bufferSource) { + const jsBufferSource = bufferSource; + let jsArrayBuffer = jsBufferSource; + let offset = 0; + let length = 0; + if (types.isTypedArray(jsBufferSource) || types.isDataView(jsBufferSource)) { + jsArrayBuffer = jsBufferSource.buffer; + offset = jsBufferSource.byteOffset; + length = jsBufferSource.byteLength; + } else { + assert(types.isAnyArrayBuffer(jsBufferSource)); + length = jsBufferSource.byteLength; + } + if (jsArrayBuffer.detached) { + return new Uint8Array(0); + } + const bytes = new Uint8Array(length); + const view = new Uint8Array(jsArrayBuffer, offset, length); + bytes.set(view); + return bytes; + }; webidl.converters.DOMString = function(V, prefix, argument, flags) { if (V === null && webidl.util.HasFlag(flags, webidl.attributes.LegacyNullToEmptyString)) { return ""; @@ -6631,7 +6660,7 @@ var require_body = __commonJS({ var { webidl } = require_webidl(); var assert = require("node:assert"); var { isErrored, isDisturbed } = require("node:stream"); - var { isArrayBuffer } = require("node:util/types"); + var { isUint8Array } = require("node:util/types"); var { serializeAMimeType } = require_data_url(); var { multipartFormDataParser } = require_formdata_parser(); var { createDeferredPromise } = require_promise(); @@ -6651,20 +6680,19 @@ var require_body = __commonJS({ }); function extractBody(object, keepalive = false) { let stream = null; + let controller = null; if (webidl.is.ReadableStream(object)) { stream = object; } else if (webidl.is.Blob(object)) { stream = object.stream(); } else { stream = new ReadableStream({ - pull(controller) { - const buffer = typeof source === "string" ? textEncoder.encode(source) : source; - if (buffer.byteLength) { - controller.enqueue(buffer); - } - queueMicrotask(() => readableStreamClose(controller)); + pull() { }, - start() { + start(c) { + controller = c; + }, + cancel() { }, type: "bytes" }); @@ -6681,7 +6709,7 @@ var require_body = __commonJS({ source = object.toString(); type = "application/x-www-form-urlencoded;charset=UTF-8"; } else if (webidl.is.BufferSource(object)) { - source = isArrayBuffer(object) ? new Uint8Array(object.slice()) : new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)); + source = webidl.util.getCopyOfBytesHeldByBufferSource(object); } else if (webidl.is.FormData(object)) { const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, "0")}`; const prefix = `--${boundary}\r @@ -6748,38 +6776,29 @@ Content-Type: ${value.type || "application/octet-stream"}\r } stream = webidl.is.ReadableStream(object) ? object : ReadableStreamFrom(object); } - if (typeof source === "string" || util.isBuffer(source)) { - length = Buffer.byteLength(source); + if (typeof source === "string" || isUint8Array(source)) { + action = /* @__PURE__ */ __name(() => { + length = typeof source === "string" ? Buffer.byteLength(source) : source.length; + return source; + }, "action"); } if (action != null) { - let iterator; - stream = new ReadableStream({ - start() { - iterator = action(object)[Symbol.asyncIterator](); - }, - pull(controller) { - return iterator.next().then(({ value, done }) => { - if (done) { - queueMicrotask(() => { - controller.close(); - controller.byobRequest?.respond(0); - }); - } else { - if (!isErrored(stream)) { - const buffer = new Uint8Array(value); - if (buffer.byteLength) { - controller.enqueue(buffer); - } - } + ; + (async () => { + const result = action(); + const iterator = result?.[Symbol.asyncIterator]?.(); + if (iterator) { + for await (const bytes of iterator) { + if (isErrored(stream)) break; + if (bytes.length) { + controller.enqueue(new Uint8Array(bytes)); } - return controller.desiredSize > 0; - }); - }, - cancel(reason) { - return iterator.return(); - }, - type: "bytes" - }); + } + } else if (result?.length && !isErrored(stream)) { + controller.enqueue(typeof result === "string" ? textEncoder.encode(result) : new Uint8Array(result)); + } + queueMicrotask(() => readableStreamClose(controller)); + })(); } const body = { stream, source, length }; return [body, type]; @@ -6872,13 +6891,10 @@ Content-Type: ${value.type || "application/octet-stream"}\r } catch (e) { return Promise.reject(e); } - const state = getInternalState(object); - if (bodyUnusable(state)) { + object = getInternalState(object); + if (bodyUnusable(object)) { return Promise.reject(new TypeError("Body is unusable: Body has already been read")); } - if (state.aborted) { - return Promise.reject(new DOMException("The operation was aborted.", "AbortError")); - } const promise = createDeferredPromise(); const errorSteps = promise.reject; const successSteps = /* @__PURE__ */ __name((data) => { @@ -6888,11 +6904,11 @@ Content-Type: ${value.type || "application/octet-stream"}\r errorSteps(e); } }, "successSteps"); - if (state.body == null) { + if (object.body == null) { successSteps(Buffer.allocUnsafe(0)); return promise.promise; } - fullyReadBody(state.body, successSteps, errorSteps); + fullyReadBody(object.body, successSteps, errorSteps); return promise.promise; } __name(consumeBody, "consumeBody"); @@ -7501,8 +7517,12 @@ var require_client_h1 = __commonJS({ return 0; } }; - function onParserTimeout(parser) { - const { socket, timeoutType, client, paused } = parser.deref(); + function onParserTimeout(parserWeakRef) { + const parser = parserWeakRef.deref(); + if (!parser) { + return; + } + const { socket, timeoutType, client, paused } = parser; if (timeoutType === TIMEOUT_HEADERS) { if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) { assert(!paused, "cannot be paused while waiting for headers"); @@ -8130,6 +8150,7 @@ var require_client_h2 = __commonJS({ kStrictContentLength, kOnError, kMaxConcurrentStreams, + kPingInterval, kHTTP2Session, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, @@ -8140,7 +8161,8 @@ var require_client_h2 = __commonJS({ kBodyTimeout, kEnableConnectProtocol, kRemoteSettings, - kHTTP2Stream + kHTTP2Stream, + kHTTP2SessionState } = require_symbols(); var { channels } = require_diagnostics(); var kOpenStreams = Symbol("open streams"); @@ -8192,10 +8214,15 @@ var require_client_h2 = __commonJS({ ...http2InitialWindowSize != null ? { initialWindowSize: http2InitialWindowSize } : null } }); + client[kSocket] = socket; session[kOpenStreams] = 0; session[kClient] = client; session[kSocket] = socket; - session[kHTTP2Session] = null; + session[kHTTP2SessionState] = { + ping: { + interval: client[kPingInterval] === 0 ? null : setInterval(onHttp2SendPing, client[kPingInterval], session).unref() + } + }; session[kEnableConnectProtocol] = false; session[kRemoteSettings] = false; if (http2ConnectionWindowSize) { @@ -8302,6 +8329,28 @@ var require_client_h2 = __commonJS({ this[kClient][kResume](); } __name(onHttp2RemoteSettings, "onHttp2RemoteSettings"); + function onHttp2SendPing(session) { + const state = session[kHTTP2SessionState]; + if ((session.closed || session.destroyed) && state.ping.interval != null) { + clearInterval(state.ping.interval); + state.ping.interval = null; + return; + } + session.ping(onPing.bind(session)); + function onPing(err, duration) { + const client = this[kClient]; + const socket = this[kClient]; + if (err != null) { + const error = new InformationalError(`HTTP/2: "PING" errored - type ${err.message}`); + socket[kError] = error; + client[kOnError](error); + } else { + client.emit("ping", duration); + } + } + __name(onPing, "onPing"); + } + __name(onHttp2SendPing, "onHttp2SendPing"); function onHttp2SessionError(err) { assert(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); this[kSocket][kError] = err; @@ -8343,11 +8392,15 @@ var require_client_h2 = __commonJS({ } __name(onHttp2SessionGoAway, "onHttp2SessionGoAway"); function onHttp2SessionClose() { - const { [kClient]: client } = this; + const { [kClient]: client, [kHTTP2SessionState]: state } = this; const { [kSocket]: socket } = client; const err = this[kSocket][kError] || this[kError] || new SocketError("closed", util.getSocketInfo(socket)); client[kSocket] = null; client[kHTTPContext] = null; + if (state.ping.interval != null) { + clearInterval(state.ping.interval); + state.ping.interval = null; + } if (client.destroyed) { assert(client[kPending] === 0); const requests = client[kQueue].splice(client[kRunningIdx]); @@ -8875,7 +8928,8 @@ var require_client = __commonJS({ kMaxConcurrentStreams, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, - kResume + kResume, + kPingInterval } = require_symbols(); var connectH1 = require_client_h1(); var connectH2 = require_client_h2(); @@ -8927,7 +8981,8 @@ var require_client = __commonJS({ allowH2, useH2c, initialWindowSize, - connectionWindowSize + connectionWindowSize, + pingInterval } = {}) { if (keepAlive !== void 0) { throw new InvalidArgumentError("unsupported keepAlive, use pipelining=0 instead"); @@ -9002,6 +9057,9 @@ var require_client = __commonJS({ if (connectionWindowSize != null && (!Number.isInteger(connectionWindowSize) || connectionWindowSize < 1)) { throw new InvalidArgumentError("connectionWindowSize must be a positive integer, greater than 0"); } + if (pingInterval != null && (typeof pingInterval !== "number" || !Number.isInteger(pingInterval) || pingInterval < 0)) { + throw new InvalidArgumentError("pingInterval must be a positive integer, greater or equal to 0"); + } super(); if (typeof connect2 !== "function") { connect2 = buildConnector({ @@ -9035,10 +9093,11 @@ var require_client = __commonJS({ this[kMaxRequests] = maxRequestsPerClient; this[kClosedResolve] = null; this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1; + this[kHTTPContext] = null; this[kMaxConcurrentStreams] = maxConcurrentStreams != null ? maxConcurrentStreams : 100; this[kHTTP2InitialWindowSize] = initialWindowSize != null ? initialWindowSize : 262144; this[kHTTP2ConnectionWindowSize] = connectionWindowSize != null ? connectionWindowSize : 524288; - this[kHTTPContext] = null; + this[kPingInterval] = pingInterval != null ? pingInterval : 6e4; this[kQueue] = []; this[kRunningIdx] = 0; this[kPendingIdx] = 0; @@ -9520,7 +9579,9 @@ var require_agent = __commonJS({ if (connected) result2.count -= 1; if (result2.count <= 0) { this[kClients].delete(key); - result2.dispatcher.close(); + if (!result2.dispatcher.destroyed) { + result2.dispatcher.close(); + } } this[kOrigins].delete(key); } @@ -10620,7 +10681,7 @@ var require_response = __commonJS({ }); } const clonedResponse = cloneResponse(this.#state); - if (this.#state.body?.stream) { + if (this.#state.urlList.length !== 0 && this.#state.body?.stream) { streamRegistry.register(this, new WeakRef(this.#state.body.stream)); } return fromInnerResponse(clonedResponse, getHeadersGuard(this.#headers)); @@ -11931,7 +11992,7 @@ var require_fetch = __commonJS({ } const request = getRequestState(requestObject); if (requestObject.signal.aborted) { - abortFetch(p, request, null, requestObject.signal.reason); + abortFetch(p, request, null, requestObject.signal.reason, null); return p.promise; } const globalObject = request.client.globalObject; @@ -11948,7 +12009,7 @@ var require_fetch = __commonJS({ assert(controller != null); controller.abort(requestObject.signal.reason); const realResponse = responseObject?.deref(); - abortFetch(p, request, realResponse, requestObject.signal.reason); + abortFetch(p, request, realResponse, requestObject.signal.reason, controller.controller); } ); const processResponse = /* @__PURE__ */ __name((response) => { @@ -11956,7 +12017,7 @@ var require_fetch = __commonJS({ return; } if (response.aborted) { - abortFetch(p, request, responseObject, controller.serializedAbortReason); + abortFetch(p, request, responseObject, controller.serializedAbortReason, controller.controller); return; } if (response.type === "error") { @@ -12014,7 +12075,7 @@ var require_fetch = __commonJS({ } __name(finalizeAndReportTiming, "finalizeAndReportTiming"); var markResourceTiming = performance.markResourceTiming; - function abortFetch(p, request, responseObject, error) { + function abortFetch(p, request, responseObject, error, controller) { if (p) { p.reject(error); } @@ -12031,12 +12092,7 @@ var require_fetch = __commonJS({ } const response = getResponseState(responseObject); if (response.body?.stream != null && isReadable(response.body.stream)) { - response.body.stream.cancel(error).catch((err) => { - if (err.code === "ERR_INVALID_STATE") { - return; - } - throw err; - }); + controller.error(error); } } __name(abortFetch, "abortFetch"); @@ -15758,6 +15814,7 @@ var require_api_request = __commonJS({ try { this.runInAsyncScope(callback, null, null, { statusCode, + statusText: statusMessage, headers, trailers: this.trailers, opaque, @@ -16474,10 +16531,32 @@ var require_api = __commonJS({ var { getGlobalDispatcher, setGlobalDispatcher } = require_global2(); var EnvHttpProxyAgent = require_env_http_proxy_agent(); var fetchImpl = require_fetch().fetch; +var currentFilename = typeof __filename !== "undefined" ? __filename : void 0; +function appendFetchStackTrace(err, filename) { + if (!err || typeof err !== "object") { + return; + } + const stack = typeof err.stack === "string" ? err.stack : ""; + const normalizedFilename = filename.replace(/\\/g, "/"); + if (stack && (stack.includes(filename) || stack.includes(normalizedFilename))) { + return; + } + const capture = {}; + Error.captureStackTrace(capture, appendFetchStackTrace); + if (!capture.stack) { + return; + } + const captureLines = capture.stack.split("\n").slice(1).join("\n"); + err.stack = stack ? `${stack} +${captureLines}` : capture.stack; +} +__name(appendFetchStackTrace, "appendFetchStackTrace"); module.exports.fetch = /* @__PURE__ */ __name(function fetch(init, options = void 0) { return fetchImpl(init, options).catch((err) => { - if (err && typeof err === "object") { - Error.captureStackTrace(err); + if (currentFilename) { + appendFetchStackTrace(err, currentFilename); + } else if (err && typeof err === "object") { + Error.captureStackTrace(err, module.exports.fetch); } throw err; }); diff --git a/src/undici_version.h b/src/undici_version.h index a494e8adc3ece6..7dd65961984a57 100644 --- a/src/undici_version.h +++ b/src/undici_version.h @@ -2,5 +2,5 @@ // Refer to tools/dep_updaters/update-undici.sh #ifndef SRC_UNDICI_VERSION_H_ #define SRC_UNDICI_VERSION_H_ -#define UNDICI_VERSION "7.19.2" +#define UNDICI_VERSION "7.21.0" #endif // SRC_UNDICI_VERSION_H_