deps: update undici to 7.12.0

PR-URL: https://github.com/nodejs/node/pull/59135
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
This commit is contained in:
Node.js GitHub Bot 2025-07-23 03:02:28 -04:00 committed by GitHub
parent 2a17346cc4
commit 5fe16bc6bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1094 additions and 963 deletions

View File

@ -88,3 +88,7 @@ undici-fetch.js
# File generated by /test/request-timeout.js
test/request-timeout.10mb.bin
# Claude files
CLAUDE.md
.claude

View File

@ -114,8 +114,10 @@ const response = await fetch('https://api.example.com/data');
#### Use Built-in Fetch When:
- You want zero dependencies
- Building isomorphic code that runs in browsers and Node.js
- Publishing to npm and want to maximize compatibility with JS runtimes
- Simple HTTP requests without advanced configuration
- You're okay with the undici version bundled in your Node.js version
- You're publishing to npm and you want to maximize compatiblity
- You don't depend on features from a specific version of undici
#### Use Undici Module When:
- You need the latest undici features and performance improvements
@ -209,7 +211,7 @@ The `install()` function adds the following classes to `globalThis`:
- `fetch` - The fetch function
- `Headers` - HTTP headers management
- `Response` - HTTP response representation
- `Request` - HTTP request representation
- `Request` - HTTP request representation
- `FormData` - Form data handling
- `WebSocket` - WebSocket client
- `CloseEvent`, `ErrorEvent`, `MessageEvent` - WebSocket events

View File

@ -169,10 +169,11 @@ This message is published after the client has successfully connected to a serve
```js
import diagnosticsChannel from 'diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:open').subscribe(({ address, protocol, extensions }) => {
diagnosticsChannel.channel('undici:websocket:open').subscribe(({ address, protocol, extensions, websocket }) => {
console.log(address) // address, family, and port
console.log(protocol) // negotiated subprotocols
console.log(extensions) // negotiated extensions
console.log(websocket) // the WebSocket instance
})
```
@ -184,7 +185,7 @@ This message is published after the connection has closed.
import diagnosticsChannel from 'diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:close').subscribe(({ websocket, code, reason }) => {
console.log(websocket) // the WebSocket object
console.log(websocket) // the WebSocket instance
console.log(code) // the closing status code
console.log(reason) // the closing reason
})
@ -209,9 +210,10 @@ This message is published after the client receives a ping frame, if the connect
```js
import diagnosticsChannel from 'diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:ping').subscribe(({ payload }) => {
diagnosticsChannel.channel('undici:websocket:ping').subscribe(({ payload, websocket }) => {
// a Buffer or undefined, containing the optional application data of the frame
console.log(payload)
console.log(websocket) // the WebSocket instance
})
```
@ -222,8 +224,9 @@ This message is published after the client receives a pong frame.
```js
import diagnosticsChannel from 'diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:pong').subscribe(({ payload }) => {
diagnosticsChannel.channel('undici:websocket:pong').subscribe(({ payload, websocket }) => {
// a Buffer or undefined, containing the optional application data of the frame
console.log(payload)
console.log(websocket) // the WebSocket instance
})
```

View File

