Comparing version 6.15.0 to 6.16.0
@@ -11,7 +11,7 @@ # Util | ||
- **headers** `Record<string, string | string[]> | (Buffer | string | (Buffer | string)[])[]` (required) - Header object. | ||
- **headers** `(Buffer | string | (Buffer | string)[])[]` (required) - Header object. | ||
- **obj** `Record<string, string | string[]>` (optional) - Object to specify a proxy object. The parsed value is assigned to this object. But, if **headers** is an object, it is not used. | ||
Returns: `Record<string, string | string[]>` If **headers** is an object, it is **headers**. Otherwise, if **obj** is specified, it is equivalent to **obj**. | ||
Returns: `Record<string, string | string[]>` If **obj** is specified, it is equivalent to **obj**. | ||
@@ -18,0 +18,0 @@ ## `headerNameToString(value)` |
@@ -5,3 +5,3 @@ # Connecting through a proxy | ||
- Using [AgentProxy](../api/ProxyAgent.md). | ||
- Using [ProxyAgent](../api/ProxyAgent.md). | ||
- Configuring `Client` or `Pool` constructor. | ||
@@ -8,0 +8,0 @@ |
@@ -5,7 +5,6 @@ 'use strict' | ||
const { Readable } = require('./readable') | ||
const { InvalidArgumentError } = require('../core/errors') | ||
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors') | ||
const util = require('../core/util') | ||
const { getResolveErrorBodyCallback } = require('./util') | ||
const { AsyncResource } = require('node:async_hooks') | ||
const { addSignal, removeSignal } = require('./abort-signal') | ||
@@ -60,2 +59,5 @@ class RequestHandler extends AsyncResource { | ||
this.highWaterMark = highWaterMark | ||
this.signal = signal | ||
this.reason = null | ||
this.removeAbortListener = null | ||
@@ -68,3 +70,22 @@ if (util.isStream(body)) { | ||
addSignal(this, signal) | ||
if (this.signal) { | ||
if (this.signal.aborted) { | ||
this.reason = this.signal.reason ?? new RequestAbortedError() | ||
} else { | ||
this.removeAbortListener = util.addAbortListener(this.signal, () => { | ||
this.reason = this.signal.reason ?? new RequestAbortedError() | ||
if (this.res) { | ||
util.destroy(this.res, this.reason) | ||
} else if (this.abort) { | ||
this.abort(this.reason) | ||
} | ||
if (this.removeAbortListener) { | ||
this.res?.off('close', this.removeAbortListener) | ||
this.removeAbortListener() | ||
this.removeAbortListener = null | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
@@ -99,10 +120,14 @@ | ||
const contentLength = parsedHeaders['content-length'] | ||
const body = new Readable({ resume, abort, contentType, contentLength, highWaterMark }) | ||
const res = new Readable({ resume, abort, contentType, contentLength, highWaterMark }) | ||
if (this.removeAbortListener) { | ||
res.on('close', this.removeAbortListener) | ||
} | ||
this.callback = null | ||
this.res = body | ||
this.res = res | ||
if (callback !== null) { | ||
if (this.throwOnError && statusCode >= 400) { | ||
this.runInAsyncScope(getResolveErrorBodyCallback, null, | ||
{ callback, body, contentType, statusCode, statusMessage, headers } | ||
{ callback, body: res, contentType, statusCode, statusMessage, headers } | ||
) | ||
@@ -115,3 +140,3 @@ } else { | ||
opaque, | ||
body, | ||
body: res, | ||
context | ||
@@ -124,14 +149,8 @@ }) | ||
onData (chunk) { | ||
const { res } = this | ||
return res.push(chunk) | ||
return this.res.push(chunk) | ||
} | ||
onComplete (trailers) { | ||
const { res } = this | ||
removeSignal(this) | ||
util.parseHeaders(trailers, this.trailers) | ||
res.push(null) | ||
this.res.push(null) | ||
} | ||
@@ -142,4 +161,2 @@ | ||
removeSignal(this) | ||
if (callback) { | ||
@@ -165,2 +182,8 @@ // TODO: Does this need queueMicrotask? | ||
} | ||
if (this.removeAbortListener) { | ||
res?.off('close', this.removeAbortListener) | ||
this.removeAbortListener() | ||
this.removeAbortListener = null | ||
} | ||
} | ||
@@ -167,0 +190,0 @@ } |
@@ -66,5 +66,9 @@ // Ported from https://github.com/nodejs/undici/pull/907 | ||
// never get a chance and will always encounter an unhandled exception. | ||
setImmediate(() => { | ||
if (!this[kReading]) { | ||
setImmediate(() => { | ||
callback(err) | ||
}) | ||
} else { | ||
callback(err) | ||
}) | ||
} | ||
} | ||
@@ -71,0 +75,0 @@ |
@@ -527,2 +527,3 @@ 'use strict' | ||
writeStream({ | ||
abort, | ||
body, | ||
@@ -539,2 +540,3 @@ client, | ||
writeIterable({ | ||
abort, | ||
body, | ||
@@ -541,0 +543,0 @@ client, |
@@ -10,3 +10,3 @@ 'use strict' | ||
*/ | ||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/ | ||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/ | ||
const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/ // eslint-disable-line | ||
@@ -17,3 +17,3 @@ const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line | ||
*/ | ||
const HTTP_QUOTED_STRING_TOKENS = /[\u0009\u0020-\u007E\u0080-\u00FF]/ // eslint-disable-line | ||
const HTTP_QUOTED_STRING_TOKENS = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/ // eslint-disable-line | ||
@@ -20,0 +20,0 @@ // https://fetch.spec.whatwg.org/#data-url-processor |
@@ -253,3 +253,3 @@ // https://github.com/Ethan-Arrowood/undici-fetch | ||
if (this[kHeadersMap].size) { | ||
if (this[kHeadersMap].size !== 0) { | ||
for (const { name, value } of this[kHeadersMap].values()) { | ||
@@ -263,2 +263,6 @@ headers[name] = value | ||
rawValues () { | ||
return this[kHeadersMap].values() | ||
} | ||
get entriesList () { | ||
@@ -265,0 +269,0 @@ const headers = [] |
@@ -480,5 +480,4 @@ /* globals AbortController */ | ||
if (headers instanceof HeadersList) { | ||
for (const { 0: key, 1: val } of headers) { | ||
// Note: The header names are already in lowercase. | ||
headersList.append(key, val, true) | ||
for (const { name, value } of headers.rawValues()) { | ||
headersList.append(name, value, false) | ||
} | ||
@@ -824,41 +823,42 @@ // Note: Copy the `set-cookie` meta-data. | ||
// https://fetch.spec.whatwg.org/#requests | ||
function makeRequest (init) { | ||
// https://fetch.spec.whatwg.org/#requests | ||
const request = { | ||
method: 'GET', | ||
localURLsOnly: false, | ||
unsafeRequest: false, | ||
body: null, | ||
client: null, | ||
reservedClient: null, | ||
replacesClientId: '', | ||
window: 'client', | ||
keepalive: false, | ||
serviceWorkers: 'all', | ||
initiator: '', | ||
destination: '', | ||
priority: null, | ||
origin: 'client', | ||
policyContainer: 'client', | ||
referrer: 'client', | ||
referrerPolicy: '', | ||
mode: 'no-cors', | ||
useCORSPreflightFlag: false, | ||
credentials: 'same-origin', | ||
useCredentials: false, | ||
cache: 'default', | ||
redirect: 'follow', | ||
integrity: '', | ||
cryptoGraphicsNonceMetadata: '', | ||
parserMetadata: '', | ||
reloadNavigation: false, | ||
historyNavigation: false, | ||
userActivation: false, | ||
taintedOrigin: false, | ||
redirectCount: 0, | ||
responseTainting: 'basic', | ||
preventNoCacheCacheControlHeaderModification: false, | ||
done: false, | ||
timingAllowFailed: false, | ||
...init, | ||
return { | ||
method: init.method ?? 'GET', | ||
localURLsOnly: init.localURLsOnly ?? false, | ||
unsafeRequest: init.unsafeRequest ?? false, | ||
body: init.body ?? null, | ||
client: init.client ?? null, | ||
reservedClient: init.reservedClient ?? null, | ||
replacesClientId: init.replacesClientId ?? '', | ||
window: init.window ?? 'client', | ||
keepalive: init.keepalive ?? false, | ||
serviceWorkers: init.serviceWorkers ?? 'all', | ||
initiator: init.initiator ?? '', | ||
destination: init.destination ?? '', | ||
priority: init.priority ?? null, | ||
origin: init.origin ?? 'client', | ||
policyContainer: init.policyContainer ?? 'client', | ||
referrer: init.referrer ?? 'client', | ||
referrerPolicy: init.referrerPolicy ?? '', | ||
mode: init.mode ?? 'no-cors', | ||
useCORSPreflightFlag: init.useCORSPreflightFlag ?? false, | ||
credentials: init.credentials ?? 'same-origin', | ||
useCredentials: init.useCredentials ?? false, | ||
cache: init.cache ?? 'default', | ||
redirect: init.redirect ?? 'follow', | ||
integrity: init.integrity ?? '', | ||
cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? '', | ||
parserMetadata: init.parserMetadata ?? '', | ||
reloadNavigation: init.reloadNavigation ?? false, | ||
historyNavigation: init.historyNavigation ?? false, | ||
userActivation: init.userActivation ?? false, | ||
taintedOrigin: init.taintedOrigin ?? false, | ||
redirectCount: init.redirectCount ?? 0, | ||
responseTainting: init.responseTainting ?? 'basic', | ||
preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false, | ||
done: init.done ?? false, | ||
timingAllowFailed: init.timingAllowFailed ?? false, | ||
urlList: init.urlList, | ||
url: init.urlList[0], | ||
headersList: init.headersList | ||
@@ -868,4 +868,2 @@ ? new HeadersList(init.headersList) | ||
} | ||
request.url = request.urlList[0] | ||
return request | ||
} | ||
@@ -872,0 +870,0 @@ |
@@ -29,5 +29,19 @@ 'use strict' | ||
const { types } = require('node:util') | ||
const { isDisturbed, isErrored } = require('node:stream') | ||
const textEncoder = new TextEncoder('utf-8') | ||
const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0 | ||
let registry | ||
if (hasFinalizationRegistry) { | ||
registry = new FinalizationRegistry((stream) => { | ||
if (!stream.locked && !isDisturbed(stream) && !isErrored(stream)) { | ||
stream.cancel('Response object has been garbage collected').catch(noop) | ||
} | ||
}) | ||
} | ||
function noop () {} | ||
// https://fetch.spec.whatwg.org/#response-class | ||
@@ -514,2 +528,7 @@ class Response { | ||
response[kHeaders][kGuard] = guard | ||
if (hasFinalizationRegistry && innerResponse.body?.stream) { | ||
registry.register(response, innerResponse.body.stream) | ||
} | ||
return response | ||
@@ -516,0 +535,0 @@ } |
@@ -253,2 +253,3 @@ 'use strict' | ||
const seq = [] | ||
let index = 0 | ||
@@ -274,3 +275,3 @@ // 3. If method is undefined, throw a TypeError. | ||
seq.push(converter(value, prefix, argument)) | ||
seq.push(converter(value, prefix, `${argument}[${index++}]`)) | ||
} | ||
@@ -277,0 +278,0 @@ |
'use strict' | ||
const { uid, states, sentCloseFrameState } = require('./constants') | ||
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants') | ||
const { | ||
@@ -8,5 +8,6 @@ kReadyState, | ||
kByteParser, | ||
kReceivedClose | ||
kReceivedClose, | ||
kResponse | ||
} = require('./symbols') | ||
const { fireEvent, failWebsocketConnection } = require('./util') | ||
const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished } = require('./util') | ||
const { channels } = require('../../core/diagnostics') | ||
@@ -19,2 +20,3 @@ const { CloseEvent } = require('./events') | ||
const { kHeadersList } = require('../../core/symbols') | ||
const { WebsocketFrameSend } = require('./frame') | ||
@@ -216,2 +218,68 @@ /** @type {import('crypto')} */ | ||
function closeWebSocketConnection (ws, code, reason, reasonByteLength) { | ||
if (isClosing(ws) || isClosed(ws)) { | ||
// If this's ready state is CLOSING (2) or CLOSED (3) | ||
// Do nothing. | ||
} else if (!isEstablished(ws)) { | ||
// If the WebSocket connection is not yet established | ||
// Fail the WebSocket connection and set this's ready state | ||
// to CLOSING (2). | ||
failWebsocketConnection(ws, 'Connection was closed before it was established.') | ||
ws[kReadyState] = states.CLOSING | ||
} else if (ws[kSentClose] === sentCloseFrameState.NOT_SENT) { | ||
// If the WebSocket closing handshake has not yet been started | ||
// Start the WebSocket closing handshake and set this's ready | ||
// state to CLOSING (2). | ||
// - If neither code nor reason is present, the WebSocket Close | ||
// message must not have a body. | ||
// - If code is present, then the status code to use in the | ||
// WebSocket Close message must be the integer given by code. | ||
// - If reason is also present, then reasonBytes must be | ||
// provided in the Close message after the status code. | ||
ws[kSentClose] = sentCloseFrameState.PROCESSING | ||
const frame = new WebsocketFrameSend() | ||
// If neither code nor reason is present, the WebSocket Close | ||
// message must not have a body. | ||
// If code is present, then the status code to use in the | ||
// WebSocket Close message must be the integer given by code. | ||
if (code !== undefined && reason === undefined) { | ||
frame.frameData = Buffer.allocUnsafe(2) | ||
frame.frameData.writeUInt16BE(code, 0) | ||
} else if (code !== undefined && reason !== undefined) { | ||
// If reason is also present, then reasonBytes must be | ||
// provided in the Close message after the status code. | ||
frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength) | ||
frame.frameData.writeUInt16BE(code, 0) | ||
// the body MAY contain UTF-8-encoded data with value /reason/ | ||
frame.frameData.write(reason, 2, 'utf-8') | ||
} else { | ||
frame.frameData = emptyBuffer | ||
} | ||
/** @type {import('stream').Duplex} */ | ||
const socket = ws[kResponse].socket | ||
socket.write(frame.createFrame(opcodes.CLOSE), (err) => { | ||
if (!err) { | ||
ws[kSentClose] = sentCloseFrameState.SENT | ||
} | ||
}) | ||
ws[kSentClose] = sentCloseFrameState.PROCESSING | ||
// 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. | ||
ws[kReadyState] = states.CLOSING | ||
} else { | ||
// Otherwise | ||
// Set this's ready state to CLOSING (2). | ||
ws[kReadyState] = states.CLOSING | ||
} | ||
} | ||
/** | ||
@@ -243,6 +311,6 @@ * @param {Buffer} chunk | ||
if (result) { | ||
if (result && !result.error) { | ||
code = result.code ?? 1005 | ||
reason = result.reason | ||
} else if (ws[kSentClose] !== sentCloseFrameState.SENT) { | ||
} else if (!ws[kReceivedClose]) { | ||
// If _The WebSocket | ||
@@ -300,3 +368,4 @@ // Connection is Closed_ and no Close control frame was received by the | ||
module.exports = { | ||
establishWebSocketConnection | ||
establishWebSocketConnection, | ||
closeWebSocketConnection | ||
} |
@@ -5,4 +5,9 @@ 'use strict' | ||
const BUFFER_SIZE = 16386 | ||
/** @type {import('crypto')} */ | ||
let crypto | ||
let buffer = null | ||
let bufIdx = BUFFER_SIZE | ||
try { | ||
@@ -12,3 +17,19 @@ crypto = require('node:crypto') | ||
} catch { | ||
crypto = { | ||
// not full compatibility, but minimum. | ||
randomFillSync: function randomFillSync (buffer, _offset, _size) { | ||
for (let i = 0; i < buffer.length; ++i) { | ||
buffer[i] = Math.random() * 255 | 0 | ||
} | ||
return buffer | ||
} | ||
} | ||
} | ||
function generateMask () { | ||
if (bufIdx === BUFFER_SIZE) { | ||
bufIdx = 0 | ||
crypto.randomFillSync((buffer ??= Buffer.allocUnsafe(BUFFER_SIZE)), 0, BUFFER_SIZE) | ||
} | ||
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]] | ||
} | ||
@@ -22,7 +43,8 @@ | ||
this.frameData = data | ||
this.maskKey = crypto.randomBytes(4) | ||
} | ||
createFrame (opcode) { | ||
const bodyLength = this.frameData?.byteLength ?? 0 | ||
const frameData = this.frameData | ||
const maskKey = generateMask() | ||
const bodyLength = frameData?.byteLength ?? 0 | ||
@@ -49,6 +71,6 @@ /** @type {number} */ | ||
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */ | ||
buffer[offset - 4] = this.maskKey[0] | ||
buffer[offset - 3] = this.maskKey[1] | ||
buffer[offset - 2] = this.maskKey[2] | ||
buffer[offset - 1] = this.maskKey[3] | ||
buffer[offset - 4] = maskKey[0] | ||
buffer[offset - 3] = maskKey[1] | ||
buffer[offset - 2] = maskKey[2] | ||
buffer[offset - 1] = maskKey[3] | ||
@@ -68,4 +90,4 @@ buffer[1] = payloadLength | ||
// mask body | ||
for (let i = 0; i < bodyLength; i++) { | ||
buffer[offset + i] = this.frameData[i] ^ this.maskKey[i % 4] | ||
for (let i = 0; i < bodyLength; ++i) { | ||
buffer[offset + i] = frameData[i] ^ maskKey[i & 3] | ||
} | ||
@@ -72,0 +94,0 @@ |
@@ -9,2 +9,3 @@ 'use strict' | ||
const { WebsocketFrameSend } = require('./frame') | ||
const { CloseEvent } = require('./events') | ||
@@ -59,3 +60,9 @@ // This code was influenced by ws released under the MIT license. | ||
this.#info.opcode = buffer[0] & 0x0F | ||
this.#info.masked = (buffer[1] & 0x80) === 0x80 | ||
if (this.#info.masked) { | ||
failWebsocketConnection(this.ws, 'Frame cannot be masked') | ||
return callback() | ||
} | ||
// If we receive a fragmented message, we use the type of the first | ||
@@ -107,2 +114,9 @@ // frame to parse the full message as binary/text, when it's terminated | ||
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) { | ||
@@ -245,3 +259,3 @@ // If an endpoint receives a Close frame and did not previously send a | ||
if (this.#byteOffset === 0) { | ||
if (this.#byteOffset === 0 && this.#info.payloadLength !== 0) { | ||
callback() | ||
@@ -317,3 +331,3 @@ break | ||
if (code !== undefined && !isValidStatusCode(code)) { | ||
return null | ||
return { code: 1002, reason: 'Invalid status code', error: true } | ||
} | ||
@@ -324,6 +338,6 @@ | ||
} catch { | ||
return null | ||
return { code: 1007, reason: 'Invalid UTF-8', error: true } | ||
} | ||
return { code, reason } | ||
return { code, reason, error: false } | ||
} | ||
@@ -330,0 +344,0 @@ |
@@ -107,3 +107,3 @@ 'use strict' | ||
// WebSocket object, whose contents are data | ||
dataForEvent = new Uint8Array(data).buffer | ||
dataForEvent = toArrayBuffer(data) | ||
} | ||
@@ -121,2 +121,9 @@ } | ||
function toArrayBuffer (buffer) { | ||
if (buffer.byteLength === buffer.buffer.byteLength) { | ||
return buffer.buffer | ||
} | ||
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) | ||
} | ||
/** | ||
@@ -202,3 +209,4 @@ * @see https://datatracker.ietf.org/doc/html/rfc6455 | ||
fireEvent('error', ws, (type, init) => new ErrorEvent(type, init), { | ||
error: new Error(reason) | ||
error: new Error(reason), | ||
message: reason | ||
}) | ||
@@ -205,0 +213,0 @@ } |
@@ -5,4 +5,4 @@ 'use strict' | ||
const { URLSerializer } = require('../fetch/data-url') | ||
const { getGlobalOrigin } = require('../fetch/global') | ||
const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes, emptyBuffer } = require('./constants') | ||
const { environmentSettingsObject } = require('../fetch/util') | ||
const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes } = require('./constants') | ||
const { | ||
@@ -20,9 +20,7 @@ kWebSocketURL, | ||
isEstablished, | ||
isClosed, | ||
isClosing, | ||
isValidSubprotocol, | ||
failWebsocketConnection, | ||
fireEvent | ||
} = require('./util') | ||
const { establishWebSocketConnection } = require('./connection') | ||
const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection') | ||
const { WebsocketFrameSend } = require('./frame') | ||
@@ -33,5 +31,8 @@ const { ByteParser } = require('./receiver') | ||
const { types } = require('node:util') | ||
const { ErrorEvent } = require('./events') | ||
let experimentalWarned = false | ||
const FastBuffer = Buffer[Symbol.species] | ||
// https://websockets.spec.whatwg.org/#interface-definition | ||
@@ -73,3 +74,3 @@ class WebSocket extends EventTarget { | ||
// 1. Let baseURL be this's relevant settings object's API base URL. | ||
const baseURL = getGlobalOrigin() | ||
const baseURL = environmentSettingsObject.settingsObject.baseUrl | ||
@@ -204,63 +205,3 @@ // 1. Let urlRecord be the result of applying the URL parser to url with baseURL. | ||
// 3. Run the first matching steps from the following list: | ||
if (isClosing(this) || isClosed(this)) { | ||
// If this's ready state is CLOSING (2) or CLOSED (3) | ||
// Do nothing. | ||
} else if (!isEstablished(this)) { | ||
// If the WebSocket connection is not yet established | ||
// Fail the WebSocket connection and set this's ready state | ||
// to CLOSING (2). | ||
failWebsocketConnection(this, 'Connection was closed before it was established.') | ||
this[kReadyState] = WebSocket.CLOSING | ||
} else if (this[kSentClose] === sentCloseFrameState.NOT_SENT) { | ||
// If the WebSocket closing handshake has not yet been started | ||
// Start the WebSocket closing handshake and set this's ready | ||
// state to CLOSING (2). | ||
// - If neither code nor reason is present, the WebSocket Close | ||
// message must not have a body. | ||
// - If code is present, then the status code to use in the | ||
// WebSocket Close message must be the integer given by code. | ||
// - If reason is also present, then reasonBytes must be | ||
// provided in the Close message after the status code. | ||
this[kSentClose] = sentCloseFrameState.PROCESSING | ||
const frame = new WebsocketFrameSend() | ||
// If neither code nor reason is present, the WebSocket Close | ||
// message must not have a body. | ||
// If code is present, then the status code to use in the | ||
// WebSocket Close message must be the integer given by code. | ||
if (code !== undefined && reason === undefined) { | ||
frame.frameData = Buffer.allocUnsafe(2) | ||
frame.frameData.writeUInt16BE(code, 0) | ||
} else if (code !== undefined && reason !== undefined) { | ||
// If reason is also present, then reasonBytes must be | ||
// provided in the Close message after the status code. | ||
frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength) | ||
frame.frameData.writeUInt16BE(code, 0) | ||
// the body MAY contain UTF-8-encoded data with value /reason/ | ||
frame.frameData.write(reason, 2, 'utf-8') | ||
} else { | ||
frame.frameData = emptyBuffer | ||
} | ||
/** @type {import('stream').Duplex} */ | ||
const socket = this[kResponse].socket | ||
socket.write(frame.createFrame(opcodes.CLOSE), (err) => { | ||
if (!err) { | ||
this[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[kReadyState] = states.CLOSING | ||
} else { | ||
// Otherwise | ||
// Set this's ready state to CLOSING (2). | ||
this[kReadyState] = WebSocket.CLOSING | ||
} | ||
closeWebSocketConnection(this, code, reason, reasonByteLength) | ||
} | ||
@@ -331,3 +272,3 @@ | ||
const value = Buffer.from(data) | ||
const value = new FastBuffer(data) | ||
const frame = new WebsocketFrameSend(value) | ||
@@ -353,3 +294,3 @@ const buffer = frame.createFrame(opcodes.BINARY) | ||
const ab = Buffer.from(data, data.byteOffset, data.byteLength) | ||
const ab = new FastBuffer(data, data.byteOffset, data.byteLength) | ||
@@ -378,3 +319,3 @@ const frame = new WebsocketFrameSend(ab) | ||
data.arrayBuffer().then((ab) => { | ||
const value = Buffer.from(ab) | ||
const value = new FastBuffer(ab) | ||
frame.frameData = value | ||
@@ -532,5 +473,4 @@ const buffer = frame.createFrame(opcodes.BINARY) | ||
const parser = new ByteParser(this) | ||
parser.on('drain', function onParserDrain () { | ||
this.ws[kResponse].socket.resume() | ||
}) | ||
parser.on('drain', onParserDrain) | ||
parser.on('error', onParserError.bind(this)) | ||
@@ -659,4 +599,14 @@ response.socket.ws = this | ||
function onParserDrain () { | ||
this.ws[kResponse].socket.resume() | ||
} | ||
function onParserError (err) { | ||
fireEvent('error', this, () => new ErrorEvent('error', { error: err, message: err.reason })) | ||
closeWebSocketConnection(this, err.code) | ||
} | ||
module.exports = { | ||
WebSocket | ||
} |
{ | ||
"name": "undici", | ||
"version": "6.15.0", | ||
"version": "6.16.0", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -80,3 +80,3 @@ "homepage": "https://undici.nodejs.org", | ||
"test:fetch": "npm run build:node && npm run test:fetch:nobuild", | ||
"test:fetch:nobuild": "borp --expose-gc -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy", | ||
"test:fetch:nobuild": "borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy", | ||
"test:interceptors": "borp -p \"test/interceptors/*.js\"", | ||
@@ -109,3 +109,3 @@ "test:jest": "cross-env NODE_V8_COVERAGE= jest", | ||
"abort-controller": "^3.0.0", | ||
"borp": "^0.12.0", | ||
"borp": "^0.13.0", | ||
"c8": "^9.1.0", | ||
@@ -112,0 +112,0 @@ "cross-env": "^7.0.3", |
@@ -88,3 +88,3 @@ // based on https://github.com/Ethan-Arrowood/undici-fetch/blob/249269714db874351589d2d364a0645d5160ae71/index.d.ts (MIT license) | ||
readonly entries: () => SpecIterableIterator<[string, string]> | ||
readonly [Symbol.iterator]: () => SpecIterator<[string, string]> | ||
readonly [Symbol.iterator]: () => SpecIterableIterator<[string, string]> | ||
} | ||
@@ -167,2 +167,3 @@ | ||
readonly redirect: RequestRedirect | ||
readonly referrer: string | ||
readonly referrerPolicy: ReferrerPolicy | ||
@@ -169,0 +170,0 @@ readonly url: string |
@@ -74,7 +74,7 @@ import { IncomingHttpHeaders } from './header' | ||
path: string; | ||
origin: string; | ||
method: string; | ||
body?: BodyInit | Dispatcher.DispatchOptions['body']; | ||
headers: Headers | Record<string, string>; | ||
maxRedirections: number; | ||
headers?: Headers | Record<string, string>; | ||
origin?: string; | ||
body?: BodyInit | Dispatcher.DispatchOptions['body'] | null; | ||
maxRedirections?: number; | ||
} | ||
@@ -81,0 +81,0 @@ |
@@ -11,22 +11,9 @@ export namespace util { | ||
* @param headers Header object | ||
* @param obj Object to specify a proxy object. Used to assign parsed values. | ||
* @returns If `obj` is specified, it is equivalent to `obj`. | ||
*/ | ||
export function parseHeaders( | ||
headers: | ||
| Record<string, string | string[]> | ||
| (Buffer | string | (Buffer | string)[])[] | ||
headers: (Buffer | string | (Buffer | string)[])[], | ||
obj?: Record<string, string | string[]> | ||
): Record<string, string | string[]>; | ||
/** | ||
* Receives a header object and returns the parsed value. | ||
* @param headers Header object | ||
* @param obj Object to specify a proxy object. Used to assign parsed values. But, if `headers` is an object, it is not used. | ||
* @returns If `headers` is an object, it is `headers`. Otherwise, if `obj` is specified, it is equivalent to `obj`. | ||
*/ | ||
export function parseHeaders< | ||
H extends | ||
| Record<string, string | string[]> | ||
| (Buffer | string | (Buffer | string)[])[] | ||
>( | ||
headers: H, | ||
obj?: H extends any[] ? Record<string, string | string[]> : never | ||
): Record<string, string | string[]>; | ||
} |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1116952
23484