Update websocket.js

This commit is contained in:
ZyLacx 2021-12-27 17:15:51 +01:00
parent 091f4a8f41
commit 3db08b859b

454
node_modules/ws/lib/websocket.js generated vendored
View file

@ -1,3 +1,5 @@
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Readable$" }] */
'use strict';
const EventEmitter = require('events');
@ -6,6 +8,7 @@ const http = require('http');
const net = require('net');
const tls = require('tls');
const { randomBytes, createHash } = require('crypto');
const { Readable } = require('stream');
const { URL } = require('url');
const PerMessageDeflate = require('./permessage-deflate');
@ -36,23 +39,22 @@ class WebSocket extends EventEmitter {
/**
* Create a new `WebSocket`.
*
* @param {(String|url.URL)} address The URL to which to connect
* @param {(String|String[])} protocols The subprotocols
* @param {Object} options Connection options
* @param {(String|URL)} address The URL to which to connect
* @param {(String|String[])} [protocols] The subprotocols
* @param {Object} [options] Connection options
*/
constructor(address, protocols, options) {
super();
this.readyState = WebSocket.CONNECTING;
this.protocol = '';
this._binaryType = BINARY_TYPES[0];
this._closeCode = 1006;
this._closeFrameReceived = false;
this._closeFrameSent = false;
this._closeMessage = '';
this._closeTimer = null;
this._closeCode = 1006;
this._extensions = {};
this._protocol = '';
this._readyState = WebSocket.CONNECTING;
this._receiver = null;
this._sender = null;
this._socket = null;
@ -75,19 +77,6 @@ class WebSocket extends EventEmitter {
}
}
get CONNECTING() {
return WebSocket.CONNECTING;
}
get CLOSING() {
return WebSocket.CLOSING;
}
get CLOSED() {
return WebSocket.CLOSED;
}
get OPEN() {
return WebSocket.OPEN;
}
/**
* This deviates from the WHATWG interface since ws doesn't support the
* required default "blob" type (instead we define a custom "nodebuffer"
@ -126,17 +115,83 @@ class WebSocket extends EventEmitter {
return Object.keys(this._extensions).join();
}
/**
* @type {Function}
*/
/* istanbul ignore next */
get onclose() {
return undefined;
}
/* istanbul ignore next */
set onclose(listener) {}
/**
* @type {Function}
*/
/* istanbul ignore next */
get onerror() {
return undefined;
}
/* istanbul ignore next */
set onerror(listener) {}
/**
* @type {Function}
*/
/* istanbul ignore next */
get onopen() {
return undefined;
}
/* istanbul ignore next */
set onopen(listener) {}
/**
* @type {Function}
*/
/* istanbul ignore next */
get onmessage() {
return undefined;
}
/* istanbul ignore next */
set onmessage(listener) {}
/**
* @type {String}
*/
get protocol() {
return this._protocol;
}
/**
* @type {Number}
*/
get readyState() {
return this._readyState;
}
/**
* @type {String}
*/
get url() {
return this._url;
}
/**
* Set up the socket and the internal resources.
*
* @param {net.Socket} socket The network socket between the server and client
* @param {(net.Socket|tls.Socket)} socket The network socket between the
* server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Number} maxPayload The maximum allowed message size
* @param {Number} [maxPayload=0] The maximum allowed message size
* @private
*/
setSocket(socket, head, maxPayload) {
const receiver = new Receiver(
this._binaryType,
this.binaryType,
this._extensions,
this._isServer,
maxPayload
@ -166,7 +221,7 @@ class WebSocket extends EventEmitter {
socket.on('end', socketOnEnd);
socket.on('error', socketOnError);
this.readyState = WebSocket.OPEN;
this._readyState = WebSocket.OPEN;
this.emit('open');
}
@ -177,7 +232,7 @@ class WebSocket extends EventEmitter {
*/
emitClose() {
if (!this._socket) {
this.readyState = WebSocket.CLOSED;
this._readyState = WebSocket.CLOSED;
this.emit('close', this._closeCode, this._closeMessage);
return;
}
@ -187,7 +242,7 @@ class WebSocket extends EventEmitter {
}
this._receiver.removeAllListeners();
this.readyState = WebSocket.CLOSED;
this._readyState = WebSocket.CLOSED;
this.emit('close', this._closeCode, this._closeMessage);
}
@ -206,8 +261,8 @@ class WebSocket extends EventEmitter {
* - - - - -|fin|<---------------------+
* +---+
*
* @param {Number} code Status code explaining why the connection is closing
* @param {String} data A string explaining why the connection is closing
* @param {Number} [code] Status code explaining why the connection is closing
* @param {String} [data] A string explaining why the connection is closing
* @public
*/
close(code, data) {
@ -218,11 +273,17 @@ class WebSocket extends EventEmitter {
}
if (this.readyState === WebSocket.CLOSING) {
if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
if (
this._closeFrameSent &&
(this._closeFrameReceived || this._receiver._writableState.errorEmitted)
) {
this._socket.end();
}
return;
}
this.readyState = WebSocket.CLOSING;
this._readyState = WebSocket.CLOSING;
this._sender.close(code, data, !this._isServer, (err) => {
//
// This error is handled by the `'error'` listener on the socket. We only
@ -231,7 +292,13 @@ class WebSocket extends EventEmitter {
if (err) return;
this._closeFrameSent = true;
if (this._closeFrameReceived) this._socket.end();
if (
this._closeFrameReceived ||
this._receiver._writableState.errorEmitted
) {
this._socket.end();
}
});
//
@ -246,9 +313,9 @@ class WebSocket extends EventEmitter {
/**
* Send a ping.
*
* @param {*} data The data to send
* @param {Boolean} mask Indicates whether or not to mask `data`
* @param {Function} cb Callback which is executed when the ping is sent
* @param {*} [data] The data to send
* @param {Boolean} [mask] Indicates whether or not to mask `data`
* @param {Function} [cb] Callback which is executed when the ping is sent
* @public
*/
ping(data, mask, cb) {
@ -278,9 +345,9 @@ class WebSocket extends EventEmitter {
/**
* Send a pong.
*
* @param {*} data The data to send
* @param {Boolean} mask Indicates whether or not to mask `data`
* @param {Function} cb Callback which is executed when the pong is sent
* @param {*} [data] The data to send
* @param {Boolean} [mask] Indicates whether or not to mask `data`
* @param {Function} [cb] Callback which is executed when the pong is sent
* @public
*/
pong(data, mask, cb) {
@ -311,13 +378,15 @@ class WebSocket extends EventEmitter {
* Send a data message.
*
* @param {*} data The message to send
* @param {Object} options Options object
* @param {Boolean} options.compress Specifies whether or not to compress
* @param {Object} [options] Options object
* @param {Boolean} [options.compress] Specifies whether or not to compress
* `data`
* @param {Boolean} options.binary Specifies whether `data` is binary or text
* @param {Boolean} options.fin Specifies whether the fragment is the last one
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Function} cb Callback which is executed when data is written out
* @param {Boolean} [options.binary] Specifies whether `data` is binary or
* text
* @param {Boolean} [options.fin=true] Specifies whether the fragment is the
* last one
* @param {Boolean} [options.mask] Specifies whether or not to mask `data`
* @param {Function} [cb] Callback which is executed when data is written out
* @public
*/
send(data, options, cb) {
@ -365,14 +434,93 @@ class WebSocket extends EventEmitter {
}
if (this._socket) {
this.readyState = WebSocket.CLOSING;
this._readyState = WebSocket.CLOSING;
this._socket.destroy();
}
}
}
readyStates.forEach((readyState, i) => {
WebSocket[readyState] = i;
/**
* @constant {Number} CONNECTING
* @memberof WebSocket
*/
Object.defineProperty(WebSocket, 'CONNECTING', {
enumerable: true,
value: readyStates.indexOf('CONNECTING')
});
/**
* @constant {Number} CONNECTING
* @memberof WebSocket.prototype
*/
Object.defineProperty(WebSocket.prototype, 'CONNECTING', {
enumerable: true,
value: readyStates.indexOf('CONNECTING')
});
/**
* @constant {Number} OPEN
* @memberof WebSocket
*/
Object.defineProperty(WebSocket, 'OPEN', {
enumerable: true,
value: readyStates.indexOf('OPEN')
});
/**
* @constant {Number} OPEN
* @memberof WebSocket.prototype
*/
Object.defineProperty(WebSocket.prototype, 'OPEN', {
enumerable: true,
value: readyStates.indexOf('OPEN')
});
/**
* @constant {Number} CLOSING
* @memberof WebSocket
*/
Object.defineProperty(WebSocket, 'CLOSING', {
enumerable: true,
value: readyStates.indexOf('CLOSING')
});
/**
* @constant {Number} CLOSING
* @memberof WebSocket.prototype
*/
Object.defineProperty(WebSocket.prototype, 'CLOSING', {
enumerable: true,
value: readyStates.indexOf('CLOSING')
});
/**
* @constant {Number} CLOSED
* @memberof WebSocket
*/
Object.defineProperty(WebSocket, 'CLOSED', {
enumerable: true,
value: readyStates.indexOf('CLOSED')
});
/**
* @constant {Number} CLOSED
* @memberof WebSocket.prototype
*/
Object.defineProperty(WebSocket.prototype, 'CLOSED', {
enumerable: true,
value: readyStates.indexOf('CLOSED')
});
[
'binaryType',
'bufferedAmount',
'extensions',
'protocol',
'readyState',
'url'
].forEach((property) => {
Object.defineProperty(WebSocket.prototype, property, { enumerable: true });
});
//
@ -381,12 +529,7 @@ readyStates.forEach((readyState, i) => {
//
['open', 'error', 'close', 'message'].forEach((method) => {
Object.defineProperty(WebSocket.prototype, `on${method}`, {
/**
* Return the listener of the event.
*
* @return {(Function|undefined)} The event listener or `undefined`
* @public
*/
enumerable: true,
get() {
const listeners = this.listeners(method);
for (let i = 0; i < listeners.length; i++) {
@ -395,12 +538,6 @@ readyStates.forEach((readyState, i) => {
return undefined;
},
/**
* Add a listener for the event.
*
* @param {Function} listener The listener to add
* @public
*/
set(listener) {
const listeners = this.listeners(method);
for (let i = 0; i < listeners.length; i++) {
@ -423,20 +560,23 @@ module.exports = WebSocket;
* Initialize a WebSocket client.
*
* @param {WebSocket} websocket The client to initialize
* @param {(String|url.URL)} address The URL to which to connect
* @param {String} protocols The subprotocols
* @param {Object} options Connection options
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable
* @param {(String|URL)} address The URL to which to connect
* @param {String} [protocols] The subprotocols
* @param {Object} [options] Connection options
* @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
* permessage-deflate
* @param {Number} options.handshakeTimeout Timeout in milliseconds for the
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
* handshake request
* @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version`
* header
* @param {String} options.origin Value of the `Origin` or
* @param {Number} [options.protocolVersion=13] Value of the
* `Sec-WebSocket-Version` header
* @param {String} [options.origin] Value of the `Origin` or
* `Sec-WebSocket-Origin` header
* @param {Number} options.maxPayload The maximum allowed message size
* @param {Boolean} options.followRedirects Whether or not to follow redirects
* @param {Number} options.maxRedirects The maximum number of redirects allowed
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
* size
* @param {Boolean} [options.followRedirects=false] Whether or not to follow
* redirects
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
* allowed
* @private
*/
function initAsClient(websocket, address, protocols, options) {
@ -469,16 +609,23 @@ function initAsClient(websocket, address, protocols, options) {
if (address instanceof URL) {
parsedUrl = address;
websocket.url = address.href;
websocket._url = address.href;
} else {
parsedUrl = new URL(address);
websocket.url = address;
websocket._url = address;
}
const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
throw new Error(`Invalid URL: ${websocket.url}`);
const err = new Error(`Invalid URL: ${websocket.url}`);
if (websocket._redirects === 0) {
throw err;
} else {
emitErrorAndClose(websocket, err);
return;
}
}
const isSecure =
@ -544,12 +691,10 @@ function initAsClient(websocket, address, protocols, options) {
}
req.on('error', (err) => {
if (websocket._req.aborted) return;
if (req === null || req.aborted) return;
req = websocket._req = null;
websocket.readyState = WebSocket.CLOSING;
websocket.emit('error', err);
websocket.emitClose();
emitErrorAndClose(websocket, err);
});
req.on('response', (res) => {
@ -569,7 +714,14 @@ function initAsClient(websocket, address, protocols, options) {
req.abort();
const addr = new URL(location, address);
let addr;
try {
addr = new URL(location, address);
} catch (err) {
emitErrorAndClose(websocket, err);
return;
}
initAsClient(websocket, addr, protocols, options);
} else if (!websocket.emit('unexpected-response', req, res)) {
@ -618,32 +770,72 @@ function initAsClient(websocket, address, protocols, options) {
return;
}
if (serverProt) websocket.protocol = serverProt;
if (serverProt) websocket._protocol = serverProt;
if (perMessageDeflate) {
try {
const extensions = parse(res.headers['sec-websocket-extensions']);
const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
if (extensions[PerMessageDeflate.extensionName]) {
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
websocket._extensions[
PerMessageDeflate.extensionName
] = perMessageDeflate;
}
} catch (err) {
abortHandshake(
websocket,
socket,
'Invalid Sec-WebSocket-Extensions header'
);
if (secWebSocketExtensions !== undefined) {
if (!perMessageDeflate) {
const message =
'Server sent a Sec-WebSocket-Extensions header but no extension ' +
'was requested';
abortHandshake(websocket, socket, message);
return;
}
let extensions;
try {
extensions = parse(secWebSocketExtensions);
} catch (err) {
const message = 'Invalid Sec-WebSocket-Extensions header';
abortHandshake(websocket, socket, message);
return;
}
const extensionNames = Object.keys(extensions);
if (extensionNames.length) {
if (
extensionNames.length !== 1 ||
extensionNames[0] !== PerMessageDeflate.extensionName
) {
const message =
'Server indicated an extension that was not requested';
abortHandshake(websocket, socket, message);
return;
}
try {
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
} catch (err) {
const message = 'Invalid Sec-WebSocket-Extensions header';
abortHandshake(websocket, socket, message);
return;
}
websocket._extensions[PerMessageDeflate.extensionName] =
perMessageDeflate;
}
}
websocket.setSocket(socket, head, opts.maxPayload);
});
}
/**
* Emit the `'error'` and `'close'` event.
*
* @param {WebSocket} websocket The WebSocket instance
* @param {Error} The error to emit
* @private
*/
function emitErrorAndClose(websocket, err) {
websocket._readyState = WebSocket.CLOSING;
websocket.emit('error', err);
websocket.emitClose();
}
/**
* Create a `net.Socket` and initiate a connection.
*
@ -667,7 +859,7 @@ function tlsConnect(options) {
options.path = undefined;
if (!options.servername && options.servername !== '') {
options.servername = options.host;
options.servername = net.isIP(options.host) ? '' : options.host;
}
return tls.connect(options);
@ -677,19 +869,29 @@ function tlsConnect(options) {
* Abort the handshake and emit an error.
*
* @param {WebSocket} websocket The WebSocket instance
* @param {(http.ClientRequest|net.Socket)} stream The request to abort or the
* socket to destroy
* @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to
* abort or the socket to destroy
* @param {String} message The error message
* @private
*/
function abortHandshake(websocket, stream, message) {
websocket.readyState = WebSocket.CLOSING;
websocket._readyState = WebSocket.CLOSING;
const err = new Error(message);
Error.captureStackTrace(err, abortHandshake);
if (stream.setHeader) {
stream.abort();
if (stream.socket && !stream.socket.destroyed) {
//
// On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if
// called after the request completed. See
// https://github.com/websockets/ws/issues/1869.
//
stream.socket.destroy();
}
stream.once('abort', websocket.emitClose.bind(websocket));
websocket.emit('error', err);
} else {
@ -704,8 +906,8 @@ function abortHandshake(websocket, stream, message) {
* when the `readyState` attribute is `CLOSING` or `CLOSED`.
*
* @param {WebSocket} websocket The WebSocket instance
* @param {*} data The data to send
* @param {Function} cb Callback
* @param {*} [data] The data to send
* @param {Function} [cb] Callback
* @private
*/
function sendAfterClose(websocket, data, cb) {
@ -741,13 +943,15 @@ function sendAfterClose(websocket, data, cb) {
function receiverOnConclude(code, reason) {
const websocket = this[kWebSocket];
websocket._socket.removeListener('data', socketOnData);
websocket._socket.resume();
websocket._closeFrameReceived = true;
websocket._closeMessage = reason;
websocket._closeCode = code;
if (websocket._socket[kWebSocket] === undefined) return;
websocket._socket.removeListener('data', socketOnData);
process.nextTick(resume, websocket._socket);
if (code === 1005) websocket.close();
else websocket.close(code, reason);
}
@ -770,12 +974,19 @@ function receiverOnDrain() {
function receiverOnError(err) {
const websocket = this[kWebSocket];
if (websocket._socket[kWebSocket] !== undefined) {
websocket._socket.removeListener('data', socketOnData);
websocket.readyState = WebSocket.CLOSING;
websocket._closeCode = err[kStatusCode];
//
// On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
// https://github.com/websockets/ws/issues/1940.
//
process.nextTick(resume, websocket._socket);
websocket.close(err[kStatusCode]);
}
websocket.emit('error', err);
websocket._socket.destroy();
}
/**
@ -820,6 +1031,16 @@ function receiverOnPong(data) {
this[kWebSocket].emit('pong', data);
}
/**
* Resume a readable stream
*
* @param {Readable} stream The readable stream
* @private
*/
function resume(stream) {
stream.resume();
}
/**
* The listener of the `net.Socket` `'close'` event.
*
@ -829,9 +1050,12 @@ function socketOnClose() {
const websocket = this[kWebSocket];
this.removeListener('close', socketOnClose);
this.removeListener('data', socketOnData);
this.removeListener('end', socketOnEnd);
websocket.readyState = WebSocket.CLOSING;
websocket._readyState = WebSocket.CLOSING;
let chunk;
//
// The close frame might not have been received or the `'end'` event emitted,
@ -840,13 +1064,19 @@ function socketOnClose() {
// it. If the readable side of the socket is in flowing mode then there is no
// buffered data as everything has been already written and `readable.read()`
// will return `null`. If instead, the socket is paused, any possible buffered
// data will be read as a single chunk and emitted synchronously in a single
// `'data'` event.
// data will be read as a single chunk.
//
websocket._socket.read();
if (
!this._readableState.endEmitted &&
!websocket._closeFrameReceived &&
!websocket._receiver._writableState.errorEmitted &&
(chunk = websocket._socket.read()) !== null
) {
websocket._receiver.write(chunk);
}
websocket._receiver.end();
this.removeListener('data', socketOnData);
this[kWebSocket] = undefined;
clearTimeout(websocket._closeTimer);
@ -882,7 +1112,7 @@ function socketOnData(chunk) {
function socketOnEnd() {
const websocket = this[kWebSocket];
websocket.readyState = WebSocket.CLOSING;
websocket._readyState = WebSocket.CLOSING;
websocket._receiver.end();
this.end();
}
@ -899,7 +1129,7 @@ function socketOnError() {
this.on('error', NOOP);
if (websocket) {
websocket.readyState = WebSocket.CLOSING;
websocket._readyState = WebSocket.CLOSING;
this.destroy();
}
}