Socket
Socket
Sign inDemoInstall

undici

Package Overview
Dependencies
0
Maintainers
3
Versions
202
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 6.16.1 to 6.17.0

lib/interceptor/dump.js

32

docs/docs/api/Dispatcher.md

@@ -955,2 +955,34 @@ # Dispatcher

##### `dump`
The `dump` interceptor enables you to dump the response body from a request upon a given limit.
**Options**
- `maxSize` - The maximum size (in bytes) of the response body to dump. If the size of the request's body exceeds this value then the connection will be closed. Default: `1048576`.
> The `Dispatcher#options` also gets extended with the options `dumpMaxSize`, `abortOnDumped`, and `waitForTrailers` which can be used to configure the interceptor at a request-per-request basis.
**Example - Basic Dump Interceptor**
```js
const { Client, interceptors } = require("undici");
const { dump } = interceptors;
const client = new Client("http://example.com").compose(
dump({
maxSize: 1024,
})
);
// or
client.dispatch(
{
path: "/",
method: "GET",
dumpMaxSize: 1024,
},
handler
);
```
## Instance Events

@@ -957,0 +989,0 @@

2

index-fetch.js

@@ -10,3 +10,3 @@ 'use strict'

if (err && typeof err === 'object') {
Error.captureStackTrace(err, this)
Error.captureStackTrace(err)
}

@@ -13,0 +13,0 @@ throw err

@@ -43,3 +43,4 @@ 'use strict'

redirect: require('./lib/interceptor/redirect'),
retry: require('./lib/interceptor/retry')
retry: require('./lib/interceptor/retry'),
dump: require('./lib/interceptor/dump')
}

@@ -112,3 +113,3 @@

if (err && typeof err === 'object') {
Error.captureStackTrace(err, this)
Error.captureStackTrace(err)
}

@@ -115,0 +116,0 @@

