aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/undici/lib/web/websocket/websocket.js
diff options
context:
space:
mode:
Diffstat (limited to 'vanilla/node_modules/undici/lib/web/websocket/websocket.js')
-rw-r--r--vanilla/node_modules/undici/lib/web/websocket/websocket.js739
1 files changed, 739 insertions, 0 deletions
diff --git a/vanilla/node_modules/undici/lib/web/websocket/websocket.js b/vanilla/node_modules/undici/lib/web/websocket/websocket.js
new file mode 100644
index 0000000..3314976
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/web/websocket/websocket.js
@@ -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
+}