Update websocket.js

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

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

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