@@ -11,3 +11,2 @@ module.exports = {

kConnecting: Symbol('connecting'),
kHeadersList: Symbol('headers list'),
kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'),

@@ -14,0 +13,0 @@ kKeepAliveMaxTimeout: Symbol('max keep alive timeout'),

@@ -1104,3 +1104,3 @@ 'use strict'

async function writeBuffer ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
function writeBuffer ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
try {

@@ -1107,0 +1107,0 @@ if (!body) {

'use strict'
const assert = require('node:assert')
const { kHeadersList } = require('../../core/symbols')
const { getHeadersList: internalGetHeadersList } = require('../fetch/headers')

@@ -281,4 +281,6 @@ /**

function getHeadersList (headers) {
if (headers[kHeadersList]) {
return headers[kHeadersList]
try {
return internalGetHeadersList(headers)
} catch {
// fall-through
}

@@ -285,0 +287,0 @@

@@ -388,2 +388,11 @@ 'use strict'

}, instance, false)
},
bytes () {
// The bytes() method steps are to return the result of running consume body
// with this and the following step given a byte sequence bytes: return the
// result of creating a Uint8Array from bytes in this’s relevant realm.
return consumeBody(this, (bytes) => {
return new Uint8Array(bytes.buffer, 0, bytes.byteLength)
}, instance, true)
}

@@ -390,0 +399,0 @@ }

@@ -5,4 +5,3 @@ // https://github.com/Ethan-Arrowood/undici-fetch

const { kHeadersList, kConstruct } = require('../../core/symbols')
const { kGuard } = require('./symbols')
const { kConstruct } = require('../../core/symbols')
const { kEnumerableProperty } = require('../../core/util')

@@ -107,8 +106,7 @@ const {

// forbidden header name, return.
// 5. Otherwise, if headers’s guard is "request-no-cors":
// TODO
// Note: undici does not implement forbidden header names
if (headers[kGuard] === 'immutable') {
if (getHeadersGuard(headers) === 'immutable') {
throw new TypeError('immutable')
} else if (headers[kGuard] === 'request-no-cors') {
// 5. Otherwise, if headers’s guard is "request-no-cors":
// TODO
}

@@ -120,3 +118,3 @@

// 7. Append (name, value) to headers’s header list.
return headers[kHeadersList].append(name, value, false)
return getHeadersList(headers).append(name, value, false)

@@ -363,2 +361,5 @@ // 8. If headers’s guard is "request-no-cors", then remove

class Headers {
#guard
#headersList
constructor (init = undefined) {

@@ -368,8 +369,9 @@ if (init === kConstruct) {

}
this[kHeadersList] = new HeadersList()
this.#headersList = new HeadersList()
// The new Headers(init) constructor steps are:
// 1. Set this’s guard to "none".
this[kGuard] = 'none'
this.#guard = 'none'

@@ -424,6 +426,4 @@ // 2. If init is given, then fill this with init.

// Note: undici does not implement forbidden header names
if (this[kGuard] === 'immutable') {
if (this.#guard === 'immutable') {
throw new TypeError('immutable')
} else if (this[kGuard] === 'request-no-cors') {
// TODO
}

@@ -433,3 +433,3 @@

// return.
if (!this[kHeadersList].contains(name, false)) {
if (!this.#headersList.contains(name, false)) {
return

@@ -441,3 +441,3 @@ }

// privileged no-CORS request headers from this.
this[kHeadersList].delete(name, false)
this.#headersList.delete(name, false)
}

@@ -465,3 +465,3 @@

// list.
return this[kHeadersList].get(name, false)
return this.#headersList.get(name, false)
}

@@ -489,3 +489,3 @@

// otherwise false.
return this[kHeadersList].contains(name, false)
return this.#headersList.contains(name, false)
}

@@ -531,6 +531,4 @@

// Note: undici does not implement forbidden header names
if (this[kGuard] === 'immutable') {
if (this.#guard === 'immutable') {
throw new TypeError('immutable')
} else if (this[kGuard] === 'request-no-cors') {
// TODO
}

@@ -541,3 +539,3 @@

// privileged no-CORS request headers from this
this[kHeadersList].set(name, value, false)
this.#headersList.set(name, value, false)
}

@@ -553,3 +551,3 @@

const list = this[kHeadersList].cookies
const list = this.#headersList.cookies

@@ -565,4 +563,4 @@ if (list) {

get [kHeadersSortedMap] () {
if (this[kHeadersList][kHeadersSortedMap]) {
return this[kHeadersList][kHeadersSortedMap]
if (this.#headersList[kHeadersSortedMap]) {
return this.#headersList[kHeadersSortedMap]
}

@@ -576,5 +574,5 @@

// set with all the names of the headers in list.
const names = this[kHeadersList].toSortedArray()
const names = this.#headersList.toSortedArray()
const cookies = this[kHeadersList].cookies
const cookies = this.#headersList.cookies

@@ -584,3 +582,3 @@ // fast-path

// Note: The non-null assertion of value has already been done by `HeadersList#toSortedArray`
return (this[kHeadersList][kHeadersSortedMap] = names)
return (this.#headersList[kHeadersSortedMap] = names)
}

@@ -615,3 +613,3 @@

// 4. Return headers.
return (this[kHeadersList][kHeadersSortedMap] = headers)
return (this.#headersList[kHeadersSortedMap] = headers)
}

@@ -622,6 +620,28 @@

return `Headers ${util.formatWithOptions(options, this[kHeadersList].entries)}`
return `Headers ${util.formatWithOptions(options, this.#headersList.entries)}`
}
static getHeadersGuard (o) {
return o.#guard
}
static setHeadersGuard (o, guard) {
o.#guard = guard
}
static getHeadersList (o) {
return o.#headersList
}
static setHeadersList (o, list) {
o.#headersList = list
}
}
const { getHeadersGuard, setHeadersGuard, getHeadersList, setHeadersList } = Headers
Reflect.deleteProperty(Headers, 'getHeadersGuard')
Reflect.deleteProperty(Headers, 'setHeadersGuard')
Reflect.deleteProperty(Headers, 'getHeadersList')
Reflect.deleteProperty(Headers, 'setHeadersList')
Object.defineProperty(Headers.prototype, util.inspect.custom, {

@@ -652,4 +672,8 @@ enumerable: false

// Read https://github.com/nodejs/undici/pull/3159#issuecomment-2075537226 before touching, please.
if (!util.types.isProxy(V) && kHeadersList in V && iterator === Headers.prototype.entries) { // Headers object
return V[kHeadersList].entriesList
if (!util.types.isProxy(V) && iterator === Headers.prototype.entries) { // Headers object
try {
return getHeadersList(V).entriesList
} catch {
// fall-through
}
}

@@ -676,3 +700,7 @@

Headers,
HeadersList
HeadersList,
getHeadersGuard,
setHeadersGuard,
setHeadersList,
getHeadersList
}

@@ -6,3 +6,3 @@ /* globals AbortController */

const { extractBody, mixinBody, cloneBody } = require('./body')
const { Headers, fill: fillHeaders, HeadersList } = require('./headers')
const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require('./headers')
const { FinalizationRegistry } = require('./dispatcher-weakref')()

@@ -29,6 +29,6 @@ const util = require('../../core/util')

const { kEnumerableProperty } = util
const { kHeaders, kSignal, kState, kGuard, kDispatcher } = require('./symbols')
const { kHeaders, kSignal, kState, kDispatcher } = require('./symbols')
const { webidl } = require('./webidl')
const { URLSerializer } = require('./data-url')
const { kHeadersList, kConstruct } = require('../../core/symbols')
const { kConstruct } = require('../../core/symbols')
const assert = require('node:assert')

@@ -450,4 +450,4 @@ const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = require('node:events')

this[kHeaders] = new Headers(kConstruct)
this[kHeaders][kHeadersList] = request.headersList
this[kHeaders][kGuard] = 'request'
setHeadersList(this[kHeaders], request.headersList)
setHeadersGuard(this[kHeaders], 'request')

@@ -465,3 +465,3 @@ // 31. If this’s request’s mode is "no-cors", then:

// 2. Set this’s headers’s guard to "request-no-cors".
this[kHeaders][kGuard] = 'request-no-cors'
setHeadersGuard(this[kHeaders], 'request-no-cors')
}

@@ -472,3 +472,3 @@

/** @type {HeadersList} */
const headersList = this[kHeaders][kHeadersList]
const headersList = getHeadersList(this[kHeaders])
// 1. Let headers be a copy of this’s headers and its associated header

@@ -527,3 +527,3 @@ // list.

// this’s headers.
if (contentType && !this[kHeaders][kHeadersList].contains('content-type', true)) {
if (contentType && !getHeadersList(this[kHeaders]).contains('content-type', true)) {
this[kHeaders].append('content-type', contentType)

@@ -794,3 +794,3 @@ }

// 4. Return clonedRequestObject.
return fromInnerRequest(clonedRequest, ac.signal, this[kHeaders][kGuard])
return fromInnerRequest(clonedRequest, ac.signal, getHeadersGuard(this[kHeaders]))
}

@@ -904,4 +904,4 @@

request[kHeaders] = new Headers(kConstruct)
request[kHeaders][kHeadersList] = innerRequest.headersList
request[kHeaders][kGuard] = guard
setHeadersList(request[kHeaders], innerRequest.headersList)
setHeadersGuard(request[kHeaders], guard)
return request

@@ -908,0 +908,0 @@ }

'use strict'
const { Headers, HeadersList, fill } = require('./headers')
const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
const { extractBody, cloneBody, mixinBody } = require('./body')

@@ -22,7 +22,7 @@ const util = require('../../core/util')

} = require('./constants')
const { kState, kHeaders, kGuard } = require('./symbols')
const { kState, kHeaders } = require('./symbols')
const { webidl } = require('./webidl')
const { FormData } = require('./formdata')
const { URLSerializer } = require('./data-url')
const { kHeadersList, kConstruct } = require('../../core/symbols')
const { kConstruct } = require('../../core/symbols')
const assert = require('node:assert')

@@ -145,4 +145,4 @@ const { types } = require('node:util')

this[kHeaders] = new Headers(kConstruct)
this[kHeaders][kGuard] = 'response'
this[kHeaders][kHeadersList] = this[kState].headersList
setHeadersGuard(this[kHeaders], 'response')
setHeadersList(this[kHeaders], this[kState].headersList)

@@ -260,3 +260,3 @@ // 3. Let bodyWithType be null.

// clonedResponse, this’s headers’s guard, and this’s relevant Realm.
return fromInnerResponse(clonedResponse, this[kHeaders][kGuard])
return fromInnerResponse(clonedResponse, getHeadersGuard(this[kHeaders]))
}

@@ -528,4 +528,4 @@

response[kHeaders] = new Headers(kConstruct)
response[kHeaders][kHeadersList] = innerResponse.headersList
response[kHeaders][kGuard] = guard
setHeadersList(response[kHeaders], innerResponse.headersList)
setHeadersGuard(response[kHeaders], guard)

@@ -532,0 +532,0 @@ if (hasFinalizationRegistry && innerResponse.body?.stream) {

@@ -8,4 +8,3 @@ 'use strict'

kState: Symbol('state'),
kGuard: Symbol('guard'),
kDispatcher: Symbol('dispatcher')
}

