Update websocket.js
This commit is contained in:
parent
091f4a8f41
commit
3db08b859b
1 changed files with 343 additions and 113 deletions
454
node_modules/ws/lib/websocket.js
generated
vendored
454
node_modules/ws/lib/websocket.js
generated
vendored
|
@ -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];
|
||||||
|
|
||||||
|
if (websocket._socket[kWebSocket] !== undefined) {
|
||||||
websocket._socket.removeListener('data', socketOnData);
|
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.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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue