754 lines
22 KiB
JavaScript
754 lines
22 KiB
JavaScript
'use strict';
|
||
|
||
const { webidl } = require('../fetch/webidl');
|
||
const { URLSerializer } = require('../fetch/data-url');
|
||
const { environmentSettingsObject } = require('../fetch/util');
|
||
const {
|
||
staticPropertyDescriptors,
|
||
states,
|
||
sentCloseFrameState,
|
||
sendHints,
|
||
opcodes,
|
||
} = require('./constants');
|
||
const {
|
||
isConnecting,
|
||
isEstablished,
|
||
isClosing,
|
||
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 { types } = require('node:util');
|
||
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events');
|
||
const { SendQueue } = require('./sender');
|
||
const { channels } = require('../../core/diagnostics');
|
||
|
||
/**
|
||
* @typedef {object} Handler
|
||
* @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
|
||
* @property {(code: number, reason: any) => void} onFail
|
||
* @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 {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),
|
||
onFail: (code, reason) => this.#onFail(code, reason),
|
||
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(),
|
||
|
||
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', {
|
||
clamp: true,
|
||
});
|
||
}
|
||
|
||
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 (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.
|
||
|
||
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);
|
||
}
|
||
|
||
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.#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);
|
||
}
|
||
|
||
#onFail(code, reason) {
|
||
if (reason) {
|
||
// TODO: process.nextTick
|
||
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
|
||
error: new Error(reason),
|
||
message: reason,
|
||
});
|
||
}
|
||
|
||
if (!this.#handler.wasEverConnected) {
|
||
this.#handler.readyState = states.CLOSED;
|
||
|
||
// If the WebSocket connection could not be established, it is also said
|
||
// that _The WebSocket Connection is Closed_, but not _cleanly_.
|
||
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
||
wasClean: false,
|
||
code,
|
||
reason,
|
||
});
|
||
}
|
||
}
|
||
|
||
#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;
|
||
} else 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;
|
||
}
|
||
|
||
// 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.
|
||
// TODO
|
||
|
||
// 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,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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: () => new Array(0),
|
||
},
|
||
{
|
||
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 (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
|
||
return V;
|
||
}
|
||
}
|
||
|
||
return webidl.converters.USVString(V);
|
||
};
|
||
|
||
module.exports = {
|
||
WebSocket,
|
||
};
|