@@ -16,5 +16,4 @@ 'use strict'

const { fetching } = require('../fetch/index')
const { Headers } = require('../fetch/headers')
const { Headers, getHeadersList } = require('../fetch/headers')
const { getDecodeSplit } = require('../fetch/util')
const { kHeadersList } = require('../../core/symbols')
const { WebsocketFrameSend } = require('./frame')

@@ -63,3 +62,3 @@

if (options.headers) {
const headersList = new Headers(options.headers)[kHeadersList]
const headersList = getHeadersList(new Headers(options.headers))

@@ -266,9 +265,5 @@ request.headersList = headersList

socket.write(frame.createFrame(opcodes.CLOSE), (err) => {
if (!err) {
ws[kSentClose] = sentCloseFrameState.SENT
}
})
socket.write(frame.createFrame(opcodes.CLOSE))
ws[kSentClose] = sentCloseFrameState.PROCESSING
ws[kSentClose] = sentCloseFrameState.SENT

@@ -275,0 +270,0 @@ // Upon either sending or receiving a Close control frame, it is said

'use strict'
const { Writable } = require('node:stream')
const assert = require('node:assert')
const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require('./constants')
const { kReadyState, kSentClose, kResponse, kReceivedClose } = require('./symbols')
const { channels } = require('../../core/diagnostics')
const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived, utf8Decode } = require('./util')
const {
isValidStatusCode,
isValidOpcode,
failWebsocketConnection,
websocketMessageReceived,
utf8Decode,
isControlFrame,
isTextBinaryFrame,
isContinuationFrame
} = require('./util')
const { WebsocketFrameSend } = require('./frame')
const { CloseEvent } = require('./events')
const { closeWebSocketConnection } = require('./connection')

@@ -19,2 +29,3 @@ // This code was influenced by ws released under the MIT license.

#byteOffset = 0
#loop = false

@@ -39,2 +50,3 @@ #state = parserStates.INFO

this.#byteOffset += chunk.length
this.#loop = true

@@ -50,3 +62,3 @@ this.run(callback)