@ -1103,8 +1103,8 @@ The `cache` interceptor implements client-side response caching as described in
- `store` - The [`CacheStore`](/docs/docs/api/CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](/docs/docs/api/CacheStore.md#memorycachestore).
- `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.
- `cacheByDefault` - The default expiration time to cache responses by if they don't have an explicit expiration. If this isn't present, responses without explicit expiration will not be cached. Default `undefined`.
- `type` - The type of cache for Undici to act as. Can be `shared` or `private`. Default `shared`.
- `cacheByDefault` - The default expiration time to cache responses by if they don't have an explicit expiration and cannot have an heuristic expiry computed. If this isn't present, responses neither with an explicit expiration nor heuristically cacheable will not be cached. Default `undefined`.
- `type` - The [type of cache](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching#types_of_caches) for Undici to act as. Can be `shared` or `private`. Default `shared`. `private` implies privately cacheable responses will be cached and potentially shared with other users of your application.
## Instance Events

View File

@ -78,6 +78,33 @@ setInterval(() => write(), 5000)
```
## ping(websocket, payload)
Arguments:
* **websocket** `WebSocket` - The WebSocket instance to send the ping frame on
* **payload** `Buffer|undefined` (optional) - Optional payload data to include with the ping frame. Must not exceed 125 bytes.
Sends a ping frame to the WebSocket server. The server must respond with a pong frame containing the same payload data. This can be used for keepalive purposes or to verify that the connection is still active.
### Example:
```js
import { WebSocket, ping } from 'undici'
const ws = new WebSocket('wss://echo.websocket.events')
ws.addEventListener('open', () => {
// Send ping with no payload
ping(ws)
// Send ping with payload
const payload = Buffer.from('hello')
ping(ws, payload)
})
```
**Note**: A ping frame cannot have a payload larger than 125 bytes. The ping will only be sent if the WebSocket connection is in the OPEN state.
## Read More
- [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)

View File

@ -157,10 +157,12 @@ module.exports.parseMIMEType = parseMIMEType
module.exports.serializeAMimeType = serializeAMimeType
const { CloseEvent, ErrorEvent, MessageEvent } = require('./lib/web/websocket/events')
module.exports.WebSocket = require('./lib/web/websocket/websocket').WebSocket
const { WebSocket, ping } = require('./lib/web/websocket/websocket')
module.exports.WebSocket = WebSocket
module.exports.CloseEvent = CloseEvent
module.exports.ErrorEvent = ErrorEvent
module.exports.MessageEvent = MessageEvent
module.exports.ping = ping
module.exports.WebSocketStream = require('./lib/web/websocket/stream/websocketstream').WebSocketStream
module.exports.WebSocketError = require('./lib/web/websocket/stream/websocketerror').WebSocketError

View File

@ -89,9 +89,7 @@ class BodyReadable extends Readable {
// promise (i.e micro tick) for installing an 'error' listener will
// never get a chance and will always encounter an unhandled exception.
if (!this[kUsed]) {
setImmediate(() => {
callback(err)
})
setImmediate(callback, err)
} else {
callback(err)
}

View File

@ -42,7 +42,8 @@ class Request {
reset,
expectContinue,
servername,
throwOnError
throwOnError,
maxRedirections
}, handler) {
if (typeof path !== 'string') {
throw new InvalidArgumentError('path must be a string')
@ -86,6 +87,10 @@ class Request {
throw new InvalidArgumentError('invalid throwOnError')
}
if (maxRedirections != null && maxRedirections !== 0) {
throw new InvalidArgumentError('maxRedirections is not supported, use the redirect interceptor')
}
this.headersTimeout = headersTimeout
this.bodyTimeout = bodyTimeout

View File

@ -86,7 +86,7 @@ class TstNode {
/**
* @param {Uint8Array} key
* @return {TstNode | null}
* @returns {TstNode | null}
*/
search (key) {
const keylength = key.length

View File

@ -60,12 +60,12 @@ const removeAllListeners = util.removeAllListeners
let extractBody
async function lazyllhttp () {
function lazyllhttp () {
const llhttpWasmData = process.env.JEST_WORKER_ID ? require('../llhttp/llhttp-wasm.js') : undefined
let mod
try {
mod = await WebAssembly.compile(require('../llhttp/llhttp_simd-wasm.js'))
mod = new WebAssembly.Module(require('../llhttp/llhttp_simd-wasm.js'))
} catch (e) {
/* istanbul ignore next */
@ -73,10 +73,10 @@ async function lazyllhttp () {
// being enabled, but the occurring of this other error
// * https://github.com/emscripten-core/emscripten/issues/11495
// got me to remove that check to avoid breaking Node 12.
mod = await WebAssembly.compile(llhttpWasmData || require('../llhttp/llhttp-wasm.js'))
mod = new WebAssembly.Module(llhttpWasmData || require('../llhttp/llhttp-wasm.js'))
}
return await WebAssembly.instantiate(mod, {
return new WebAssembly.Instance(mod, {
env: {
/**
* @param {number} p
@ -165,11 +165,6 @@ async function lazyllhttp () {
}
let llhttpInstance = null
/**
* @type {Promise<WebAssembly.Instance>|null}
*/
let llhttpPromise = lazyllhttp()
llhttpPromise.catch()
/**
* @type {Parser|null}
@ -732,7 +727,7 @@ class Parser {
// We must wait a full event loop cycle to reuse this socket to make sure
// that non-spec compliant servers are not closing the connection even if they
// said they won't.
setImmediate(() => client[kResume]())
setImmediate(client[kResume])
} else {
client[kResume]()
}
@ -769,11 +764,7 @@ async function connectH1 (client, socket) {
client[kSocket] = socket
if (!llhttpInstance) {
const noop = () => {}
socket.on('error', noop)
llhttpInstance = await llhttpPromise
llhttpPromise = null
socket.off('error', noop)
llhttpInstance = lazyllhttp()
}
if (socket.errored) {
@ -1297,9 +1288,9 @@ function writeStream (abort, body, client, request, socket, contentLength, heade
.on('error', onFinished)
if (body.errorEmitted ?? body.errored) {
setImmediate(() => onFinished(body.errored))
setImmediate(onFinished, body.errored)
} else if (body.endEmitted ?? body.readableEnded) {
setImmediate(() => onFinished(null))
setImmediate(onFinished, null)
}
if (body.closeEmitted ?? body.closed) {

View File

@ -241,7 +241,10 @@ class CacheHandler {
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
*/
function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives) {
if (statusCode !== 200 && statusCode !== 307) {
// Allow caching for status codes 200 and 307 (original behavior)
// Also allow caching for other status codes that are heuristically cacheable
// when they have explicit cache directives
if (statusCode !== 200 && statusCode !== 307 && !HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode)) {
return false
}

View File

@ -42,7 +42,8 @@ class RedirectHandler {
this.dispatch = dispatch
this.location = null
this.opts = { ...opts, maxRedirections: 0 } // opts must be a copy
const { maxRedirections: _, ...cleanOpts } = opts
this.opts = cleanOpts // opts must be a copy, exclude maxRedirections
this.maxRedirections = maxRedirections
this.handler = handler
this.history = []
@ -138,7 +139,6 @@ class RedirectHandler {
this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin)
this.opts.path = path
this.opts.origin = origin
this.opts.maxRedirections = 0
this.opts.query = null
}

View File

@ -301,11 +301,11 @@ module.exports = (opts = {}) => {
assertCacheMethods(methods, 'opts.methods')
if (typeof cacheByDefault !== 'undefined' && typeof cacheByDefault !== 'number') {
throw new TypeError(`exepcted opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`)
throw new TypeError(`expected opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`)
}
if (typeof type !== 'undefined' && type !== 'shared' && type !== 'private') {
throw new TypeError(`exepcted opts.type to be shared, private, or undefined, got ${typeof type}`)
throw new TypeError(`expected opts.type to be shared, private, or undefined, got ${typeof type}`)
}
const globalOpts = {

View File

@ -11,7 +11,7 @@ function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections }
return dispatch(opts, handler)
}
const dispatchOpts = { ...rest, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
const dispatchOpts = { ...rest } // Stop sub dispatcher from also redirecting.
const redirectHandler = new RedirectHandler(dispatch, maxRedirections, dispatchOpts, handler)
return dispatch(dispatchOpts, redirectHandler)
}

View File

@ -1,5 +1,5 @@
> undici@7.11.0 build:wasm
> undici@7.12.0 build:wasm
> node build/wasm.js --docker
> docker run --rm --platform=linux/x86_64 --user 1001:118 --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

View File

@ -32,7 +32,7 @@ function makeCacheKey (opts) {
/**
* @param {Record<string, string[] | string>}
* @return {Record<string, string[] | string>}
* @returns {Record<string, string[] | string>}
*/
function normaliseHeaders (opts) {
let headers

28
deps/undici/src/lib/util/promise.js vendored Normal file
View File

@ -0,0 +1,28 @@
'use strict'
/**
* @template {*} T
* @typedef {Object} DeferredPromise
* @property {Promise<T>} promise
* @property {(value?: T) => void} resolve
* @property {(reason?: any) => void} reject
*/
/**
* @template {*} T
* @returns {DeferredPromise<T>} An object containing a promise and its resolve/reject methods.
*/
function createDeferredPromise () {
let res
let rej
const promise = new Promise((resolve, reject) => {
res = resolve
rej = reject
})
return { promise, resolve: res, reject: rej }
}
module.exports = {
createDeferredPromise
}

View File

@ -1,5 +1,7 @@
'use strict'
const assert = require('node:assert')
const { kConstruct } = require('../../core/symbols')
const { urlEquals, getFieldValues } = require('./util')
const { kEnumerableProperty, isDisturbed } = require('../../core/util')
@ -7,8 +9,8 @@ const { webidl } = require('../webidl')
const { cloneResponse, fromInnerResponse, getResponseState } = require('../fetch/response')
const { Request, fromInnerRequest, getRequestState } = require('../fetch/request')
const { fetching } = require('../fetch/index')
const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require('../fetch/util')
const assert = require('node:assert')
const { urlIsHttpHttpsScheme, readAllBytes } = require('../fetch/util')
const { createDeferredPromise } = require('../../util/promise')
/**
* @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation
@ -46,7 +48,7 @@ class Cache {
const prefix = 'Cache.match'
webidl.argumentLengthCheck(arguments, 1, prefix)
request = webidl.converters.RequestInfo(request, prefix, 'request')
request = webidl.converters.RequestInfo(request)
options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
const p = this.#internalMatchAll(request, options, 1)
@ -62,7 +64,7 @@ class Cache {
webidl.brandCheck(this, Cache)
const prefix = 'Cache.matchAll'
if (request !== undefined) request = webidl.converters.RequestInfo(request, prefix, 'request')
if (request !== undefined) request = webidl.converters.RequestInfo(request)
options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
return this.#internalMatchAll(request, options)
@ -74,7 +76,7 @@ class Cache {
const prefix = 'Cache.add'
webidl.argumentLengthCheck(arguments, 1, prefix)
request = webidl.converters.RequestInfo(request, prefix, 'request')
request = webidl.converters.RequestInfo(request)
// 1.
const requests = [request]
@ -262,7 +264,7 @@ class Cache {
const prefix = 'Cache.put'
webidl.argumentLengthCheck(arguments, 2, prefix)
request = webidl.converters.RequestInfo(request, prefix, 'request')
request = webidl.converters.RequestInfo(request)
response = webidl.converters.Response(response, prefix, 'response')
// 1.
@ -393,7 +395,7 @@ class Cache {
const prefix = 'Cache.delete'
webidl.argumentLengthCheck(arguments, 1, prefix)
request = webidl.converters.RequestInfo(request, prefix, 'request')
request = webidl.converters.RequestInfo(request)
options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
/**
@ -458,7 +460,7 @@ class Cache {
const prefix = 'Cache.keys'
if (request !== undefined) request = webidl.converters.RequestInfo(request, prefix, 'request')
if (request !== undefined) request = webidl.converters.RequestInfo(request)
options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
// 1.

View File

@ -4,7 +4,6 @@ const util = require('../../core/util')
const {
ReadableStreamFrom,
readableStreamClose,
createDeferredPromise,
fullyReadBody,
extractMimeType,
utf8DecodeBytes
@ -17,6 +16,8 @@ const { isErrored, isDisturbed } = require('node:stream')
const { isArrayBuffer } = require('node:util/types')
const { serializeAMimeType } = require('./data-url')
const { multipartFormDataParser } = require('./formdata-parser')
const { createDeferredPromise } = require('../../util/promise')
let random
try {
@ -29,19 +30,22 @@ try {
const textEncoder = new TextEncoder()
function noop () {}
const hasFinalizationRegistry = globalThis.FinalizationRegistry
let streamRegistry
const streamRegistry = new FinalizationRegistry((weakRef) => {
const stream = weakRef.deref()
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
stream.cancel('Response object has been garbage collected').catch(noop)
}
})
if (hasFinalizationRegistry) {
streamRegistry = new FinalizationRegistry((weakRef) => {
const stream = weakRef.deref()
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
stream.cancel('Response object has been garbage collected').catch(noop)
}
})
}
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
/**
* Extract a body with type from a byte sequence or BodyInit object
*
* @param {import('../../../types').BodyInit} object - The BodyInit object to extract from
* @param {boolean} [keepalive=false] - If true, indicates that the body
* @returns {[{stream: ReadableStream, source: any, length: number | null}, string | null]} - Returns a tuple containing the body and its type
*
* @see https://fetch.spec.whatwg.org/#concept-bodyinit-extract
*/
function extractBody (object, keepalive = false) {
// 1. Let stream be null.
let stream = null
@ -267,7 +271,22 @@ function extractBody (object, keepalive = false) {
return [body, type]
}
// https://fetch.spec.whatwg.org/#bodyinit-safely-extract
/**
* @typedef {object} ExtractBodyResult
* @property {ReadableStream<Uint8Array<ArrayBuffer>>} stream - The ReadableStream containing the body data
* @property {any} source - The original source of the body data
* @property {number | null} length - The length of the body data, or null
*/
/**
* Safely extract a body with type from a byte sequence or BodyInit object.
*
* @param {import('../../../types').BodyInit} object - The BodyInit object to extract from
* @param {boolean} [keepalive=false] - If true, indicates that the body
* @returns {[ExtractBodyResult, string | null]} - Returns a tuple containing the body and its type
*
* @see https://fetch.spec.whatwg.org/#bodyinit-safely-extract
*/
function safelyExtractBody (object, keepalive = false) {
// To safely extract a body and a `Content-Type` value from
// a byte sequence or BodyInit object object, run these steps:
@ -275,9 +294,7 @@ function safelyExtractBody (object, keepalive = false) {
// 1. If object is a ReadableStream object, then:
if (webidl.is.ReadableStream(object)) {
// Assert: object is neither disturbed nor locked.
// istanbul ignore next
assert(!util.isDisturbed(object), 'The body has already been consumed.')
// istanbul ignore next
assert(!object.locked, 'The stream is locked.')
}
@ -285,17 +302,13 @@ function safelyExtractBody (object, keepalive = false) {
return extractBody(object, keepalive)
}
function cloneBody (instance, body) {
function cloneBody (body) {
// To clone a body body, run these steps:
// https://fetch.spec.whatwg.org/#concept-body-clone
// 1. Let « out1, out2 » be the result of teeing bodys stream.
const [out1, out2] = body.stream.tee()
if (hasFinalizationRegistry) {
streamRegistry.register(instance, new WeakRef(out1))
}
const { 0: out1, 1: out2 } = body.stream.tee()
// 2. Set bodys stream to out1.
body.stream = out1
@ -527,6 +540,5 @@ module.exports = {
cloneBody,
mixinBody,
streamRegistry,
hasFinalizationRegistry,
bodyUnusable
}

View File

@ -1,5 +0,0 @@
'use strict'
module.exports = function () {
return { WeakRef, FinalizationRegistry }
}

View File

@ -30,7 +30,6 @@ const {
crossOriginResourcePolicyCheck,
determineRequestsReferrer,
coarsenedSharedCurrentTime,
createDeferredPromise,
sameOrigin,
isCancelled,
isAborted,
@ -63,6 +62,7 @@ const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = requ
const { getGlobalDispatcher } = require('../../global')
const { webidl } = require('../webidl')
const { STATUS_CODES } = require('node:http')
const { createDeferredPromise } = require('../../util/promise')
const GET_OR_HEAD = ['GET', 'HEAD']
const defaultUserAgent = typeof __UNDICI_IS_NODE__ !== 'undefined' || typeof esbuildDetection !== 'undefined'
@ -507,257 +507,258 @@ function fetching ({
}
// 16. Run main fetch given fetchParams.
mainFetch(fetchParams)
.catch(err => {
fetchParams.controller.terminate(err)
})
mainFetch(fetchParams, false)
// 17. Return fetchParam's controller
return fetchParams.controller
}
// https://fetch.spec.whatwg.org/#concept-main-fetch
async function mainFetch (fetchParams, recursive = false) {
// 1. Let request be fetchParamss request.
const request = fetchParams.request
async function mainFetch (fetchParams, recursive) {
try {
// 1. Let request be fetchParamss request.
const request = fetchParams.request
// 2. Let response be null.
let response = null
// 2. Let response be null.
let response = null
// 3. If requests local-URLs-only flag is set and requests current URL is
// not local, then set response to a network error.
if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
response = makeNetworkError('local URLs only')
}
// 3. If requests local-URLs-only flag is set and requests current URL is
// not local, then set response to a network error.
if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
response = makeNetworkError('local URLs only')
}
// 4. Run report Content Security Policy violations for request.
// TODO
// 4. Run report Content Security Policy violations for request.
// TODO
// 5. Upgrade request to a potentially trustworthy URL, if appropriate.
tryUpgradeRequestToAPotentiallyTrustworthyURL(request)
// 5. Upgrade request to a potentially trustworthy URL, if appropriate.
tryUpgradeRequestToAPotentiallyTrustworthyURL(request)
// 6. If should request be blocked due to a bad port, should fetching request
// be blocked as mixed content, or should request be blocked by Content
// Security Policy returns blocked, then set response to a network error.
if (requestBadPort(request) === 'blocked') {
response = makeNetworkError('bad port')
}
// TODO: should fetching request be blocked as mixed content?
// TODO: should request be blocked by Content Security Policy?
// 6. If should request be blocked due to a bad port, should fetching request
// be blocked as mixed content, or should request be blocked by Content
// Security Policy returns blocked, then set response to a network error.
if (requestBadPort(request) === 'blocked') {
response = makeNetworkError('bad port')
}
// TODO: should fetching request be blocked as mixed content?
// TODO: should request be blocked by Content Security Policy?
// 7. If requests referrer policy is the empty string, then set requests
// referrer policy to requests policy containers referrer policy.
if (request.referrerPolicy === '') {
request.referrerPolicy = request.policyContainer.referrerPolicy
}
// 7. If requests referrer policy is the empty string, then set requests
// referrer policy to requests policy containers referrer policy.
if (request.referrerPolicy === '') {
request.referrerPolicy = request.policyContainer.referrerPolicy
}
// 8. If requests referrer is not "no-referrer", then set requests
// referrer to the result of invoking determine requests referrer.
if (request.referrer !== 'no-referrer') {
request.referrer = determineRequestsReferrer(request)
}
// 8. If requests referrer is not "no-referrer", then set requests
// referrer to the result of invoking determine requests referrer.
if (request.referrer !== 'no-referrer') {
request.referrer = determineRequestsReferrer(request)
}
// 9. Set requests current URLs scheme to "https" if all of the following
// conditions are true:
// - requests current URLs scheme is "http"
// - requests current URLs host is a domain
// - Matching requests current URLs host per Known HSTS Host Domain Name
// Matching results in either a superdomain match with an asserted
// includeSubDomains directive or a congruent match (with or without an
// asserted includeSubDomains directive). [HSTS]
// TODO
// 9. Set requests current URLs scheme to "https" if all of the following
// conditions are true:
// - requests current URLs scheme is "http"
// - requests current URLs host is a domain
// - Matching requests current URLs host per Known HSTS Host Domain Name
// Matching results in either a superdomain match with an asserted
// includeSubDomains directive or a congruent match (with or without an
// asserted includeSubDomains directive). [HSTS]
// TODO
// 10. If recursive is false, then run the remaining steps in parallel.
// TODO
// 10. If recursive is false, then run the remaining steps in parallel.
// TODO
// 11. If response is null, then set response to the result of running
// the steps corresponding to the first matching statement:
if (response === null) {
const currentURL = requestCurrentURL(request)
if (
// - requests current URLs origin is same origin with requests origin,
// and requests response tainting is "basic"
(sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') ||
// requests current URLs scheme is "data"
(currentURL.protocol === 'data:') ||
// - requests mode is "navigate" or "websocket"
(request.mode === 'navigate' || request.mode === 'websocket')
) {
// 1. Set requests response tainting to "basic".
request.responseTainting = 'basic'
// 11. If response is null, then set response to the result of running
// the steps corresponding to the first matching statement:
if (response === null) {
const currentURL = requestCurrentURL(request)
if (
// - requests current URLs origin is same origin with requests origin,
// and requests response tainting is "basic"
(sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') ||
// requests current URLs scheme is "data"
(currentURL.protocol === 'data:') ||
// - requests mode is "navigate" or "websocket"
(request.mode === 'navigate' || request.mode === 'websocket')
) {
// 1. Set requests response tainting to "basic".
request.responseTainting = 'basic'
// 2. Return the result of running scheme fetch given fetchParams.
response = await schemeFetch(fetchParams)
// requests mode is "same-origin"
} else if (request.mode === 'same-origin') {
// 1. Return a network error.
response = makeNetworkError('request mode cannot be "same-origin"')
// requests mode is "no-cors"
} else if (request.mode === 'no-cors') {
// 1. If requests redirect mode is not "follow", then return a network
// error.
if (request.redirect !== 'follow') {
response = makeNetworkError(
'redirect mode cannot be "follow" for "no-cors" request'
)
} else {
// 2. Set requests response tainting to "opaque".
request.responseTainting = 'opaque'
// 3. Return the result of running scheme fetch given fetchParams.
// 2. Return the result of running scheme fetch given fetchParams.
response = await schemeFetch(fetchParams)
// requests mode is "same-origin"
} else if (request.mode === 'same-origin') {
// 1. Return a network error.
response = makeNetworkError('request mode cannot be "same-origin"')
// requests mode is "no-cors"
} else if (request.mode === 'no-cors') {
// 1. If requests redirect mode is not "follow", then return a network
// error.
if (request.redirect !== 'follow') {
response = makeNetworkError(
'redirect mode cannot be "follow" for "no-cors" request'
)
} else {
// 2. Set requests response tainting to "opaque".
request.responseTainting = 'opaque'
// 3. Return the result of running scheme fetch given fetchParams.
response = await schemeFetch(fetchParams)
}
// requests current URLs scheme is not an HTTP(S) scheme
} else if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
// Return a network error.
response = makeNetworkError('URL scheme must be a HTTP(S) scheme')
// - requests use-CORS-preflight flag is set
// - requests unsafe-request flag is set and either requests method is
// not a CORS-safelisted method or CORS-unsafe request-header names with
// requests header list is not empty
// 1. Set requests response tainting to "cors".
// 2. Let corsWithPreflightResponse be the result of running HTTP fetch
// given fetchParams and true.
// 3. If corsWithPreflightResponse is a network error, then clear cache
// entries using request.
// 4. Return corsWithPreflightResponse.
// TODO
// Otherwise
} else {
// 1. Set requests response tainting to "cors".
request.responseTainting = 'cors'
// 2. Return the result of running HTTP fetch given fetchParams.
response = await httpFetch(fetchParams)
}
// requests current URLs scheme is not an HTTP(S) scheme
} else if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
// Return a network error.
response = makeNetworkError('URL scheme must be a HTTP(S) scheme')
// - requests use-CORS-preflight flag is set
// - requests unsafe-request flag is set and either requests method is
// not a CORS-safelisted method or CORS-unsafe request-header names with
// requests header list is not empty
// 1. Set requests response tainting to "cors".
// 2. Let corsWithPreflightResponse be the result of running HTTP fetch
// given fetchParams and true.
// 3. If corsWithPreflightResponse is a network error, then clear cache
// entries using request.
// 4. Return corsWithPreflightResponse.
// TODO
// Otherwise
} else {
// 1. Set requests response tainting to "cors".
request.responseTainting = 'cors'
// 2. Return the result of running HTTP fetch given fetchParams.
response = await httpFetch(fetchParams)
}
}
// 12. If recursive is true, then return response.
if (recursive) {
return response
}
// 13. If response is not a network error and response is not a filtered
// response, then:
if (response.status !== 0 && !response.internalResponse) {
// If requests response tainting is "cors", then:
if (request.responseTainting === 'cors') {
// 1. Let headerNames be the result of extracting header list values
// given `Access-Control-Expose-Headers` and responses header list.
// TODO
// 2. If requests credentials mode is not "include" and headerNames
// contains `*`, then set responses CORS-exposed header-name list to
// all unique header names in responses header list.
// TODO
// 3. Otherwise, if headerNames is not null or failure, then set
// responses CORS-exposed header-name list to headerNames.
// TODO
}
// Set response to the following filtered response with response as its
// internal response, depending on requests response tainting:
if (request.responseTainting === 'basic') {
response = filterResponse(response, 'basic')
} else if (request.responseTainting === 'cors') {
response = filterResponse(response, 'cors')
} else if (request.responseTainting === 'opaque') {
response = filterResponse(response, 'opaque')
} else {
assert(false)
}
}
// 14. Let internalResponse be response, if response is a network error,
// and responses internal response otherwise.
let internalResponse =
response.status === 0 ? response : response.internalResponse
// 15. If internalResponses URL list is empty, then set it to a clone of
// requests URL list.
if (internalResponse.urlList.length === 0) {
internalResponse.urlList.push(...request.urlList)
}
// 16. If requests timing allow failed flag is unset, then set
// internalResponses timing allow passed flag.
if (!request.timingAllowFailed) {
response.timingAllowPassed = true
}
// 17. If response is not a network error and any of the following returns
// blocked
// - should internalResponse to request be blocked as mixed content
// - should internalResponse to request be blocked by Content Security Policy
// - should internalResponse to request be blocked due to its MIME type
// - should internalResponse to request be blocked due to nosniff
// TODO
// 18. If responses type is "opaque", internalResponses status is 206,
// internalResponses range-requested flag is set, and requests header
// list does not contain `Range`, then set response and internalResponse
// to a network error.
if (
response.type === 'opaque' &&
internalResponse.status === 206 &&
internalResponse.rangeRequested &&
!request.headers.contains('range', true)
) {
response = internalResponse = makeNetworkError()
}
// 19. If response is not a network error and either requests method is
// `HEAD` or `CONNECT`, or internalResponses status is a null body status,
// set internalResponses body to null and disregard any enqueuing toward
// it (if any).
if (
response.status !== 0 &&
(request.method === 'HEAD' ||
request.method === 'CONNECT' ||
nullBodyStatus.includes(internalResponse.status))
) {
internalResponse.body = null
fetchParams.controller.dump = true
}
// 20. If requests integrity metadata is not the empty string, then:
if (request.integrity) {
// 1. Let processBodyError be this step: run fetch finale given fetchParams
// and a network error.
const processBodyError = (reason) =>
fetchFinale(fetchParams, makeNetworkError(reason))
// 2. If requests response tainting is "opaque", or responses body is null,
// then run processBodyError and abort these steps.
if (request.responseTainting === 'opaque' || response.body == null) {
processBodyError(response.error)
return
// 12. If recursive is true, then return response.
if (recursive) {
return response
}
// 3. Let processBody given bytes be these steps:
const processBody = (bytes) => {
// 1. If bytes do not match requests integrity metadata,
// then run processBodyError and abort these steps. [SRI]
if (!bytesMatch(bytes, request.integrity)) {
processBodyError('integrity mismatch')
// 13. If response is not a network error and response is not a filtered
// response, then:
if (response.status !== 0 && !response.internalResponse) {
// If requests response tainting is "cors", then:
if (request.responseTainting === 'cors') {
// 1. Let headerNames be the result of extracting header list values
// given `Access-Control-Expose-Headers` and responses header list.
// TODO
// 2. If requests credentials mode is not "include" and headerNames
// contains `*`, then set responses CORS-exposed header-name list to
// all unique header names in responses header list.
// TODO
// 3. Otherwise, if headerNames is not null or failure, then set
// responses CORS-exposed header-name list to headerNames.
// TODO
}
// Set response to the following filtered response with response as its
// internal response, depending on requests response tainting:
if (request.responseTainting === 'basic') {
response = filterResponse(response, 'basic')
} else if (request.responseTainting === 'cors') {
response = filterResponse(response, 'cors')
} else if (request.responseTainting === 'opaque') {
response = filterResponse(response, 'opaque')
} else {
assert(false)
}
}
// 14. Let internalResponse be response, if response is a network error,
// and responses internal response otherwise.
let internalResponse =
response.status === 0 ? response : response.internalResponse
// 15. If internalResponses URL list is empty, then set it to a clone of
// requests URL list.
if (internalResponse.urlList.length === 0) {
internalResponse.urlList.push(...request.urlList)
}
// 16. If requests timing allow failed flag is unset, then set
// internalResponses timing allow passed flag.
if (!request.timingAllowFailed) {
response.timingAllowPassed = true
}
// 17. If response is not a network error and any of the following returns
// blocked
// - should internalResponse to request be blocked as mixed content
// - should internalResponse to request be blocked by Content Security Policy
// - should internalResponse to request be blocked due to its MIME type
// - should internalResponse to request be blocked due to nosniff
// TODO
// 18. If responses type is "opaque", internalResponses status is 206,
// internalResponses range-requested flag is set, and requests header
// list does not contain `Range`, then set response and internalResponse
// to a network error.
if (
response.type === 'opaque' &&
internalResponse.status === 206 &&
internalResponse.rangeRequested &&
!request.headers.contains('range', true)
) {
response = internalResponse = makeNetworkError()
}
// 19. If response is not a network error and either requests method is
// `HEAD` or `CONNECT`, or internalResponses status is a null body status,
// set internalResponses body to null and disregard any enqueuing toward
// it (if any).
if (
response.status !== 0 &&
(request.method === 'HEAD' ||
request.method === 'CONNECT' ||
nullBodyStatus.includes(internalResponse.status))
) {
internalResponse.body = null
fetchParams.controller.dump = true
}
// 20. If requests integrity metadata is not the empty string, then:
if (request.integrity) {
// 1. Let processBodyError be this step: run fetch finale given fetchParams
// and a network error.
const processBodyError = (reason) =>
fetchFinale(fetchParams, makeNetworkError(reason))
// 2. If requests response tainting is "opaque", or responses body is null,
// then run processBodyError and abort these steps.
if (request.responseTainting === 'opaque' || response.body == null) {
processBodyError(response.error)
return
}
// 2. Set responses body to bytes as a body.
response.body = safelyExtractBody(bytes)[0]
// 3. Let processBody given bytes be these steps:
const processBody = (bytes) => {
// 1. If bytes do not match requests integrity metadata,
// then run processBodyError and abort these steps. [SRI]
if (!bytesMatch(bytes, request.integrity)) {
processBodyError('integrity mismatch')
return
}
// 3. Run fetch finale given fetchParams and response.
// 2. Set responses body to bytes as a body.
response.body = safelyExtractBody(bytes)[0]
// 3. Run fetch finale given fetchParams and response.
fetchFinale(fetchParams, response)
}
// 4. Fully read responses body given processBody and processBodyError.
fullyReadBody(response.body, processBody, processBodyError)
} else {
// 21. Otherwise, run fetch finale given fetchParams and response.
fetchFinale(fetchParams, response)
}
// 4. Fully read responses body given processBody and processBodyError.
await fullyReadBody(response.body, processBody, processBodyError)
} else {
// 21. Otherwise, run fetch finale given fetchParams and response.
fetchFinale(fetchParams, response)
} catch (err) {
fetchParams.controller.terminate(err)
}
}
@ -1909,15 +1910,11 @@ async function httpNetworkFetch (
// cancelAlgorithm set to cancelAlgorithm.
const stream = new ReadableStream(
{
async start (controller) {
start (controller) {
fetchParams.controller.controller = controller
},
async pull (controller) {
await pullAlgorithm(controller)
},
async cancel (reason) {
await cancelAlgorithm(reason)
},
pull: pullAlgorithm,
cancel: cancelAlgorithm,
type: 'bytes'
}
)
@ -2055,7 +2052,7 @@ async function httpNetworkFetch (
function dispatch ({ body }) {
const url = requestCurrentURL(request)
/** @type {import('../..').Agent} */
/** @type {import('../../..').Agent} */
const agent = fetchParams.controller.dispatcher
return new Promise((resolve, reject) => agent.dispatch(
@ -2104,12 +2101,11 @@ async function httpNetworkFetch (
onHeaders (status, rawHeaders, resume, statusText) {
if (status < 200) {
return
return false
}
/** @type {string[]} */
let codings = []
let location = ''
const headersList = new HeadersList()
@ -2122,7 +2118,7 @@ async function httpNetworkFetch (
// "All content-coding values are case-insensitive..."
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
}
location = headersList.get('location', true)
const location = headersList.get('location', true)
this.body = new Readable({ read: resume })

View File

@ -4,7 +4,6 @@
const { extractBody, mixinBody, cloneBody, bodyUnusable } = require('./body')
const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require('./headers')
const { FinalizationRegistry } = require('./dispatcher-weakref')()
const util = require('../../core/util')
const nodeUtil = require('node:util')
const {
@ -109,8 +108,8 @@ class Request {
const prefix = 'Request constructor'
webidl.argumentLengthCheck(arguments, 1, prefix)
input = webidl.converters.RequestInfo(input, prefix, 'input')
init = webidl.converters.RequestInit(init, prefix, 'init')
input = webidl.converters.RequestInfo(input)
init = webidl.converters.RequestInit(init)
// 1. Let request be null.
let request = null
@ -937,7 +936,7 @@ function cloneRequest (request) {
// 2. If requests body is non-null, set newRequests body to the
// result of cloning requests body.
if (request.body != null) {
newRequest.body = cloneBody(newRequest, request.body)
newRequest.body = cloneBody(request.body)
}
// 3. Return newRequest.
@ -993,8 +992,13 @@ Object.defineProperties(Request.prototype, {
webidl.is.Request = webidl.util.MakeTypeAssertion(Request)
// https://fetch.spec.whatwg.org/#requestinfo
webidl.converters.RequestInfo = function (V, prefix, argument) {
/**
* @param {*} V
* @returns {import('../../../types/fetch').Request|string}
*
* @see https://fetch.spec.whatwg.org/#requestinfo
*/
webidl.converters.RequestInfo = function (V) {
if (typeof V === 'string') {
return webidl.converters.USVString(V)
}
@ -1006,7 +1010,11 @@ webidl.converters.RequestInfo = function (V, prefix, argument) {
return webidl.converters.USVString(V)
}
// https://fetch.spec.whatwg.org/#requestinit
/**
* @param {*} V
* @returns {import('../../../types/fetch').RequestInit}
* @see https://fetch.spec.whatwg.org/#requestinit
*/
webidl.converters.RequestInit = webidl.dictionaryConverter([
{
key: 'method',

View File

@ -1,7 +1,7 @@
'use strict'
const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
const { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require('./body')
const { extractBody, cloneBody, mixinBody, streamRegistry, bodyUnusable } = require('./body')
const util = require('../../core/util')
const nodeUtil = require('node:util')
const { kEnumerableProperty } = util
@ -352,7 +352,9 @@ function cloneResponse (response) {
// 3. If responses body is non-null, then set newResponses body to the
// result of cloning responses body.
if (response.body != null) {
newResponse.body = cloneBody(newResponse, response.body)
newResponse.body = cloneBody(response.body)
streamRegistry.register(newResponse, new WeakRef(response.body.stream))
}
// 4. Return newResponse.
@ -552,7 +554,7 @@ function fromInnerResponse (innerResponse, guard) {
setHeadersList(headers, innerResponse.headersList)
setHeadersGuard(headers, guard)
if (hasFinalizationRegistry && innerResponse.body?.stream) {
if (innerResponse.body?.stream) {
// If the target (response) is reclaimed, the cleanup callback may be called at some point with
// the held value provided for it (innerResponse.body.stream). The held value can be any value:
// a primitive or an object, even undefined. If the held value is an object, the registry keeps

View File

@ -924,17 +924,6 @@ function sameOrigin (A, B) {
return false
}
function createDeferredPromise () {
let res
let rej
const promise = new Promise((resolve, reject) => {
res = resolve
rej = reject
})
return { promise, resolve: res, reject: rej }
}
function isAborted (fetchParams) {
return fetchParams.controller.state === 'aborted'
}
@ -1177,6 +1166,11 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde
}
/**
* @param {import('./body').ExtractBodyResult} body
* @param {(bytes: Uint8Array) => void} processBody
* @param {(error: Error) => void} processBodyError
* @returns {void}
*
* @see https://fetch.spec.whatwg.org/#body-fully-read
*/
function fullyReadBody (body, processBody, processBodyError) {
@ -1191,20 +1185,17 @@ function fullyReadBody (body, processBody, processBodyError) {
// with taskDestination.
const errorSteps = processBodyError
try {
// 4. Let reader be the result of getting a reader for bodys stream.
// If that threw an exception, then run errorSteps with that
// exception and return.
let reader
const reader = body.stream.getReader()
try {
reader = body.stream.getReader()
// 5. Read all bytes from reader, given successSteps and errorSteps.
readAllBytes(reader, successSteps, errorSteps)
} catch (e) {
errorSteps(e)
return
}
// 5. Read all bytes from reader, given successSteps and errorSteps.
readAllBytes(reader, successSteps, errorSteps)
}
/**
@ -1241,15 +1232,16 @@ function isomorphicEncode (input) {
/**
* @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
* @see https://streams.spec.whatwg.org/#read-loop
* @param {ReadableStreamDefaultReader} reader
* @param {ReadableStream<Uint8Array<ArrayBuffer>>} reader
* @param {(bytes: Uint8Array) => void} successSteps
* @param {(error: Error) => void} failureSteps
* @returns {Promise<void>}
*/
async function readAllBytes (reader, successSteps, failureSteps) {
const bytes = []
let byteLength = 0
try {
const bytes = []
let byteLength = 0
do {
const { done, value: chunk } = await reader.read()
@ -1324,10 +1316,17 @@ function urlIsHttpHttpsScheme (url) {
return protocol === 'http:' || protocol === 'https:'
}
/**
* @typedef {Object} RangeHeaderValue
* @property {number|null} rangeStartValue
* @property {number|null} rangeEndValue
*/
/**
* @see https://fetch.spec.whatwg.org/#simple-range-header-value
* @param {string} value
* @param {boolean} allowWhitespace
* @return {RangeHeaderValue|'failure'}
*/
function simpleRangeHeaderValue (value, allowWhitespace) {
// 1. Let data be the isomorphic decoding of value.
@ -1732,7 +1731,6 @@ module.exports = {
isAborted,
isCancelled,
isValidEncodedURL,
createDeferredPromise,
ReadableStreamFrom,
tryUpgradeRequestToAPotentiallyTrustworthyURL,
clampAndCoarsenConnectionTimingInfo,

View File

@ -2,7 +2,6 @@
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
const { parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
const { channels } = require('../../core/diagnostics')
const { makeRequest } = require('../fetch/request')
const { fetching } = require('../fetch/index')
const { Headers, getHeadersList } = require('../fetch/headers')
@ -200,14 +199,6 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
response.socket.on('close', handler.onSocketClose)
response.socket.on('error', handler.onSocketError)
if (channels.open.hasSubscribers) {
channels.open.publish({
address: response.socket.address(),
protocol: secProtocol,
extensions: secExtension
})
}
handler.wasEverConnected = true
handler.onConnectionEstablished(response, extensions)
}

View File

@ -3,7 +3,6 @@
const { Writable } = require('node:stream')
const assert = require('node:assert')
const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require('./constants')
const { channels } = require('../../core/diagnostics')
const {
isValidStatusCode,
isValidOpcode,
@ -423,22 +422,13 @@ class ByteParser extends Writable {
this.#handler.socket.write(frame.createFrame(opcodes.PONG))
if (channels.ping.hasSubscribers) {
channels.ping.publish({
payload: body
})
}
this.#handler.onPing(body)
}
} else if (opcode === opcodes.PONG) {
// A Pong frame MAY be sent unsolicited. This serves as a
// unidirectional heartbeat. A response to an unsolicited Pong frame is
// not expected.
if (channels.pong.hasSubscribers) {
channels.pong.publish({
payload: body
})
}
this.#handler.onPong(body)
}
return true

View File

@ -1,6 +1,7 @@
'use strict'
const { createDeferredPromise, environmentSettingsObject } = require('../../fetch/util')
const { createDeferredPromise } = require('../../../util/promise')
const { environmentSettingsObject } = require('../../fetch/util')
const { states, opcodes, sentCloseFrameState } = require('../constants')
const { webidl } = require('../../webidl')
const { getURLRecord, isValidSubprotocol, isEstablished, utf8Decode } = require('../util')
@ -21,11 +22,11 @@ class WebSocketStream {
#url
// Each WebSocketStream object has an associated opened promise , which is a promise.
/** @type {ReturnType<typeof createDeferredPromise>} */
/** @type {import('../../../util/promise').DeferredPromise} */
#openedPromise
// Each WebSocketStream object has an associated closed promise , which is a promise.
/** @type {ReturnType<typeof createDeferredPromise>} */
/** @type {import('../../../util/promise').DeferredPromise} */
#closedPromise
// Each WebSocketStream object has an associated readable stream , which is a ReadableStream .
@ -64,6 +65,8 @@ class WebSocketStream {
this.#handler.socket.destroy()
},
onSocketClose: () => this.#onSocketClose(),
onPing: () => {},
onPong: () => {},
readyState: states.CONNECTING,
socket: null,
@ -388,7 +391,7 @@ class WebSocketStream {
// 6. If the connection was closed cleanly ,
if (wasClean) {
// 6.1. Close stream s readable stream .
this.#readableStream.cancel().catch(() => {})
this.#readableStreamController.close()
// 6.2. Error stream s writable stream with an " InvalidStateError " DOMException indicating that a closed WebSocketStream cannot be written to.
if (!this.#writableStream.locked) {

View File

@ -8,6 +8,7 @@ const {
isConnecting,
isEstablished,
isClosing,
isClosed,
isValidSubprotocol,
fireEvent,
utf8Decode,
@ -21,6 +22,7 @@ const { getGlobalDispatcher } = require('../../global')
const { types } = require('node:util')
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
const { SendQueue } = require('./sender')
const { WebsocketFrameSend } = require('./frame')
const { channels } = require('../../core/diagnostics')
/**
@ -33,6 +35,8 @@ const { channels } = require('../../core/diagnostics')
* @property {(chunk: Buffer) => void} onSocketData
* @property {(err: Error) => void} onSocketError
* @property {() => void} onSocketClose
* @property {(body: Buffer) => void} onPing
* @property {(body: Buffer) => void} onPong
*
* @property {number} readyState
* @property {import('stream').Duplex} socket
@ -79,6 +83,22 @@ class WebSocket extends EventTarget {
this.#handler.socket.destroy()
},
onSocketClose: () => this.#onSocketClose(),
onPing: (body) => {
if (channels.ping.hasSubscribers) {
channels.ping.publish({
payload: body,
websocket: this
})
}
},
onPong: (body) => {
if (channels.pong.hasSubscribers) {
channels.pong.publish({
payload: body,
websocket: this
})
}
},
readyState: states.CONNECTING,
socket: null,
@ -460,6 +480,15 @@ class WebSocket extends EventTarget {
// 4. Fire an event named open at the WebSocket object.
fireEvent('open', this)
if (channels.open.hasSubscribers) {
channels.open.publish({
address: response.socket.address(),
protocol: this.#protocol,
extensions: this.#extensions,
websocket: this
})
}
}
#onFail (code, reason, cause) {
@ -586,8 +615,34 @@ class WebSocket extends EventTarget {
})
}
}
/**
* @param {WebSocket} ws
* @param {Buffer|undefined} buffer
*/
static ping (ws, buffer) {
if (Buffer.isBuffer(buffer)) {
if (buffer.length > 125) {
throw new TypeError('A PING frame cannot have a body larger than 125 bytes.')
}
} else if (buffer !== undefined) {
throw new TypeError('Expected buffer payload')
}
// An endpoint MAY send a Ping frame any time after the connection is
// established and before the connection is closed.
const readyState = ws.#handler.readyState
if (isEstablished(readyState) && !isClosing(readyState) && !isClosed(readyState)) {
const frame = new WebsocketFrameSend(buffer)
ws.#handler.socket.write(frame.createFrame(opcodes.PING))
}
}
}
const { ping } = WebSocket
Reflect.deleteProperty(WebSocket, 'ping')
// https://websockets.spec.whatwg.org/#dom-websocket-connecting
WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING
// https://websockets.spec.whatwg.org/#dom-websocket-open
@ -682,5 +737,6 @@ webidl.converters.WebSocketSendData = function (V) {
}
module.exports = {
WebSocket
WebSocket,
ping
}

796
deps/undici/src/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "undici",
"version": "7.11.0",
"version": "7.12.0",
"description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org",
"bugs": {
@ -91,7 +91,7 @@
"test:tdd:node-test": "borp -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:websocket": "borp -p \"test/websocket/**/*.js\"",
"test:websocket:autobahn": "node test/autobahn/client.js",
"test:websocket:autobahn:report": "node test/autobahn/report.js",
"test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",

View File

@ -182,6 +182,16 @@ interface WebidlConverters {
['record<ByteString, ByteString>']: RecordConverter<string, string>
/**
* @see https://fetch.spec.whatwg.org/#requestinfo
*/
RequestInfo (V: unknown): undici.Request | string
/**
* @see https://fetch.spec.whatwg.org/#requestinit
*/
RequestInit (V: unknown): undici.RequestInit
[Key: string]: (...args: any[]) => unknown
}

View File

@ -182,3 +182,5 @@ export declare const WebSocketError: {
prototype: WebSocketError
new (type: string, init?: WebSocketCloseInfo): WebSocketError
}
export declare const ping: (ws: WebSocket, body?: Buffer) => void

391
deps/undici/undici.js vendored
View File

@ -947,7 +947,7 @@ var require_tree = __commonJS({
}
/**
* @param {Uint8Array} key
* @return {TstNode | null}
* @returns {TstNode | null}
*/
search(key) {
const keylength = key.length;
@ -2359,7 +2359,8 @@ var require_request = __commonJS({
reset,
expectContinue,
servername,
throwOnError
throwOnError,
maxRedirections
}, handler) {
if (typeof path !== "string") {
throw new InvalidArgumentError("path must be a string");
@ -2391,6 +2392,9 @@ var require_request = __commonJS({
if (throwOnError != null) {
throw new InvalidArgumentError("invalid throwOnError");
}
if (maxRedirections != null && maxRedirections !== 0) {
throw new InvalidArgumentError("maxRedirections is not supported, use the redirect interceptor");
}
this.headersTimeout = headersTimeout;
this.bodyTimeout = bodyTimeout;
this.method = method;
@ -4970,16 +4974,6 @@ var require_util2 = __commonJS({
return false;
}
__name(sameOrigin, "sameOrigin");
function createDeferredPromise() {
let res;
let rej;
const promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
return { promise, resolve: res, reject: rej };
}
__name(createDeferredPromise, "createDeferredPromise");
function isAborted(fetchParams) {
return fetchParams.controller.state === "aborted";
}
@ -5136,14 +5130,12 @@ var require_util2 = __commonJS({
function fullyReadBody(body, processBody, processBodyError) {
const successSteps = processBody;
const errorSteps = processBodyError;
let reader;
try {
reader = body.stream.getReader();
const reader = body.stream.getReader();
readAllBytes(reader, successSteps, errorSteps);
} catch (e) {
errorSteps(e);
return;
}
readAllBytes(reader, successSteps, errorSteps);
}
__name(fullyReadBody, "fullyReadBody");
function readableStreamClose(controller) {
@ -5164,9 +5156,9 @@ var require_util2 = __commonJS({
}
__name(isomorphicEncode, "isomorphicEncode");
async function readAllBytes(reader, successSteps, failureSteps) {
const bytes = [];
let byteLength = 0;
try {
const bytes = [];
let byteLength = 0;
do {
const { done, value: chunk } = await reader.read();
if (done) {
@ -5423,7 +5415,6 @@ var require_util2 = __commonJS({
isAborted,
isCancelled,
isValidEncodedURL,
createDeferredPromise,
ReadableStreamFrom,
tryUpgradeRequestToAPotentiallyTrustworthyURL,
clampAndCoarsenConnectionTimingInfo,
@ -5921,6 +5912,26 @@ var require_formdata_parser = __commonJS({
}
});
// lib/util/promise.js
var require_promise = __commonJS({
"lib/util/promise.js"(exports2, module2) {
"use strict";
function createDeferredPromise() {
let res;
let rej;
const promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
return { promise, resolve: res, reject: rej };
}
__name(createDeferredPromise, "createDeferredPromise");
module2.exports = {
createDeferredPromise
};
}
});
// lib/web/fetch/body.js
var require_body = __commonJS({
"lib/web/fetch/body.js"(exports2, module2) {
@ -5929,7 +5940,6 @@ var require_body = __commonJS({
var {
ReadableStreamFrom,
readableStreamClose,
createDeferredPromise,
fullyReadBody,
extractMimeType,
utf8DecodeBytes
@ -5942,6 +5952,7 @@ var require_body = __commonJS({
var { isArrayBuffer } = require("node:util/types");
var { serializeAMimeType } = require_data_url();
var { multipartFormDataParser } = require_formdata_parser();
var { createDeferredPromise } = require_promise();
var random;
try {
const crypto = require("node:crypto");
@ -5953,16 +5964,12 @@ var require_body = __commonJS({
function noop() {
}
__name(noop, "noop");
var hasFinalizationRegistry = globalThis.FinalizationRegistry;
var streamRegistry;
if (hasFinalizationRegistry) {
streamRegistry = new FinalizationRegistry((weakRef) => {
const stream = weakRef.deref();
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
stream.cancel("Response object has been garbage collected").catch(noop);
}
});
}
var streamRegistry = new FinalizationRegistry((weakRef) => {
const stream = weakRef.deref();
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
stream.cancel("Response object has been garbage collected").catch(noop);
}
});
function extractBody(object, keepalive = false) {
let stream = null;
if (webidl.is.ReadableStream(object)) {
@ -6108,11 +6115,8 @@ Content-Type: ${value.type || "application/octet-stream"}\r
return extractBody(object, keepalive);
}
__name(safelyExtractBody, "safelyExtractBody");
function cloneBody(instance, body) {
const [out1, out2] = body.stream.tee();
if (hasFinalizationRegistry) {
streamRegistry.register(instance, new WeakRef(out1));
}
function cloneBody(body) {
const { 0: out1, 1: out2 } = body.stream.tee();
body.stream = out1;
return {
stream: out2,
@ -6238,7 +6242,6 @@ Content-Type: ${value.type || "application/octet-stream"}\r
cloneBody,
mixinBody,
streamRegistry,
hasFinalizationRegistry,
bodyUnusable
};
}
@ -6303,15 +6306,15 @@ var require_client_h1 = __commonJS({
var FastBuffer = Buffer[Symbol.species];
var removeAllListeners = util.removeAllListeners;
var extractBody;
async function lazyllhttp() {
function lazyllhttp() {
const llhttpWasmData = process.env.JEST_WORKER_ID ? require_llhttp_wasm() : void 0;
let mod;
try {
mod = await WebAssembly.compile(require_llhttp_simd_wasm());
mod = new WebAssembly.Module(require_llhttp_simd_wasm());
} catch (e) {
mod = await WebAssembly.compile(llhttpWasmData || require_llhttp_wasm());
mod = new WebAssembly.Module(llhttpWasmData || require_llhttp_wasm());
}
return await WebAssembly.instantiate(mod, {
return new WebAssembly.Instance(mod, {
env: {
/**
* @param {number} p
@ -6398,8 +6401,6 @@ var require_client_h1 = __commonJS({
}
__name(lazyllhttp, "lazyllhttp");
var llhttpInstance = null;
var llhttpPromise = lazyllhttp();
llhttpPromise.catch();
var currentParser = null;
var currentBufferRef = null;
var currentBufferSize = 0;
@ -6811,7 +6812,7 @@ var require_client_h1 = __commonJS({
util.destroy(socket, new InformationalError("reset"));
return constants.ERROR.PAUSED;
} else if (client[kPipelining] == null || client[kPipelining] === 1) {
setImmediate(() => client[kResume]());
setImmediate(client[kResume]);
} else {
client[kResume]();
}
@ -6838,12 +6839,7 @@ var require_client_h1 = __commonJS({
async function connectH1(client, socket) {
client[kSocket] = socket;
if (!llhttpInstance) {
const noop = /* @__PURE__ */ __name(() => {
}, "noop");
socket.on("error", noop);
llhttpInstance = await llhttpPromise;
llhttpPromise = null;
socket.off("error", noop);
llhttpInstance = lazyllhttp();
}
if (socket.errored) {
throw socket.errored;
@ -7185,9 +7181,9 @@ upgrade: ${upgrade}\r
}
socket.on("drain", onDrain).on("error", onFinished);
if (body.errorEmitted ?? body.errored) {
setImmediate(() => onFinished(body.errored));
setImmediate(onFinished, body.errored);
} else if (body.endEmitted ?? body.readableEnded) {
setImmediate(() => onFinished(null));
setImmediate(onFinished, null);
}
if (body.closeEmitted ?? body.closed) {
setImmediate(onClose);
@ -9636,7 +9632,7 @@ var require_response = __commonJS({
"lib/web/fetch/response.js"(exports2, module2) {
"use strict";
var { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require_headers();
var { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require_body();
var { extractBody, cloneBody, mixinBody, streamRegistry, bodyUnusable } = require_body();
var util = require_util();
var nodeUtil = require("node:util");
var { kEnumerableProperty } = util;
@ -9867,7 +9863,8 @@ var require_response = __commonJS({
}
const newResponse = makeResponse({ ...response, body: null });
if (response.body != null) {
newResponse.body = cloneBody(newResponse, response.body);
newResponse.body = cloneBody(response.body);
streamRegistry.register(newResponse, new WeakRef(response.body.stream));
}
return newResponse;
}
@ -10000,7 +9997,7 @@ var require_response = __commonJS({
setResponseHeaders(response, headers);
setHeadersList(headers, innerResponse.headersList);
setHeadersGuard(headers, guard);
if (hasFinalizationRegistry && innerResponse.body?.stream) {
if (innerResponse.body?.stream) {
streamRegistry.register(response, new WeakRef(innerResponse.body.stream));
}
return response;
@ -10064,23 +10061,12 @@ var require_response = __commonJS({
}
});
// lib/web/fetch/dispatcher-weakref.js
var require_dispatcher_weakref = __commonJS({
"lib/web/fetch/dispatcher-weakref.js"(exports2, module2) {
"use strict";
module2.exports = function() {
return { WeakRef, FinalizationRegistry };
};
}
});
// lib/web/fetch/request.js
var require_request2 = __commonJS({
"lib/web/fetch/request.js"(exports2, module2) {
"use strict";
var { extractBody, mixinBody, cloneBody, bodyUnusable } = require_body();
var { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require_headers();
var { FinalizationRegistry: FinalizationRegistry2 } = require_dispatcher_weakref()();
var util = require_util();
var nodeUtil = require("node:util");
var {
@ -10105,7 +10091,7 @@ var require_request2 = __commonJS({
var assert = require("node:assert");
var { getMaxListeners, setMaxListeners, defaultMaxListeners } = require("node:events");
var kAbortController = Symbol("abortController");
var requestFinalizer = new FinalizationRegistry2(({ signal, abort }) => {
var requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
signal.removeEventListener("abort", abort);
});
var dependentControllerMap = /* @__PURE__ */ new WeakMap();
@ -10161,8 +10147,8 @@ var require_request2 = __commonJS({
}
const prefix = "Request constructor";
webidl.argumentLengthCheck(arguments, 1, prefix);
input = webidl.converters.RequestInfo(input, prefix, "input");
init = webidl.converters.RequestInit(init, prefix, "init");
input = webidl.converters.RequestInfo(input);
init = webidl.converters.RequestInit(init);
let request = null;
let fallbackMode = null;
const baseUrl = environmentSettingsObject.settingsObject.baseUrl;
@ -10685,7 +10671,7 @@ var require_request2 = __commonJS({
function cloneRequest(request) {
const newRequest = makeRequest({ ...request, body: null });
if (request.body != null) {
newRequest.body = cloneBody(newRequest, request.body);
newRequest.body = cloneBody(request.body);
}
return newRequest;
}
@ -10729,7 +10715,7 @@ var require_request2 = __commonJS({
}
});
webidl.is.Request = webidl.util.MakeTypeAssertion(Request);
webidl.converters.RequestInfo = function(V, prefix, argument) {
webidl.converters.RequestInfo = function(V) {
if (typeof V === "string") {
return webidl.converters.USVString(V);
}
@ -10863,7 +10849,6 @@ var require_fetch = __commonJS({
crossOriginResourcePolicyCheck,
determineRequestsReferrer,
coarsenedSharedCurrentTime,
createDeferredPromise,
sameOrigin,
isCancelled,
isAborted,
@ -10896,6 +10881,7 @@ var require_fetch = __commonJS({
var { getGlobalDispatcher: getGlobalDispatcher2 } = require_global2();
var { webidl } = require_webidl();
var { STATUS_CODES } = require("node:http");
var { createDeferredPromise } = require_promise();
var GET_OR_HEAD = ["GET", "HEAD"];
var defaultUserAgent = typeof __UNDICI_IS_NODE__ !== "undefined" || true ? "node" : "undici";
var resolveObjectURL;
@ -11118,104 +11104,106 @@ var require_fetch = __commonJS({
}
if (subresourceSet.has(request.destination)) {
}
mainFetch(fetchParams).catch((err) => {
fetchParams.controller.terminate(err);
});
mainFetch(fetchParams, false);
return fetchParams.controller;
}
__name(fetching, "fetching");
async function mainFetch(fetchParams, recursive = false) {
const request = fetchParams.request;
let response = null;
if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
response = makeNetworkError("local URLs only");
}
tryUpgradeRequestToAPotentiallyTrustworthyURL(request);
if (requestBadPort(request) === "blocked") {
response = makeNetworkError("bad port");
}
if (request.referrerPolicy === "") {
request.referrerPolicy = request.policyContainer.referrerPolicy;
}
if (request.referrer !== "no-referrer") {
request.referrer = determineRequestsReferrer(request);
}
if (response === null) {
const currentURL = requestCurrentURL(request);
if (
// - request?s current URL?s origin is same origin with request?s origin,
// and request?s response tainting is "basic"
sameOrigin(currentURL, request.url) && request.responseTainting === "basic" || // request?s current URL?s scheme is "data"
currentURL.protocol === "data:" || // - request?s mode is "navigate" or "websocket"
(request.mode === "navigate" || request.mode === "websocket")
) {
request.responseTainting = "basic";
response = await schemeFetch(fetchParams);
} else if (request.mode === "same-origin") {
response = makeNetworkError('request mode cannot be "same-origin"');
} else if (request.mode === "no-cors") {
if (request.redirect !== "follow") {
response = makeNetworkError(
'redirect mode cannot be "follow" for "no-cors" request'
);
} else {
request.responseTainting = "opaque";
async function mainFetch(fetchParams, recursive) {
try {
const request = fetchParams.request;
let response = null;
if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
response = makeNetworkError("local URLs only");
}
tryUpgradeRequestToAPotentiallyTrustworthyURL(request);
if (requestBadPort(request) === "blocked") {
response = makeNetworkError("bad port");
}
if (request.referrerPolicy === "") {
request.referrerPolicy = request.policyContainer.referrerPolicy;
}
if (request.referrer !== "no-referrer") {
request.referrer = determineRequestsReferrer(request);
}
if (response === null) {
const currentURL = requestCurrentURL(request);
if (
// - request?s current URL?s origin is same origin with request?s origin,
// and request?s response tainting is "basic"
sameOrigin(currentURL, request.url) && request.responseTainting === "basic" || // request?s current URL?s scheme is "data"
currentURL.protocol === "data:" || // - request?s mode is "navigate" or "websocket"
(request.mode === "navigate" || request.mode === "websocket")
) {
request.responseTainting = "basic";
response = await schemeFetch(fetchParams);
} else if (request.mode === "same-origin") {
response = makeNetworkError('request mode cannot be "same-origin"');
} else if (request.mode === "no-cors") {
if (request.redirect !== "follow") {
response = makeNetworkError(
'redirect mode cannot be "follow" for "no-cors" request'
);
} else {
request.responseTainting = "opaque";
response = await schemeFetch(fetchParams);
}
} else if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
response = makeNetworkError("URL scheme must be a HTTP(S) scheme");
} else {
request.responseTainting = "cors";
response = await httpFetch(fetchParams);
}
} else if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
response = makeNetworkError("URL scheme must be a HTTP(S) scheme");
} else {
request.responseTainting = "cors";
response = await httpFetch(fetchParams);
}
}
if (recursive) {
return response;
}
if (response.status !== 0 && !response.internalResponse) {
if (request.responseTainting === "cors") {
if (recursive) {
return response;
}
if (request.responseTainting === "basic") {
response = filterResponse(response, "basic");
} else if (request.responseTainting === "cors") {
response = filterResponse(response, "cors");
} else if (request.responseTainting === "opaque") {
response = filterResponse(response, "opaque");
} else {
assert(false);
if (response.status !== 0 && !response.internalResponse) {
if (request.responseTainting === "cors") {
}
if (request.responseTainting === "basic") {
response = filterResponse(response, "basic");
} else if (request.responseTainting === "cors") {
response = filterResponse(response, "cors");
} else if (request.responseTainting === "opaque") {
response = filterResponse(response, "opaque");
} else {
assert(false);
}
}
}
let internalResponse = response.status === 0 ? response : response.internalResponse;
if (internalResponse.urlList.length === 0) {
internalResponse.urlList.push(...request.urlList);
}
if (!request.timingAllowFailed) {
response.timingAllowPassed = true;
}
if (response.type === "opaque" && internalResponse.status === 206 && internalResponse.rangeRequested && !request.headers.contains("range", true)) {
response = internalResponse = makeNetworkError();
}
if (response.status !== 0 && (request.method === "HEAD" || request.method === "CONNECT" || nullBodyStatus.includes(internalResponse.status))) {
internalResponse.body = null;
fetchParams.controller.dump = true;
}
if (request.integrity) {
const processBodyError = /* @__PURE__ */ __name((reason) => fetchFinale(fetchParams, makeNetworkError(reason)), "processBodyError");
if (request.responseTainting === "opaque" || response.body == null) {
processBodyError(response.error);
return;
let internalResponse = response.status === 0 ? response : response.internalResponse;
if (internalResponse.urlList.length === 0) {
internalResponse.urlList.push(...request.urlList);
}
const processBody = /* @__PURE__ */ __name((bytes) => {
if (!bytesMatch(bytes, request.integrity)) {
processBodyError("integrity mismatch");
if (!request.timingAllowFailed) {
response.timingAllowPassed = true;
}
if (response.type === "opaque" && internalResponse.status === 206 && internalResponse.rangeRequested && !request.headers.contains("range", true)) {
response = internalResponse = makeNetworkError();
}
if (response.status !== 0 && (request.method === "HEAD" || request.method === "CONNECT" || nullBodyStatus.includes(internalResponse.status))) {
internalResponse.body = null;
fetchParams.controller.dump = true;
}
if (request.integrity) {
const processBodyError = /* @__PURE__ */ __name((reason) => fetchFinale(fetchParams, makeNetworkError(reason)), "processBodyError");
if (request.responseTainting === "opaque" || response.body == null) {
processBodyError(response.error);
return;
}
response.body = safelyExtractBody(bytes)[0];
const processBody = /* @__PURE__ */ __name((bytes) => {
if (!bytesMatch(bytes, request.integrity)) {
processBodyError("integrity mismatch");
return;
}
response.body = safelyExtractBody(bytes)[0];
fetchFinale(fetchParams, response);
}, "processBody");
fullyReadBody(response.body, processBody, processBodyError);
} else {
fetchFinale(fetchParams, response);
}, "processBody");
await fullyReadBody(response.body, processBody, processBodyError);
} else {
fetchFinale(fetchParams, response);
}
} catch (err) {
fetchParams.controller.terminate(err);
}
}
__name(mainFetch, "mainFetch");
@ -11692,15 +11680,11 @@ var require_fetch = __commonJS({
}, "cancelAlgorithm");
const stream = new ReadableStream(
{
async start(controller) {
start(controller) {
fetchParams.controller.controller = controller;
},
async pull(controller) {
await pullAlgorithm(controller);
},
async cancel(reason) {
await cancelAlgorithm(reason);
},
pull: pullAlgorithm,
cancel: cancelAlgorithm,
type: "bytes"
}
);
@ -11800,10 +11784,9 @@ var require_fetch = __commonJS({
},
onHeaders(status, rawHeaders, resume, statusText) {
if (status < 200) {
return;
return false;
}
let codings = [];
let location = "";
const headersList = new HeadersList();
for (let i = 0; i < rawHeaders.length; i += 2) {
headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true);
@ -11812,7 +11795,7 @@ var require_fetch = __commonJS({
if (contentEncoding) {
codings = contentEncoding.toLowerCase().split(",").map((x) => x.trim());
}
location = headersList.get("location", true);
const location = headersList.get("location", true);
this.body = new Readable({ read: resume });
const decoders = [];
const willFollow = location && request.redirect === "follow" && redirectStatusSet.has(status);
@ -12560,7 +12543,6 @@ var require_connection = __commonJS({
"use strict";
var { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require_constants4();
var { parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require_util3();
var { channels } = require_diagnostics();
var { makeRequest } = require_request2();
var { fetching } = require_fetch();
var { Headers, getHeadersList } = require_headers();
@ -12647,13 +12629,6 @@ var require_connection = __commonJS({
response.socket.on("data", handler.onSocketData);
response.socket.on("close", handler.onSocketClose);
response.socket.on("error", handler.onSocketError);
if (channels.open.hasSubscribers) {
channels.open.publish({
address: response.socket.address(),
protocol: secProtocol,
extensions: secExtension
});
}
handler.wasEverConnected = true;
handler.onConnectionEstablished(response, extensions);
}
@ -12779,7 +12754,6 @@ var require_receiver = __commonJS({
var { Writable } = require("node:stream");
var assert = require("node:assert");
var { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require_constants4();
var { channels } = require_diagnostics();
var {
isValidStatusCode,
isValidOpcode,
@ -13071,18 +13045,10 @@ var require_receiver = __commonJS({
if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
const frame = new WebsocketFrameSend(body);
this.#handler.socket.write(frame.createFrame(opcodes.PONG));
if (channels.ping.hasSubscribers) {
channels.ping.publish({
payload: body
});
}
this.#handler.onPing(body);
}
} else if (opcode === opcodes.PONG) {
if (channels.pong.hasSubscribers) {
channels.pong.publish({
payload: body
});
}
this.#handler.onPong(body);
}
return true;
}
@ -13200,6 +13166,7 @@ var require_websocket = __commonJS({
isConnecting,
isEstablished,
isClosing,
isClosed,
isValidSubprotocol,
fireEvent,
utf8Decode,
@ -13213,6 +13180,7 @@ var require_websocket = __commonJS({
var { types } = require("node:util");
var { ErrorEvent: ErrorEvent2, CloseEvent: CloseEvent2, createFastMessageEvent: createFastMessageEvent2 } = require_events();
var { SendQueue } = require_sender();
var { WebsocketFrameSend } = require_frame();
var { channels } = require_diagnostics();
var WebSocket = class _WebSocket extends EventTarget {
static {
@ -13249,6 +13217,22 @@ var require_websocket = __commonJS({
this.#handler.socket.destroy();
}, "onSocketError"),
onSocketClose: /* @__PURE__ */ __name(() => this.#onSocketClose(), "onSocketClose"),
onPing: /* @__PURE__ */ __name((body) => {
if (channels.ping.hasSubscribers) {
channels.ping.publish({
payload: body,
websocket: this
});
}
}, "onPing"),
onPong: /* @__PURE__ */ __name((body) => {
if (channels.pong.hasSubscribers) {
channels.pong.publish({
payload: body,
websocket: this
});
}
}, "onPong"),
readyState: states.CONNECTING,
socket: null,
closeState: /* @__PURE__ */ new Set(),
@ -13466,6 +13450,14 @@ var require_websocket = __commonJS({
this.#protocol = protocol;
}
fireEvent("open", this);
if (channels.open.hasSubscribers) {
channels.open.publish({
address: response.socket.address(),
protocol: this.#protocol,
extensions: this.#extensions,
websocket: this
});
}
}
#onFail(code, reason, cause) {
if (reason) {
@ -13539,7 +13531,27 @@ var require_websocket = __commonJS({
});
}
}
/**
* @param {WebSocket} ws
* @param {Buffer|undefined} buffer
*/
static ping(ws, buffer) {
if (Buffer.isBuffer(buffer)) {
if (buffer.length > 125) {
throw new TypeError("A PING frame cannot have a body larger than 125 bytes.");
}
} else if (buffer !== void 0) {
throw new TypeError("Expected buffer payload");
}
const readyState = ws.#handler.readyState;
if (isEstablished(readyState) && !isClosing(readyState) && !isClosed(readyState)) {
const frame = new WebsocketFrameSend(buffer);
ws.#handler.socket.write(frame.createFrame(opcodes.PING));
}
}
};
var { ping } = WebSocket;
Reflect.deleteProperty(WebSocket, "ping");
WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING;
WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN;
WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING;
@ -13617,7 +13629,8 @@ var require_websocket = __commonJS({
return webidl.converters.USVString(V);
};
module2.exports = {
WebSocket
WebSocket,
ping
};
}
});
@ -14254,9 +14267,7 @@ var require_readable = __commonJS({
this[kAbort]();
}
if (!this[kUsed]) {
setImmediate(() => {
callback(err);
});
setImmediate(callback, err);
} else {
callback(err);
}

View File

@ -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.11.0"
#define UNDICI_VERSION "7.12.0"
#endif // SRC_UNDICI_VERSION_H_