初始化
This commit is contained in:
739
node_modules/undici/lib/web/websocket/websocket.js
generated
vendored
Normal file
739
node_modules/undici/lib/web/websocket/websocket.js
generated
vendored
Normal file
@@ -0,0 +1,739 @@
|
||||
'use strict'
|
||||
|
||||
const { isArrayBuffer } = require('node:util/types')
|
||||
const { webidl } = require('../webidl')
|
||||
const { URLSerializer } = require('../fetch/data-url')
|
||||
const { environmentSettingsObject } = require('../fetch/util')
|
||||
const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes } = require('./constants')
|
||||
const {
|
||||
isConnecting,
|
||||
isEstablished,
|
||||
isClosing,
|
||||
isClosed,
|
||||
isValidSubprotocol,
|
||||
fireEvent,
|
||||
utf8Decode,
|
||||
toArrayBuffer,
|
||||
getURLRecord
|
||||
} = require('./util')
|
||||
const { establishWebSocketConnection, closeWebSocketConnection, failWebsocketConnection } = require('./connection')
|
||||
const { ByteParser } = require('./receiver')
|
||||
const { kEnumerableProperty } = require('../../core/util')
|
||||
const { getGlobalDispatcher } = require('../../global')
|
||||
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
|
||||
const { SendQueue } = require('./sender')
|
||||
const { WebsocketFrameSend } = require('./frame')
|
||||
const { channels } = require('../../core/diagnostics')
|
||||
|
||||
/**
|
||||
* @typedef {object} Handler
|
||||
* @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
|
||||
* @property {(opcode: number, data: Buffer) => void} onMessage
|
||||
* @property {(error: Error) => void} onParserError
|
||||
* @property {() => void} onParserDrain
|
||||
* @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
|
||||
* @property {Set<number>} closeState
|
||||
* @property {import('../fetch/index').Fetch} controller
|
||||
* @property {boolean} [wasEverConnected=false]
|
||||
*/
|
||||
|
||||
// https://websockets.spec.whatwg.org/#interface-definition
|
||||
class WebSocket extends EventTarget {
|
||||
#events = {
|
||||
open: null,
|
||||
error: null,
|
||||
close: null,
|
||||
message: null
|
||||
}
|
||||
|
||||
#bufferedAmount = 0
|
||||
#protocol = ''
|
||||
#extensions = ''
|
||||
|
||||
/** @type {SendQueue} */
|
||||
#sendQueue
|
||||
|
||||
/** @type {Handler} */
|
||||
#handler = {
|
||||
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
|
||||
onMessage: (opcode, data) => this.#onMessage(opcode, data),
|
||||
onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
|
||||
onParserDrain: () => this.#onParserDrain(),
|
||||
onSocketData: (chunk) => {
|
||||
if (!this.#parser.write(chunk)) {
|
||||
this.#handler.socket.pause()
|
||||
}
|
||||
},
|
||||
onSocketError: (err) => {
|
||||
this.#handler.readyState = states.CLOSING
|
||||
|
||||
if (channels.socketError.hasSubscribers) {
|
||||
channels.socketError.publish(err)
|
||||
}
|
||||
|
||||
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,
|
||||
closeState: new Set(),
|
||||
controller: null,
|
||||
wasEverConnected: false
|
||||
}
|
||||
|
||||
#url
|
||||
#binaryType
|
||||
/** @type {import('./receiver').ByteParser} */
|
||||
#parser
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string|string[]} protocols
|
||||
*/
|
||||
constructor (url, protocols = []) {
|
||||
super()
|
||||
|
||||
webidl.util.markAsUncloneable(this)
|
||||
|
||||
const prefix = 'WebSocket constructor'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
|
||||
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
|
||||
|
||||
url = webidl.converters.USVString(url)
|
||||
protocols = options.protocols
|
||||
|
||||
// 1. Let baseURL be this's relevant settings object's API base URL.
|
||||
const baseURL = environmentSettingsObject.settingsObject.baseUrl
|
||||
|
||||
// 2. Let urlRecord be the result of getting a URL record given url and baseURL.
|
||||
const urlRecord = getURLRecord(url, baseURL)
|
||||
|
||||
// 3. If protocols is a string, set protocols to a sequence consisting
|
||||
// of just that string.
|
||||
if (typeof protocols === 'string') {
|
||||
protocols = [protocols]
|
||||
}
|
||||
|
||||
// 4. If any of the values in protocols occur more than once or otherwise
|
||||
// fail to match the requirements for elements that comprise the value
|
||||
// of `Sec-WebSocket-Protocol` fields as defined by The WebSocket
|
||||
// protocol, then throw a "SyntaxError" DOMException.
|
||||
if (protocols.length !== new Set(protocols.map(p => p.toLowerCase())).size) {
|
||||
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
||||
}
|
||||
|
||||
if (protocols.length > 0 && !protocols.every(p => isValidSubprotocol(p))) {
|
||||
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
||||
}
|
||||
|
||||
// 5. Set this's url to urlRecord.
|
||||
this.#url = new URL(urlRecord.href)
|
||||
|
||||
// 6. Let client be this's relevant settings object.
|
||||
const client = environmentSettingsObject.settingsObject
|
||||
|
||||
// 7. Run this step in parallel:
|
||||
// 7.1. Establish a WebSocket connection given urlRecord, protocols,
|
||||
// and client.
|
||||
this.#handler.controller = establishWebSocketConnection(
|
||||
urlRecord,
|
||||
protocols,
|
||||
client,
|
||||
this.#handler,
|
||||
options
|
||||
)
|
||||
|
||||
// Each WebSocket object has an associated ready state, which is a
|
||||
// number representing the state of the connection. Initially it must
|
||||
// be CONNECTING (0).
|
||||
this.#handler.readyState = WebSocket.CONNECTING
|
||||
|
||||
// The extensions attribute must initially return the empty string.
|
||||
|
||||
// The protocol attribute must initially return the empty string.
|
||||
|
||||
// Each WebSocket object has an associated binary type, which is a
|
||||
// BinaryType. Initially it must be "blob".
|
||||
this.#binaryType = 'blob'
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#dom-websocket-close
|
||||
* @param {number|undefined} code
|
||||
* @param {string|undefined} reason
|
||||
*/
|
||||
close (code = undefined, reason = undefined) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
const prefix = 'WebSocket.close'
|
||||
|
||||
if (code !== undefined) {
|
||||
code = webidl.converters['unsigned short'](code, prefix, 'code', webidl.attributes.Clamp)
|
||||
}
|
||||
|
||||
if (reason !== undefined) {
|
||||
reason = webidl.converters.USVString(reason)
|
||||
}
|
||||
|
||||
// 1. If code is the special value "missing", then set code to null.
|
||||
code ??= null
|
||||
|
||||
// 2. If reason is the special value "missing", then set reason to the empty string.
|
||||
reason ??= ''
|
||||
|
||||
// 3. Close the WebSocket with this, code, and reason.
|
||||
closeWebSocketConnection(this.#handler, code, reason, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#dom-websocket-send
|
||||
* @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data
|
||||
*/
|
||||
send (data) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
const prefix = 'WebSocket.send'
|
||||
webidl.argumentLengthCheck(arguments, 1, prefix)
|
||||
|
||||
data = webidl.converters.WebSocketSendData(data, prefix, 'data')
|
||||
|
||||
// 1. If this's ready state is CONNECTING, then throw an
|
||||
// "InvalidStateError" DOMException.
|
||||
if (isConnecting(this.#handler.readyState)) {
|
||||
throw new DOMException('Sent before connected.', 'InvalidStateError')
|
||||
}
|
||||
|
||||
// 2. Run the appropriate set of steps from the following list:
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-6.1
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
||||
|
||||
if (!isEstablished(this.#handler.readyState) || isClosing(this.#handler.readyState)) {
|
||||
return
|
||||
}
|
||||
|
||||
// If data is a string
|
||||
if (typeof data === 'string') {
|
||||
// If the WebSocket connection is established and the WebSocket
|
||||
// closing handshake has not yet started, then the user agent
|
||||
// must send a WebSocket Message comprised of the data argument
|
||||
// using a text frame opcode; if the data cannot be sent, e.g.
|
||||
// because it would need to be buffered but the buffer is full,
|
||||
// the user agent must flag the WebSocket as full and then close
|
||||
// the WebSocket connection. Any invocation of this method with a
|
||||
// string argument that does not throw an exception must increase
|
||||
// the bufferedAmount attribute by the number of bytes needed to
|
||||
// express the argument as UTF-8.
|
||||
|
||||
const buffer = Buffer.from(data)
|
||||
|
||||
this.#bufferedAmount += buffer.byteLength
|
||||
this.#sendQueue.add(buffer, () => {
|
||||
this.#bufferedAmount -= buffer.byteLength
|
||||
}, sendHints.text)
|
||||
} else if (isArrayBuffer(data)) {
|
||||
// If the WebSocket connection is established, and the WebSocket
|
||||
// closing handshake has not yet started, then the user agent must
|
||||
// send a WebSocket Message comprised of data using a binary frame
|
||||
// opcode; if the data cannot be sent, e.g. because it would need
|
||||
// to be buffered but the buffer is full, the user agent must flag
|
||||
// the WebSocket as full and then close the WebSocket connection.
|
||||
// The data to be sent is the data stored in the buffer described
|
||||
// by the ArrayBuffer object. Any invocation of this method with an
|
||||
// ArrayBuffer argument that does not throw an exception must
|
||||
// increase the bufferedAmount attribute by the length of the
|
||||
// ArrayBuffer in bytes.
|
||||
|
||||
this.#bufferedAmount += data.byteLength
|
||||
this.#sendQueue.add(data, () => {
|
||||
this.#bufferedAmount -= data.byteLength
|
||||
}, sendHints.arrayBuffer)
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
// If the WebSocket connection is established, and the WebSocket
|
||||
// closing handshake has not yet started, then the user agent must
|
||||
// send a WebSocket Message comprised of data using a binary frame
|
||||
// opcode; if the data cannot be sent, e.g. because it would need to
|
||||
// be buffered but the buffer is full, the user agent must flag the
|
||||
// WebSocket as full and then close the WebSocket connection. The
|
||||
// data to be sent is the data stored in the section of the buffer
|
||||
// described by the ArrayBuffer object that data references. Any
|
||||
// invocation of this method with this kind of argument that does
|
||||
// not throw an exception must increase the bufferedAmount attribute
|
||||
// by the length of data’s buffer in bytes.
|
||||
|
||||
this.#bufferedAmount += data.byteLength
|
||||
this.#sendQueue.add(data, () => {
|
||||
this.#bufferedAmount -= data.byteLength
|
||||
}, sendHints.typedArray)
|
||||
} else if (webidl.is.Blob(data)) {
|
||||
// If the WebSocket connection is established, and the WebSocket
|
||||
// closing handshake has not yet started, then the user agent must
|
||||
// send a WebSocket Message comprised of data using a binary frame
|
||||
// opcode; if the data cannot be sent, e.g. because it would need to
|
||||
// be buffered but the buffer is full, the user agent must flag the
|
||||
// WebSocket as full and then close the WebSocket connection. The data
|
||||
// to be sent is the raw data represented by the Blob object. Any
|
||||
// invocation of this method with a Blob argument that does not throw
|
||||
// an exception must increase the bufferedAmount attribute by the size
|
||||
// of the Blob object’s raw data, in bytes.
|
||||
|
||||
this.#bufferedAmount += data.size
|
||||
this.#sendQueue.add(data, () => {
|
||||
this.#bufferedAmount -= data.size
|
||||
}, sendHints.blob)
|
||||
}
|
||||
}
|
||||
|
||||
get readyState () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
// The readyState getter steps are to return this's ready state.
|
||||
return this.#handler.readyState
|
||||
}
|
||||
|
||||
get bufferedAmount () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#bufferedAmount
|
||||
}
|
||||
|
||||
get url () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
// The url getter steps are to return this's url, serialized.
|
||||
return URLSerializer(this.#url)
|
||||
}
|
||||
|
||||
get extensions () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#extensions
|
||||
}
|
||||
|
||||
get protocol () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#protocol
|
||||
}
|
||||
|
||||
get onopen () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#events.open
|
||||
}
|
||||
|
||||
set onopen (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
if (this.#events.open) {
|
||||
this.removeEventListener('open', this.#events.open)
|
||||
}
|
||||
|
||||
const listener = webidl.converters.EventHandlerNonNull(fn)
|
||||
|
||||
if (listener !== null) {
|
||||
this.addEventListener('open', listener)
|
||||
this.#events.open = fn
|
||||
} else {
|
||||
this.#events.open = null
|
||||
}
|
||||
}
|
||||
|
||||
get onerror () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#events.error
|
||||
}
|
||||
|
||||
set onerror (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
if (this.#events.error) {
|
||||
this.removeEventListener('error', this.#events.error)
|
||||
}
|
||||
|
||||
const listener = webidl.converters.EventHandlerNonNull(fn)
|
||||
|
||||
if (listener !== null) {
|
||||
this.addEventListener('error', listener)
|
||||
this.#events.error = fn
|
||||
} else {
|
||||
this.#events.error = null
|
||||
}
|
||||
}
|
||||
|
||||
get onclose () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#events.close
|
||||
}
|
||||
|
||||
set onclose (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
if (this.#events.close) {
|
||||
this.removeEventListener('close', this.#events.close)
|
||||
}
|
||||
|
||||
const listener = webidl.converters.EventHandlerNonNull(fn)
|
||||
|
||||
if (listener !== null) {
|
||||
this.addEventListener('close', listener)
|
||||
this.#events.close = fn
|
||||
} else {
|
||||
this.#events.close = null
|
||||
}
|
||||
}
|
||||
|
||||
get onmessage () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#events.message
|
||||
}
|
||||
|
||||
set onmessage (fn) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
if (this.#events.message) {
|
||||
this.removeEventListener('message', this.#events.message)
|
||||
}
|
||||
|
||||
const listener = webidl.converters.EventHandlerNonNull(fn)
|
||||
|
||||
if (listener !== null) {
|
||||
this.addEventListener('message', listener)
|
||||
this.#events.message = fn
|
||||
} else {
|
||||
this.#events.message = null
|
||||
}
|
||||
}
|
||||
|
||||
get binaryType () {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
return this.#binaryType
|
||||
}
|
||||
|
||||
set binaryType (type) {
|
||||
webidl.brandCheck(this, WebSocket)
|
||||
|
||||
if (type !== 'blob' && type !== 'arraybuffer') {
|
||||
this.#binaryType = 'blob'
|
||||
} else {
|
||||
this.#binaryType = type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
||||
*/
|
||||
#onConnectionEstablished (response, parsedExtensions) {
|
||||
// processResponse is called when the "response’s header list has been received and initialized."
|
||||
// once this happens, the connection is open
|
||||
this.#handler.socket = response.socket
|
||||
|
||||
const parser = new ByteParser(this.#handler, parsedExtensions)
|
||||
parser.on('drain', () => this.#handler.onParserDrain())
|
||||
parser.on('error', (err) => this.#handler.onParserError(err))
|
||||
|
||||
this.#parser = parser
|
||||
this.#sendQueue = new SendQueue(response.socket)
|
||||
|
||||
// 1. Change the ready state to OPEN (1).
|
||||
this.#handler.readyState = states.OPEN
|
||||
|
||||
// 2. Change the extensions attribute’s value to the extensions in use, if
|
||||
// it is not the null value.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-9.1
|
||||
const extensions = response.headersList.get('sec-websocket-extensions')
|
||||
|
||||
if (extensions !== null) {
|
||||
this.#extensions = extensions
|
||||
}
|
||||
|
||||
// 3. Change the protocol attribute’s value to the subprotocol in use, if
|
||||
// it is not the null value.
|
||||
// https://datatracker.ietf.org/doc/html/rfc6455#section-1.9
|
||||
const protocol = response.headersList.get('sec-websocket-protocol')
|
||||
|
||||
if (protocol !== null) {
|
||||
this.#protocol = protocol
|
||||
}
|
||||
|
||||
// 4. Fire an event named open at the WebSocket object.
|
||||
fireEvent('open', this)
|
||||
|
||||
if (channels.open.hasSubscribers) {
|
||||
// Convert headers to a plain object for the event
|
||||
const headers = response.headersList.entries
|
||||
channels.open.publish({
|
||||
address: response.socket.address(),
|
||||
protocol: this.#protocol,
|
||||
extensions: this.#extensions,
|
||||
websocket: this,
|
||||
handshakeResponse: {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#onMessage (type, data) {
|
||||
// 1. If ready state is not OPEN (1), then return.
|
||||
if (this.#handler.readyState !== states.OPEN) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Let dataForEvent be determined by switching on type and binary type:
|
||||
let dataForEvent
|
||||
|
||||
if (type === opcodes.TEXT) {
|
||||
// -> type indicates that the data is Text
|
||||
// a new DOMString containing data
|
||||
try {
|
||||
dataForEvent = utf8Decode(data)
|
||||
} catch {
|
||||
failWebsocketConnection(this.#handler, 1007, 'Received invalid UTF-8 in text frame.')
|
||||
return
|
||||
}
|
||||
} else if (type === opcodes.BINARY) {
|
||||
if (this.#binaryType === 'blob') {
|
||||
// -> type indicates that the data is Binary and binary type is "blob"
|
||||
// a new Blob object, created in the relevant Realm of the WebSocket
|
||||
// object, that represents data as its raw data
|
||||
dataForEvent = new Blob([data])
|
||||
} else {
|
||||
// -> type indicates that the data is Binary and binary type is "arraybuffer"
|
||||
// a new ArrayBuffer object, created in the relevant Realm of the
|
||||
// WebSocket object, whose contents are data
|
||||
dataForEvent = toArrayBuffer(data)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fire an event named message at the WebSocket object, using MessageEvent,
|
||||
// with the origin attribute initialized to the serialization of the WebSocket
|
||||
// object’s url's origin, and the data attribute initialized to dataForEvent.
|
||||
fireEvent('message', this, createFastMessageEvent, {
|
||||
origin: this.#url.origin,
|
||||
data: dataForEvent
|
||||
})
|
||||
}
|
||||
|
||||
#onParserDrain () {
|
||||
this.#handler.socket.resume()
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
|
||||
*/
|
||||
#onSocketClose () {
|
||||
// If the TCP connection was closed after the
|
||||
// WebSocket closing handshake was completed, the WebSocket connection
|
||||
// is said to have been closed _cleanly_.
|
||||
const wasClean =
|
||||
this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
||||
this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
||||
|
||||
let code = 1005
|
||||
let reason = ''
|
||||
|
||||
const result = this.#parser?.closingInfo
|
||||
|
||||
if (result && !result.error) {
|
||||
code = result.code ?? 1005
|
||||
reason = result.reason
|
||||
}
|
||||
|
||||
// 1. Change the ready state to CLOSED (3).
|
||||
this.#handler.readyState = states.CLOSED
|
||||
|
||||
// 2. If the user agent was required to fail the WebSocket
|
||||
// connection, or if the WebSocket connection was closed
|
||||
// after being flagged as full, fire an event named error
|
||||
// at the WebSocket object.
|
||||
if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
||||
// If _The WebSocket
|
||||
// Connection is Closed_ and no Close control frame was received by the
|
||||
// endpoint (such as could occur if the underlying transport connection
|
||||
// is lost), _The WebSocket Connection Close Code_ is considered to be
|
||||
// 1006.
|
||||
code = 1006
|
||||
|
||||
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
|
||||
error: new TypeError(reason)
|
||||
})
|
||||
}
|
||||
|
||||
// 3. Fire an event named close at the WebSocket object,
|
||||
// using CloseEvent, with the wasClean attribute
|
||||
// initialized to true if the connection closed cleanly
|
||||
// and false otherwise, the code attribute initialized to
|
||||
// the WebSocket connection close code, and the reason
|
||||
// attribute initialized to the result of applying UTF-8
|
||||
// decode without BOM to the WebSocket connection close
|
||||
// reason.
|
||||
// TODO: process.nextTick
|
||||
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
||||
wasClean, code, reason
|
||||
})
|
||||
|
||||
if (channels.close.hasSubscribers) {
|
||||
channels.close.publish({
|
||||
websocket: this,
|
||||
code,
|
||||
reason
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN
|
||||
// https://websockets.spec.whatwg.org/#dom-websocket-closing
|
||||
WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING
|
||||
// https://websockets.spec.whatwg.org/#dom-websocket-closed
|
||||
WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED
|
||||
|
||||
Object.defineProperties(WebSocket.prototype, {
|
||||
CONNECTING: staticPropertyDescriptors,
|
||||
OPEN: staticPropertyDescriptors,
|
||||
CLOSING: staticPropertyDescriptors,
|
||||
CLOSED: staticPropertyDescriptors,
|
||||
url: kEnumerableProperty,
|
||||
readyState: kEnumerableProperty,
|
||||
bufferedAmount: kEnumerableProperty,
|
||||
onopen: kEnumerableProperty,
|
||||
onerror: kEnumerableProperty,
|
||||
onclose: kEnumerableProperty,
|
||||
close: kEnumerableProperty,
|
||||
onmessage: kEnumerableProperty,
|
||||
binaryType: kEnumerableProperty,
|
||||
send: kEnumerableProperty,
|
||||
extensions: kEnumerableProperty,
|
||||
protocol: kEnumerableProperty,
|
||||
[Symbol.toStringTag]: {
|
||||
value: 'WebSocket',
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
|
||||
Object.defineProperties(WebSocket, {
|
||||
CONNECTING: staticPropertyDescriptors,
|
||||
OPEN: staticPropertyDescriptors,
|
||||
CLOSING: staticPropertyDescriptors,
|
||||
CLOSED: staticPropertyDescriptors
|
||||
})
|
||||
|
||||
webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(
|
||||
webidl.converters.DOMString
|
||||
)
|
||||
|
||||
webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {
|
||||
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && Symbol.iterator in V) {
|
||||
return webidl.converters['sequence<DOMString>'](V)
|
||||
}
|
||||
|
||||
return webidl.converters.DOMString(V, prefix, argument)
|
||||
}
|
||||
|
||||
// This implements the proposal made in https://github.com/whatwg/websockets/issues/42
|
||||
webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
||||
{
|
||||
key: 'protocols',
|
||||
converter: webidl.converters['DOMString or sequence<DOMString>'],
|
||||
defaultValue: () => []
|
||||
},
|
||||
{
|
||||
key: 'dispatcher',
|
||||
converter: webidl.converters.any,
|
||||
defaultValue: () => getGlobalDispatcher()
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
converter: webidl.nullableConverter(webidl.converters.HeadersInit)
|
||||
}
|
||||
])
|
||||
|
||||
webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
|
||||
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && !(Symbol.iterator in V)) {
|
||||
return webidl.converters.WebSocketInit(V)
|
||||
}
|
||||
|
||||
return { protocols: webidl.converters['DOMString or sequence<DOMString>'](V) }
|
||||
}
|
||||
|
||||
webidl.converters.WebSocketSendData = function (V) {
|
||||
if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
|
||||
if (webidl.is.Blob(V)) {
|
||||
return V
|
||||
}
|
||||
|
||||
if (webidl.is.BufferSource(V)) {
|
||||
return V
|
||||
}
|
||||
}
|
||||
|
||||
return webidl.converters.USVString(V)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
WebSocket,
|
||||
ping
|
||||
}
|
||||
Reference in New Issue
Block a user