run (callback) {
while (true) {
while (this.#loop) {
if (this.#state === parserStates.INFO) {

@@ -59,8 +71,19 @@ // If there aren't enough bytes to parse the payload length, etc.

const buffer = this.consume(2)
const fin = (buffer[0] & 0x80) !== 0
const opcode = buffer[0] & 0x0F
const masked = (buffer[1] & 0x80) === 0x80
this.#info.fin = (buffer[0] & 0x80) !== 0
this.#info.opcode = buffer[0] & 0x0F
this.#info.masked = (buffer[1] & 0x80) === 0x80
const fragmented = !fin && opcode !== opcodes.CONTINUATION
const payloadLength = buffer[1] & 0x7F
if (this.#info.masked) {
const rsv1 = buffer[0] & 0x40
const rsv2 = buffer[0] & 0x20
const rsv3 = buffer[0] & 0x10
if (!isValidOpcode(opcode)) {
failWebsocketConnection(this.ws, 'Invalid opcode received')
return callback()
}
if (masked) {
failWebsocketConnection(this.ws, 'Frame cannot be masked')

@@ -70,9 +93,13 @@ return callback()

// If we receive a fragmented message, we use the type of the first
// frame to parse the full message as binary/text, when it's terminated
this.#info.originalOpcode ??= this.#info.opcode
// MUST be 0 unless an extension is negotiated that defines meanings
// for non-zero values. If a nonzero value is received and none of
// the negotiated extensions defines the meaning of such a nonzero
// value, the receiving endpoint MUST _Fail the WebSocket
// Connection_.
if (rsv1 !== 0 || rsv2 !== 0 || rsv3 !== 0) {
failWebsocketConnection(this.ws, 'RSV1, RSV2, RSV3 must be clear')
return
}
this.#info.fragmented = !this.#info.fin && this.#info.opcode !== opcodes.CONTINUATION
if (this.#info.fragmented && this.#info.opcode !== opcodes.BINARY && this.#info.opcode !== opcodes.TEXT) {
if (fragmented && !isTextBinaryFrame(opcode)) {
// Only text and binary frames can be fragmented

@@ -83,4 +110,27 @@ failWebsocketConnection(this.ws, 'Invalid frame type was fragmented.')

const payloadLength = buffer[1] & 0x7F
// If we are already parsing a text/binary frame and do not receive either
// a continuation frame or close frame, fail the connection.
if (isTextBinaryFrame(opcode) && this.#fragments.length > 0) {
failWebsocketConnection(this.ws, 'Expected continuation frame')
return
}
if (this.#info.fragmented && fragmented) {
// A fragmented frame can't be fragmented itself
failWebsocketConnection(this.ws, 'Fragmented frame exceeded 125 bytes.')
return
}
// "All control frames MUST have a payload length of 125 bytes or less
// and MUST NOT be fragmented."
if ((payloadLength > 125 || fragmented) && isControlFrame(opcode)) {
failWebsocketConnection(this.ws, 'Control frame either too large or fragmented')
return
}
if (isContinuationFrame(opcode) && this.#fragments.length === 0) {
failWebsocketConnection(this.ws, 'Unexpected continuation frame')
return
}
if (payloadLength <= 125) {

@@ -95,111 +145,10 @@ this.#info.payloadLength = payloadLength

if (this.#info.fragmented && payloadLength > 125) {
// A fragmented frame can't be fragmented itself
failWebsocketConnection(this.ws, 'Fragmented frame exceeded 125 bytes.')
return
} else if (
(this.#info.opcode === opcodes.PING ||
this.#info.opcode === opcodes.PONG ||
this.#info.opcode === opcodes.CLOSE) &&
payloadLength > 125
) {
// Control frames can have a payload length of 125 bytes MAX
failWebsocketConnection(this.ws, 'Payload length for control frame exceeded 125 bytes.')
return
} else if (this.#info.opcode === opcodes.CLOSE) {
if (payloadLength === 1) {
failWebsocketConnection(this.ws, 'Received close frame with a 1-byte body.')
return
}
if (isTextBinaryFrame(opcode)) {
this.#info.binaryType = opcode
}
const body = this.consume(payloadLength)
this.#info.closeInfo = this.parseCloseBody(body)
if (this.#info.closeInfo.error) {
const { code, reason } = this.#info.closeInfo
callback(new CloseEvent('close', { wasClean: false, reason, code }))
return
}
if (this.ws[kSentClose] !== sentCloseFrameState.SENT) {
// If an endpoint receives a Close frame and did not previously send a
// Close frame, the endpoint MUST send a Close frame in response. (When
// sending a Close frame in response, the endpoint typically echos the
// status code it received.)
let body = emptyBuffer
if (this.#info.closeInfo.code) {
body = Buffer.allocUnsafe(2)
body.writeUInt16BE(this.#info.closeInfo.code, 0)
}
const closeFrame = new WebsocketFrameSend(body)
this.ws[kResponse].socket.write(
closeFrame.createFrame(opcodes.CLOSE),
(err) => {
if (!err) {
this.ws[kSentClose] = sentCloseFrameState.SENT
}
}
)
}
// Upon either sending or receiving a Close control frame, it is said
// that _The WebSocket Closing Handshake is Started_ and that the
// WebSocket connection is in the CLOSING state.
this.ws[kReadyState] = states.CLOSING
this.ws[kReceivedClose] = true
this.end()
return
} else if (this.#info.opcode === opcodes.PING) {
// Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
// response, unless it already received a Close frame.
// A Pong frame sent in response to a Ping frame must have identical
// "Application data"
const body = this.consume(payloadLength)
if (!this.ws[kReceivedClose]) {
const frame = new WebsocketFrameSend(body)
this.ws[kResponse].socket.write(frame.createFrame(opcodes.PONG))
if (channels.ping.hasSubscribers) {
channels.ping.publish({
payload: body
})
}
}
this.#state = parserStates.INFO
if (this.#byteOffset > 0) {
continue
} else {
callback()
return
}
} else if (this.#info.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.
const body = this.consume(payloadLength)
if (channels.pong.hasSubscribers) {
channels.pong.publish({
payload: body
})
}
if (this.#byteOffset > 0) {
continue
} else {
callback()
return
}
}
this.#info.opcode = opcode
this.#info.masked = masked
this.#info.fin = fin
this.#info.fragmented = fragmented
} else if (this.#state === parserStates.PAYLOADLENGTH_16) {

@@ -239,29 +188,24 @@ if (this.#byteOffset < 2) {

if (this.#byteOffset < this.#info.payloadLength) {
// If there is still more data in this chunk that needs to be read
return callback()
} else if (this.#byteOffset >= this.#info.payloadLength) {
// If the server sent multiple frames in a single chunk
}
const body = this.consume(this.#info.payloadLength)
const body = this.consume(this.#info.payloadLength)
if (isControlFrame(this.#info.opcode)) {
this.#loop = this.parseControlFrame(body)
} else {
this.#fragments.push(body)
// If the frame is unfragmented, or a fragmented frame was terminated,
// a message was received
if (!this.#info.fragmented || (this.#info.fin && this.#info.opcode === opcodes.CONTINUATION)) {
// If the frame is not fragmented, a message has been received.
// If the frame is fragmented, it will terminate with a fin bit set
// and an opcode of 0 (continuation), therefore we handle that when
// parsing continuation frames, not here.
if (!this.#info.fragmented && this.#info.fin) {
const fullMessage = Buffer.concat(this.#fragments)
websocketMessageReceived(this.ws, this.#info.originalOpcode, fullMessage)
this.#info = {}
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
this.#fragments.length = 0
}
this.#state = parserStates.INFO
}
}
if (this.#byteOffset === 0 && this.#info.payloadLength !== 0) {
callback()
break
this.#state = parserStates.INFO
}

@@ -274,7 +218,7 @@ }

* @param {number} n
* @returns {Buffer|null}
* @returns {Buffer}
*/
consume (n) {
if (n > this.#byteOffset) {
return null
throw new Error('Called consume() before buffers satiated.')
} else if (n === 0) {

@@ -315,2 +259,4 @@ return emptyBuffer

parseCloseBody (data) {
assert(data.length !== 1)
// https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5

@@ -349,2 +295,87 @@ /** @type {number|undefined} */

/**
* Parses control frames.
* @param {Buffer} body
*/
parseControlFrame (body) {
const { opcode, payloadLength } = this.#info
if (opcode === opcodes.CLOSE) {
if (payloadLength === 1) {
failWebsocketConnection(this.ws, 'Received close frame with a 1-byte body.')
return false
}
this.#info.closeInfo = this.parseCloseBody(body)
if (this.#info.closeInfo.error) {
const { code, reason } = this.#info.closeInfo
closeWebSocketConnection(this.ws, code, reason, reason.length)
failWebsocketConnection(this.ws, reason)
return false
}
if (this.ws[kSentClose] !== sentCloseFrameState.SENT) {
// If an endpoint receives a Close frame and did not previously send a
// Close frame, the endpoint MUST send a Close frame in response. (When
// sending a Close frame in response, the endpoint typically echos the
// status code it received.)
let body = emptyBuffer
if (this.#info.closeInfo.code) {
body = Buffer.allocUnsafe(2)
body.writeUInt16BE(this.#info.closeInfo.code, 0)
}
const closeFrame = new WebsocketFrameSend(body)
this.ws[kResponse].socket.write(
closeFrame.createFrame(opcodes.CLOSE),
(err) => {
if (!err) {
this.ws[kSentClose] = sentCloseFrameState.SENT
}
}
)
}
// Upon either sending or receiving a Close control frame, it is said
// that _The WebSocket Closing Handshake is Started_ and that the
// WebSocket connection is in the CLOSING state.
this.ws[kReadyState] = states.CLOSING
this.ws[kReceivedClose] = true
this.end()
return false
} else if (opcode === opcodes.PING) {
// Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
// response, unless it already received a Close frame.
// A Pong frame sent in response to a Ping frame must have identical
// "Application data"
if (!this.ws[kReceivedClose]) {
const frame = new WebsocketFrameSend(body)
this.ws[kResponse].socket.write(frame.createFrame(opcodes.PONG))
if (channels.ping.hasSubscribers) {
channels.ping.publish({
payload: 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
})
}
}
return true
}
get closingInfo () {

@@ -351,0 +382,0 @@ return this.#info.closeInfo

@@ -213,2 +213,26 @@ 'use strict'

/**
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.5
* @param {number} opcode
*/
function isControlFrame (opcode) {
return (
opcode === opcodes.CLOSE ||
opcode === opcodes.PING ||
opcode === opcodes.PONG
)
}
function isContinuationFrame (opcode) {
return opcode === opcodes.CONTINUATION
}
function isTextBinaryFrame (opcode) {
return opcode === opcodes.TEXT || opcode === opcodes.BINARY
}
function isValidOpcode (opcode) {
return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode)
}
// https://nodejs.org/api/intl.html#detecting-internationalization-support

@@ -241,3 +265,7 @@ const hasIntl = typeof process.versions.icu === 'string'

websocketMessageReceived,
utf8Decode
utf8Decode,
isControlFrame,
isContinuationFrame,
isTextBinaryFrame,
isValidOpcode
}

@@ -29,3 +29,3 @@ 'use strict'

const { types } = require('node:util')
const { ErrorEvent } = require('./events')
const { ErrorEvent, CloseEvent } = require('./events')

@@ -291,3 +291,3 @@ let experimentalWarned = false

const ab = new FastBuffer(data, data.byteOffset, data.byteLength)
const ab = new FastBuffer(data.buffer, data.byteOffset, data.byteLength)

@@ -599,5 +599,15 @@ const frame = new WebsocketFrameSend(ab)

function onParserError (err) {
fireEvent('error', this, () => new ErrorEvent('error', { error: err, message: err.reason }))
let message
let code
closeWebSocketConnection(this, err.code)
if (err instanceof CloseEvent) {
message = err.reason
code = err.code
} else {
message = err.message
}
fireEvent('error', this, () => new ErrorEvent('error', { error: err, message }))
closeWebSocketConnection(this, code)
}

@@ -604,0 +614,0 @@

{
"name": "undici",
"version": "6.16.1",
"version": "6.17.0",
"description": "An HTTP/1.1 client, written from scratch for Node.js",

@@ -91,2 +91,4 @@ "homepage": "https://undici.nodejs.org",

"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-FileAPI.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",

@@ -139,3 +141,3 @@ "test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",

"lib/llhttp/utils.js",
"test/wpt/tests"
"test/fixtures/wpt"
]

@@ -142,0 +144,0 @@ },

@@ -22,4 +22,4 @@ import { URL } from 'url'

/** Compose a chain of dispatchers */
compose(dispatchers: Dispatcher.DispatcherInterceptor[]): Dispatcher.ComposedDispatcher;
compose(...dispatchers: Dispatcher.DispatcherInterceptor[]): Dispatcher.ComposedDispatcher;
compose(dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher;
compose(...dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher;
/** Performs an HTTP request. */

@@ -101,3 +101,3 @@ request(options: Dispatcher.RequestOptions): Promise<Dispatcher.ResponseData>;

export interface ComposedDispatcher extends Dispatcher {}
export type DispatcherInterceptor = (dispatch: Dispatcher['dispatch']) => Dispatcher['dispatch'];
export type DispatcherComposeInterceptor = (dispatch: Dispatcher['dispatch']) => Dispatcher['dispatch'];
export interface DispatchOptions {

@@ -104,0 +104,0 @@ origin?: string | URL;

@@ -69,2 +69,7 @@ import Dispatcher from'./dispatcher'

var caches: typeof import('./cache').caches;
var interceptors: {
dump: typeof import('./interceptors').dump;
retry: typeof import('./interceptors').retry;
redirect: typeof import('./interceptors').redirect;
}
}
import Dispatcher from "./dispatcher";
import RetryHandler from "./retry-handler";
type RedirectInterceptorOpts = { maxRedirections?: number }
export type DumpInterceptorOpts = { maxSize?: number }
export type RetryInterceptorOpts = RetryHandler.RetryOptions
export type RedirectInterceptorOpts = { maxRedirections?: number }
export declare function createRedirectInterceptor (opts: RedirectInterceptorOpts): Dispatcher.DispatchInterceptor
export declare function createRedirectInterceptor (opts: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
export declare function dump(opts?: DumpInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
export declare function retry(opts?: RetryInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
export declare function redirect(opts?: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc