642 lines
20 KiB
JavaScript
642 lines
20 KiB
JavaScript
|
'use strict'
|
|||
|
|
|||
|
const { webidl } = require('../fetch/webidl')
|
|||
|
const { DOMException } = require('../fetch/constants')
|
|||
|
const { URLSerializer } = require('../fetch/dataURL')
|
|||
|
const { getGlobalOrigin } = require('../fetch/global')
|
|||
|
const { staticPropertyDescriptors, states, opcodes, emptyBuffer } = require('./constants')
|
|||
|
const {
|
|||
|
kWebSocketURL,
|
|||
|
kReadyState,
|
|||
|
kController,
|
|||
|
kBinaryType,
|
|||
|
kResponse,
|
|||
|
kSentClose,
|
|||
|
kByteParser
|
|||
|
} = require('./symbols')
|
|||
|
const { isEstablished, isClosing, isValidSubprotocol, failWebsocketConnection, fireEvent } = require('./util')
|
|||
|
const { establishWebSocketConnection } = require('./connection')
|
|||
|
const { WebsocketFrameSend } = require('./frame')
|
|||
|
const { ByteParser } = require('./receiver')
|
|||
|
const { kEnumerableProperty, isBlobLike } = require('../core/util')
|
|||
|
const { getGlobalDispatcher } = require('../global')
|
|||
|
const { types } = require('util')
|
|||
|
|
|||
|
let experimentalWarned = 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 = ''
|
|||
|
|
|||
|
/**
|
|||
|
* @param {string} url
|
|||
|
* @param {string|string[]} protocols
|
|||
|
*/
|
|||
|
constructor (url, protocols = []) {
|
|||
|
super()
|
|||
|
|
|||
|
webidl.argumentLengthCheck(arguments, 1, { header: 'WebSocket constructor' })
|
|||
|
|
|||
|
if (!experimentalWarned) {
|
|||
|
experimentalWarned = true
|
|||
|
process.emitWarning('WebSockets are experimental, expect them to change at any time.', {
|
|||
|
code: 'UNDICI-WS'
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols)
|
|||
|
|
|||
|
url = webidl.converters.USVString(url)
|
|||
|
protocols = options.protocols
|
|||
|
|
|||
|
// 1. Let baseURL be this's relevant settings object's API base URL.
|
|||
|
const baseURL = getGlobalOrigin()
|
|||
|
|
|||
|
// 1. Let urlRecord be the result of applying the URL parser to url with baseURL.
|
|||
|
let urlRecord
|
|||
|
|
|||
|
try {
|
|||
|
urlRecord = new URL(url, baseURL)
|
|||
|
} catch (e) {
|
|||
|
// 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
|
|||
|
throw new DOMException(e, 'SyntaxError')
|
|||
|
}
|
|||
|
|
|||
|
// 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws".
|
|||
|
if (urlRecord.protocol === 'http:') {
|
|||
|
urlRecord.protocol = 'ws:'
|
|||
|
} else if (urlRecord.protocol === 'https:') {
|
|||
|
// 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss".
|
|||
|
urlRecord.protocol = 'wss:'
|
|||
|
}
|
|||
|
|
|||
|
// 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
|
|||
|
if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') {
|
|||
|
throw new DOMException(
|
|||
|
`Expected a ws: or wss: protocol, got ${urlRecord.protocol}`,
|
|||
|
'SyntaxError'
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
// 7. If urlRecord’s fragment is non-null, then throw a "SyntaxError"
|
|||
|
// DOMException.
|
|||
|
if (urlRecord.hash || urlRecord.href.endsWith('#')) {
|
|||
|
throw new DOMException('Got fragment', 'SyntaxError')
|
|||
|
}
|
|||
|
|
|||
|
// 8. If protocols is a string, set protocols to a sequence consisting
|
|||
|
// of just that string.
|
|||
|
if (typeof protocols === 'string') {
|
|||
|
protocols = [protocols]
|
|||
|
}
|
|||
|
|
|||
|
// 9. 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')
|
|||
|
}
|
|||
|
|
|||
|
// 10. Set this's url to urlRecord.
|
|||
|
this[kWebSocketURL] = new URL(urlRecord.href)
|
|||
|
|
|||
|
// 11. Let client be this's relevant settings object.
|
|||
|
|
|||
|
// 12. Run this step in parallel:
|
|||
|
|
|||
|
// 1. Establish a WebSocket connection given urlRecord, protocols,
|
|||
|
// and client.
|
|||
|
this[kController] = establishWebSocketConnection(
|
|||
|
urlRecord,
|
|||
|
protocols,
|
|||
|
this,
|
|||
|
(response) => this.#onConnectionEstablished(response),
|
|||
|
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[kReadyState] = 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[kBinaryType] = '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)
|
|||
|
|
|||
|
if (code !== undefined) {
|
|||
|
code = webidl.converters['unsigned short'](code, { clamp: true })
|
|||
|
}
|
|||
|
|
|||
|
if (reason !== undefined) {
|
|||
|
reason = webidl.converters.USVString(reason)
|
|||
|
}
|
|||
|
|
|||
|
// 1. If code is present, but is neither an integer equal to 1000 nor an
|
|||
|
// integer in the range 3000 to 4999, inclusive, throw an
|
|||
|
// "InvalidAccessError" DOMException.
|
|||
|
if (code !== undefined) {
|
|||
|
if (code !== 1000 && (code < 3000 || code > 4999)) {
|
|||
|
throw new DOMException('invalid code', 'InvalidAccessError')
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
let reasonByteLength = 0
|
|||
|
|
|||
|
// 2. If reason is present, then run these substeps:
|
|||
|
if (reason !== undefined) {
|
|||
|
// 1. Let reasonBytes be the result of encoding reason.
|
|||
|
// 2. If reasonBytes is longer than 123 bytes, then throw a
|
|||
|
// "SyntaxError" DOMException.
|
|||
|
reasonByteLength = Buffer.byteLength(reason)
|
|||
|
|
|||
|
if (reasonByteLength > 123) {
|
|||
|
throw new DOMException(
|
|||
|
`Reason must be less than 123 bytes; received ${reasonByteLength}`,
|
|||
|
'SyntaxError'
|
|||
|
)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 3. Run the first matching steps from the following list:
|
|||
|
if (this[kReadyState] === WebSocket.CLOSING || this[kReadyState] === WebSocket.CLOSED) {
|
|||
|
// 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 (!isClosing(this)) {
|
|||
|
// 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.
|
|||
|
|
|||
|
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] = true
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
// 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
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @see https://websockets.spec.whatwg.org/#dom-websocket-send
|
|||
|
* @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data
|
|||
|
*/
|
|||
|
send (data) {
|
|||
|
webidl.brandCheck(this, WebSocket)
|
|||
|
|
|||
|
webidl.argumentLengthCheck(arguments, 1, { header: 'WebSocket.send' })
|
|||
|
|
|||
|
data = webidl.converters.WebSocketSendData(data)
|
|||
|
|
|||
|
// 1. If this's ready state is CONNECTING, then throw an
|
|||
|
// "InvalidStateError" DOMException.
|
|||
|
if (this[kReadyState] === WebSocket.CONNECTING) {
|
|||
|
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) || isClosing(this)) {
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
/** @type {import('stream').Duplex} */
|
|||
|
const socket = this[kResponse].socket
|
|||
|
|
|||
|
// 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 value = Buffer.from(data)
|
|||
|
const frame = new WebsocketFrameSend(value)
|
|||
|
const buffer = frame.createFrame(opcodes.TEXT)
|
|||
|
|
|||
|
this.#bufferedAmount += value.byteLength
|
|||
|
socket.write(buffer, () => {
|
|||
|
this.#bufferedAmount -= value.byteLength
|
|||
|
})
|
|||
|
} else if (types.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.
|
|||
|
|
|||
|
const value = Buffer.from(data)
|
|||
|
const frame = new WebsocketFrameSend(value)
|
|||
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|||
|
|
|||
|
this.#bufferedAmount += value.byteLength
|
|||
|
socket.write(buffer, () => {
|
|||
|
this.#bufferedAmount -= value.byteLength
|
|||
|
})
|
|||
|
} 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.
|
|||
|
|
|||
|
const ab = Buffer.from(data, data.byteOffset, data.byteLength)
|
|||
|
|
|||
|
const frame = new WebsocketFrameSend(ab)
|
|||
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|||
|
|
|||
|
this.#bufferedAmount += ab.byteLength
|
|||
|
socket.write(buffer, () => {
|
|||
|
this.#bufferedAmount -= ab.byteLength
|
|||
|
})
|
|||
|
} else if (isBlobLike(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.
|
|||
|
|
|||
|
const frame = new WebsocketFrameSend()
|
|||
|
|
|||
|
data.arrayBuffer().then((ab) => {
|
|||
|
const value = Buffer.from(ab)
|
|||
|
frame.frameData = value
|
|||
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|||
|
|
|||
|
this.#bufferedAmount += value.byteLength
|
|||
|
socket.write(buffer, () => {
|
|||
|
this.#bufferedAmount -= value.byteLength
|
|||
|
})
|
|||
|
})
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
get readyState () {
|
|||
|
webidl.brandCheck(this, WebSocket)
|
|||
|
|
|||
|
// The readyState getter steps are to return this's ready state.
|
|||
|
return this[kReadyState]
|
|||
|
}
|
|||
|
|
|||
|
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[kWebSocketURL])
|
|||
|
}
|
|||
|
|
|||
|
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)
|
|||
|
}
|
|||
|
|
|||
|
if (typeof fn === 'function') {
|
|||
|
this.#events.open = fn
|
|||
|
this.addEventListener('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)
|
|||
|
}
|
|||
|
|
|||
|
if (typeof fn === 'function') {
|
|||
|
this.#events.error = fn
|
|||
|
this.addEventListener('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)
|
|||
|
}
|
|||
|
|
|||
|
if (typeof fn === 'function') {
|
|||
|
this.#events.close = fn
|
|||
|
this.addEventListener('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)
|
|||
|
}
|
|||
|
|
|||
|
if (typeof fn === 'function') {
|
|||
|
this.#events.message = fn
|
|||
|
this.addEventListener('message', fn)
|
|||
|
} else {
|
|||
|
this.#events.message = null
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
get binaryType () {
|
|||
|
webidl.brandCheck(this, WebSocket)
|
|||
|
|
|||
|
return this[kBinaryType]
|
|||
|
}
|
|||
|
|
|||
|
set binaryType (type) {
|
|||
|
webidl.brandCheck(this, WebSocket)
|
|||
|
|
|||
|
if (type !== 'blob' && type !== 'arraybuffer') {
|
|||
|
this[kBinaryType] = 'blob'
|
|||
|
} else {
|
|||
|
this[kBinaryType] = type
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
|||
|
*/
|
|||
|
#onConnectionEstablished (response) {
|
|||
|
// processResponse is called when the "response’s header list has been received and initialized."
|
|||
|
// once this happens, the connection is open
|
|||
|
this[kResponse] = response
|
|||
|
|
|||
|
const parser = new ByteParser(this)
|
|||
|
parser.on('drain', function onParserDrain () {
|
|||
|
this.ws[kResponse].socket.resume()
|
|||
|
})
|
|||
|
|
|||
|
response.socket.ws = this
|
|||
|
this[kByteParser] = parser
|
|||
|
|
|||
|
// 1. Change the ready state to OPEN (1).
|
|||
|
this[kReadyState] = 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)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 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) {
|
|||
|
if (webidl.util.Type(V) === 'Object' && Symbol.iterator in V) {
|
|||
|
return webidl.converters['sequence<DOMString>'](V)
|
|||
|
}
|
|||
|
|
|||
|
return webidl.converters.DOMString(V)
|
|||
|
}
|
|||
|
|
|||
|
// This implements the propsal made in https://github.com/whatwg/websockets/issues/42
|
|||
|
webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
|||
|
{
|
|||
|
key: 'protocols',
|
|||
|
converter: webidl.converters['DOMString or sequence<DOMString>'],
|
|||
|
get defaultValue () {
|
|||
|
return []
|
|||
|
}
|
|||
|
},
|
|||
|
{
|
|||
|
key: 'dispatcher',
|
|||
|
converter: (V) => V,
|
|||
|
get defaultValue () {
|
|||
|
return getGlobalDispatcher()
|
|||
|
}
|
|||
|
},
|
|||
|
{
|
|||
|
key: 'headers',
|
|||
|
converter: webidl.nullableConverter(webidl.converters.HeadersInit)
|
|||
|
}
|
|||
|
])
|
|||
|
|
|||
|
webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
|
|||
|
if (webidl.util.Type(V) === '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) === 'Object') {
|
|||
|
if (isBlobLike(V)) {
|
|||
|
return webidl.converters.Blob(V, { strict: false })
|
|||
|
}
|
|||
|
|
|||
|
if (ArrayBuffer.isView(V) || types.isAnyArrayBuffer(V)) {
|
|||
|
return webidl.converters.BufferSource(V)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return webidl.converters.USVString(V)
|
|||
|
}
|
|||
|
|
|||
|
module.exports = {
|
|||
|
WebSocket
|
|||
|
}
|