mirror of
https://github.com/S2-/minifyfromhtml.git
synced 2025-08-05 13:00:08 +02:00
update packages to latest version
This commit is contained in:
614
node_modules/ws/lib/websocket.js
generated
vendored
614
node_modules/ws/lib/websocket.js
generated
vendored
@@ -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');
|
||||
@@ -15,17 +18,23 @@ const {
|
||||
BINARY_TYPES,
|
||||
EMPTY_BUFFER,
|
||||
GUID,
|
||||
kForOnEventAttribute,
|
||||
kListener,
|
||||
kStatusCode,
|
||||
kWebSocket,
|
||||
NOOP
|
||||
} = require('./constants');
|
||||
const { addEventListener, removeEventListener } = require('./event-target');
|
||||
const {
|
||||
EventTarget: { addEventListener, removeEventListener }
|
||||
} = require('./event-target');
|
||||
const { format, parse } = require('./extension');
|
||||
const { toBuffer } = require('./buffer-util');
|
||||
|
||||
const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
|
||||
const protocolVersions = [8, 13];
|
||||
const closeTimeout = 30 * 1000;
|
||||
const kAborted = Symbol('kAborted');
|
||||
const protocolVersions = [8, 13];
|
||||
const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
|
||||
const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
||||
|
||||
/**
|
||||
* Class representing a WebSocket.
|
||||
@@ -36,7 +45,7 @@ class WebSocket extends EventEmitter {
|
||||
/**
|
||||
* 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 {Object} [options] Connection options
|
||||
*/
|
||||
@@ -47,9 +56,10 @@ class WebSocket extends EventEmitter {
|
||||
this._closeCode = 1006;
|
||||
this._closeFrameReceived = false;
|
||||
this._closeFrameSent = false;
|
||||
this._closeMessage = '';
|
||||
this._closeMessage = EMPTY_BUFFER;
|
||||
this._closeTimer = null;
|
||||
this._extensions = {};
|
||||
this._paused = false;
|
||||
this._protocol = '';
|
||||
this._readyState = WebSocket.CONNECTING;
|
||||
this._receiver = null;
|
||||
@@ -61,11 +71,15 @@ class WebSocket extends EventEmitter {
|
||||
this._isServer = false;
|
||||
this._redirects = 0;
|
||||
|
||||
if (Array.isArray(protocols)) {
|
||||
protocols = protocols.join(', ');
|
||||
} else if (typeof protocols === 'object' && protocols !== null) {
|
||||
options = protocols;
|
||||
protocols = undefined;
|
||||
if (protocols === undefined) {
|
||||
protocols = [];
|
||||
} else if (!Array.isArray(protocols)) {
|
||||
if (typeof protocols === 'object' && protocols !== null) {
|
||||
options = protocols;
|
||||
protocols = [];
|
||||
} else {
|
||||
protocols = [protocols];
|
||||
}
|
||||
}
|
||||
|
||||
initAsClient(this, address, protocols, options);
|
||||
@@ -112,6 +126,45 @@ class WebSocket extends EventEmitter {
|
||||
return Object.keys(this._extensions).join();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Boolean}
|
||||
*/
|
||||
get isPaused() {
|
||||
return this._paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Function}
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
get onclose() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Function}
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
get onerror() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Function}
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
get onopen() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Function}
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
get onmessage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
@@ -136,20 +189,27 @@ class WebSocket extends EventEmitter {
|
||||
/**
|
||||
* 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=0] The maximum allowed message size
|
||||
* @param {Object} options Options object
|
||||
* @param {Function} [options.generateMask] The function used to generate the
|
||||
* masking key
|
||||
* @param {Number} [options.maxPayload=0] The maximum allowed message size
|
||||
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
||||
* not to skip UTF-8 validation for text and close messages
|
||||
* @private
|
||||
*/
|
||||
setSocket(socket, head, maxPayload) {
|
||||
const receiver = new Receiver(
|
||||
this.binaryType,
|
||||
this._extensions,
|
||||
this._isServer,
|
||||
maxPayload
|
||||
);
|
||||
setSocket(socket, head, options) {
|
||||
const receiver = new Receiver({
|
||||
binaryType: this.binaryType,
|
||||
extensions: this._extensions,
|
||||
isServer: this._isServer,
|
||||
maxPayload: options.maxPayload,
|
||||
skipUTF8Validation: options.skipUTF8Validation
|
||||
});
|
||||
|
||||
this._sender = new Sender(socket, this._extensions);
|
||||
this._sender = new Sender(socket, this._extensions, options.generateMask);
|
||||
this._receiver = receiver;
|
||||
this._socket = socket;
|
||||
|
||||
@@ -214,7 +274,8 @@ class WebSocket extends EventEmitter {
|
||||
* +---+
|
||||
*
|
||||
* @param {Number} [code] Status code explaining why the connection is closing
|
||||
* @param {String} [data] A string explaining why the connection is closing
|
||||
* @param {(String|Buffer)} [data] The reason why the connection is
|
||||
* closing
|
||||
* @public
|
||||
*/
|
||||
close(code, data) {
|
||||
@@ -225,7 +286,13 @@ 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;
|
||||
}
|
||||
|
||||
@@ -238,7 +305,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();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
@@ -250,6 +323,23 @@ class WebSocket extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the socket.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
pause() {
|
||||
if (
|
||||
this.readyState === WebSocket.CONNECTING ||
|
||||
this.readyState === WebSocket.CLOSED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._paused = true;
|
||||
this._socket.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a ping.
|
||||
*
|
||||
@@ -314,15 +404,32 @@ class WebSocket extends EventEmitter {
|
||||
this._sender.pong(data || EMPTY_BUFFER, mask, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the socket.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
resume() {
|
||||
if (
|
||||
this.readyState === WebSocket.CONNECTING ||
|
||||
this.readyState === WebSocket.CLOSED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._paused = false;
|
||||
if (!this._receiver._writableState.needDrain) this._socket.resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* `data`
|
||||
* @param {Boolean} [options.binary] Specifies whether `data` is binary or
|
||||
* text
|
||||
* @param {Boolean} [options.compress] Specifies whether or not to compress
|
||||
* `data`
|
||||
* @param {Boolean} [options.fin=true] Specifies whether the fragment is the
|
||||
* last one
|
||||
* @param {Boolean} [options.mask] Specifies whether or not to mask `data`
|
||||
@@ -380,17 +487,83 @@ class WebSocket extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
readyStates.forEach((readyState, i) => {
|
||||
const descriptor = { enumerable: true, value: i };
|
||||
/**
|
||||
* @constant {Number} CONNECTING
|
||||
* @memberof WebSocket
|
||||
*/
|
||||
Object.defineProperty(WebSocket, 'CONNECTING', {
|
||||
enumerable: true,
|
||||
value: readyStates.indexOf('CONNECTING')
|
||||
});
|
||||
|
||||
Object.defineProperty(WebSocket.prototype, readyState, descriptor);
|
||||
Object.defineProperty(WebSocket, readyState, descriptor);
|
||||
/**
|
||||
* @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',
|
||||
'isPaused',
|
||||
'protocol',
|
||||
'readyState',
|
||||
'url'
|
||||
@@ -404,37 +577,27 @@ readyStates.forEach((readyState, i) => {
|
||||
//
|
||||
['open', 'error', 'close', 'message'].forEach((method) => {
|
||||
Object.defineProperty(WebSocket.prototype, `on${method}`, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
/**
|
||||
* Return the listener of the event.
|
||||
*
|
||||
* @return {(Function|undefined)} The event listener or `undefined`
|
||||
* @public
|
||||
*/
|
||||
get() {
|
||||
const listeners = this.listeners(method);
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i]._listener) return listeners[i]._listener;
|
||||
for (const listener of this.listeners(method)) {
|
||||
if (listener[kForOnEventAttribute]) return listener[kListener];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
* 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++) {
|
||||
//
|
||||
// Remove only the listeners added via `addEventListener`.
|
||||
//
|
||||
if (listeners[i]._listener) this.removeListener(method, listeners[i]);
|
||||
set(handler) {
|
||||
for (const listener of this.listeners(method)) {
|
||||
if (listener[kForOnEventAttribute]) {
|
||||
this.removeListener(method, listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.addEventListener(method, listener);
|
||||
|
||||
if (typeof handler !== 'function') return;
|
||||
|
||||
this.addEventListener(method, handler, {
|
||||
[kForOnEventAttribute]: true
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -448,29 +611,34 @@ 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 {(String|URL)} address The URL to which to connect
|
||||
* @param {Array} 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
|
||||
* handshake request
|
||||
* @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=104857600] The maximum allowed message
|
||||
* size
|
||||
* @param {Boolean} [options.followRedirects=false] Whether or not to follow
|
||||
* redirects
|
||||
* @param {Function} [options.generateMask] The function used to generate the
|
||||
* masking key
|
||||
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
|
||||
* handshake request
|
||||
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
||||
* size
|
||||
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
|
||||
* allowed
|
||||
* @param {String} [options.origin] Value of the `Origin` or
|
||||
* `Sec-WebSocket-Origin` header
|
||||
* @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
|
||||
* permessage-deflate
|
||||
* @param {Number} [options.protocolVersion=13] Value of the
|
||||
* `Sec-WebSocket-Version` header
|
||||
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
||||
* not to skip UTF-8 validation for text and close messages
|
||||
* @private
|
||||
*/
|
||||
function initAsClient(websocket, address, protocols, options) {
|
||||
const opts = {
|
||||
protocolVersion: protocolVersions[1],
|
||||
maxPayload: 100 * 1024 * 1024,
|
||||
skipUTF8Validation: false,
|
||||
perMessageDeflate: true,
|
||||
followRedirects: false,
|
||||
maxRedirects: 10,
|
||||
@@ -480,7 +648,7 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
hostname: undefined,
|
||||
protocol: undefined,
|
||||
timeout: undefined,
|
||||
method: undefined,
|
||||
method: 'GET',
|
||||
host: undefined,
|
||||
path: undefined,
|
||||
port: undefined
|
||||
@@ -499,21 +667,43 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
parsedUrl = address;
|
||||
websocket._url = address.href;
|
||||
} else {
|
||||
parsedUrl = new URL(address);
|
||||
try {
|
||||
parsedUrl = new URL(address);
|
||||
} catch (e) {
|
||||
throw new SyntaxError(`Invalid URL: ${address}`);
|
||||
}
|
||||
|
||||
websocket._url = address;
|
||||
}
|
||||
|
||||
const isSecure = parsedUrl.protocol === 'wss:';
|
||||
const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
|
||||
let invalidURLMessage;
|
||||
|
||||
if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
|
||||
throw new Error(`Invalid URL: ${websocket.url}`);
|
||||
if (parsedUrl.protocol !== 'ws:' && !isSecure && !isUnixSocket) {
|
||||
invalidURLMessage =
|
||||
'The URL\'s protocol must be one of "ws:", "wss:", or "ws+unix:"';
|
||||
} else if (isUnixSocket && !parsedUrl.pathname) {
|
||||
invalidURLMessage = "The URL's pathname is empty";
|
||||
} else if (parsedUrl.hash) {
|
||||
invalidURLMessage = 'The URL contains a fragment identifier';
|
||||
}
|
||||
|
||||
if (invalidURLMessage) {
|
||||
const err = new SyntaxError(invalidURLMessage);
|
||||
|
||||
if (websocket._redirects === 0) {
|
||||
throw err;
|
||||
} else {
|
||||
emitErrorAndClose(websocket, err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const isSecure =
|
||||
parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
|
||||
const defaultPort = isSecure ? 443 : 80;
|
||||
const key = randomBytes(16).toString('base64');
|
||||
const get = isSecure ? https.get : http.get;
|
||||
const request = isSecure ? https.request : http.request;
|
||||
const protocolSet = new Set();
|
||||
let perMessageDeflate;
|
||||
|
||||
opts.createConnection = isSecure ? tlsConnect : netConnect;
|
||||
@@ -523,11 +713,11 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
? parsedUrl.hostname.slice(1, -1)
|
||||
: parsedUrl.hostname;
|
||||
opts.headers = {
|
||||
...opts.headers,
|
||||
'Sec-WebSocket-Version': opts.protocolVersion,
|
||||
'Sec-WebSocket-Key': key,
|
||||
Connection: 'Upgrade',
|
||||
Upgrade: 'websocket',
|
||||
...opts.headers
|
||||
Upgrade: 'websocket'
|
||||
};
|
||||
opts.path = parsedUrl.pathname + parsedUrl.search;
|
||||
opts.timeout = opts.handshakeTimeout;
|
||||
@@ -542,8 +732,22 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
|
||||
});
|
||||
}
|
||||
if (protocols) {
|
||||
opts.headers['Sec-WebSocket-Protocol'] = protocols;
|
||||
if (protocols.length) {
|
||||
for (const protocol of protocols) {
|
||||
if (
|
||||
typeof protocol !== 'string' ||
|
||||
!subprotocolRegex.test(protocol) ||
|
||||
protocolSet.has(protocol)
|
||||
) {
|
||||
throw new SyntaxError(
|
||||
'An invalid or duplicated subprotocol was specified'
|
||||
);
|
||||
}
|
||||
|
||||
protocolSet.add(protocol);
|
||||
}
|
||||
|
||||
opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');
|
||||
}
|
||||
if (opts.origin) {
|
||||
if (opts.protocolVersion < 13) {
|
||||
@@ -563,7 +767,79 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
opts.path = parts[1];
|
||||
}
|
||||
|
||||
let req = (websocket._req = get(opts));
|
||||
let req;
|
||||
|
||||
if (opts.followRedirects) {
|
||||
if (websocket._redirects === 0) {
|
||||
websocket._originalUnixSocket = isUnixSocket;
|
||||
websocket._originalSecure = isSecure;
|
||||
websocket._originalHostOrSocketPath = isUnixSocket
|
||||
? opts.socketPath
|
||||
: parsedUrl.host;
|
||||
|
||||
const headers = options && options.headers;
|
||||
|
||||
//
|
||||
// Shallow copy the user provided options so that headers can be changed
|
||||
// without mutating the original object.
|
||||
//
|
||||
options = { ...options, headers: {} };
|
||||
|
||||
if (headers) {
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
options.headers[key.toLowerCase()] = value;
|
||||
}
|
||||
}
|
||||
} else if (websocket.listenerCount('redirect') === 0) {
|
||||
const isSameHost = isUnixSocket
|
||||
? websocket._originalUnixSocket
|
||||
? opts.socketPath === websocket._originalHostOrSocketPath
|
||||
: false
|
||||
: websocket._originalUnixSocket
|
||||
? false
|
||||
: parsedUrl.host === websocket._originalHostOrSocketPath;
|
||||
|
||||
if (!isSameHost || (websocket._originalSecure && !isSecure)) {
|
||||
//
|
||||
// Match curl 7.77.0 behavior and drop the following headers. These
|
||||
// headers are also dropped when following a redirect to a subdomain.
|
||||
//
|
||||
delete opts.headers.authorization;
|
||||
delete opts.headers.cookie;
|
||||
|
||||
if (!isSameHost) delete opts.headers.host;
|
||||
|
||||
opts.auth = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Match curl 7.77.0 behavior and make the first `Authorization` header win.
|
||||
// If the `Authorization` header is set, then there is nothing to do as it
|
||||
// will take precedence.
|
||||
//
|
||||
if (opts.auth && !options.headers.authorization) {
|
||||
options.headers.authorization =
|
||||
'Basic ' + Buffer.from(opts.auth).toString('base64');
|
||||
}
|
||||
|
||||
req = websocket._req = request(opts);
|
||||
|
||||
if (websocket._redirects) {
|
||||
//
|
||||
// Unlike what is done for the `'upgrade'` event, no early exit is
|
||||
// triggered here if the user calls `websocket.close()` or
|
||||
// `websocket.terminate()` from a listener of the `'redirect'` event. This
|
||||
// is because the user can also call `request.destroy()` with an error
|
||||
// before calling `websocket.close()` or `websocket.terminate()` and this
|
||||
// would result in an error being emitted on the `request` object with no
|
||||
// `'error'` event listeners attached.
|
||||
//
|
||||
websocket.emit('redirect', websocket.url, req);
|
||||
}
|
||||
} else {
|
||||
req = websocket._req = request(opts);
|
||||
}
|
||||
|
||||
if (opts.timeout) {
|
||||
req.on('timeout', () => {
|
||||
@@ -572,12 +848,10 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
}
|
||||
|
||||
req.on('error', (err) => {
|
||||
if (req === null || req.aborted) return;
|
||||
if (req === null || req[kAborted]) return;
|
||||
|
||||
req = websocket._req = null;
|
||||
websocket._readyState = WebSocket.CLOSING;
|
||||
websocket.emit('error', err);
|
||||
websocket.emitClose();
|
||||
emitErrorAndClose(websocket, err);
|
||||
});
|
||||
|
||||
req.on('response', (res) => {
|
||||
@@ -597,7 +871,15 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
|
||||
req.abort();
|
||||
|
||||
const addr = new URL(location, address);
|
||||
let addr;
|
||||
|
||||
try {
|
||||
addr = new URL(location, address);
|
||||
} catch (e) {
|
||||
const err = new SyntaxError(`Invalid URL: ${location}`);
|
||||
emitErrorAndClose(websocket, err);
|
||||
return;
|
||||
}
|
||||
|
||||
initAsClient(websocket, addr, protocols, options);
|
||||
} else if (!websocket.emit('unexpected-response', req, res)) {
|
||||
@@ -613,13 +895,18 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
websocket.emit('upgrade', res);
|
||||
|
||||
//
|
||||
// The user may have closed the connection from a listener of the `upgrade`
|
||||
// event.
|
||||
// The user may have closed the connection from a listener of the
|
||||
// `'upgrade'` event.
|
||||
//
|
||||
if (websocket.readyState !== WebSocket.CONNECTING) return;
|
||||
|
||||
req = websocket._req = null;
|
||||
|
||||
if (res.headers.upgrade.toLowerCase() !== 'websocket') {
|
||||
abortHandshake(websocket, socket, 'Invalid Upgrade header');
|
||||
return;
|
||||
}
|
||||
|
||||
const digest = createHash('sha1')
|
||||
.update(key + GUID)
|
||||
.digest('base64');
|
||||
@@ -630,15 +917,16 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
}
|
||||
|
||||
const serverProt = res.headers['sec-websocket-protocol'];
|
||||
const protList = (protocols || '').split(/, */);
|
||||
let protError;
|
||||
|
||||
if (!protocols && serverProt) {
|
||||
protError = 'Server sent a subprotocol but none was requested';
|
||||
} else if (protocols && !serverProt) {
|
||||
if (serverProt !== undefined) {
|
||||
if (!protocolSet.size) {
|
||||
protError = 'Server sent a subprotocol but none was requested';
|
||||
} else if (!protocolSet.has(serverProt)) {
|
||||
protError = 'Server sent an invalid subprotocol';
|
||||
}
|
||||
} else if (protocolSet.size) {
|
||||
protError = 'Server sent no subprotocol';
|
||||
} else if (serverProt && !protList.includes(serverProt)) {
|
||||
protError = 'Server sent an invalid subprotocol';
|
||||
}
|
||||
|
||||
if (protError) {
|
||||
@@ -648,27 +936,71 @@ function initAsClient(websocket, address, protocols, options) {
|
||||
|
||||
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 !== 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, {
|
||||
generateMask: opts.generateMask,
|
||||
maxPayload: opts.maxPayload,
|
||||
skipUTF8Validation: opts.skipUTF8Validation
|
||||
});
|
||||
});
|
||||
|
||||
req.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the `'error'` and `'close'` events.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -704,8 +1036,8 @@ 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
|
||||
*/
|
||||
@@ -716,6 +1048,7 @@ function abortHandshake(websocket, stream, message) {
|
||||
Error.captureStackTrace(err, abortHandshake);
|
||||
|
||||
if (stream.setHeader) {
|
||||
stream[kAborted] = true;
|
||||
stream.abort();
|
||||
|
||||
if (stream.socket && !stream.socket.destroyed) {
|
||||
@@ -727,8 +1060,7 @@ function abortHandshake(websocket, stream, message) {
|
||||
stream.socket.destroy();
|
||||
}
|
||||
|
||||
stream.once('abort', websocket.emitClose.bind(websocket));
|
||||
websocket.emit('error', err);
|
||||
process.nextTick(emitErrorAndClose, websocket, err);
|
||||
} else {
|
||||
stream.destroy(err);
|
||||
stream.once('error', websocket.emit.bind(websocket, 'error'));
|
||||
@@ -772,19 +1104,21 @@ function sendAfterClose(websocket, data, cb) {
|
||||
* The listener of the `Receiver` `'conclude'` event.
|
||||
*
|
||||
* @param {Number} code The status code
|
||||
* @param {String} reason The reason for closing
|
||||
* @param {Buffer} reason The reason for closing
|
||||
* @private
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@@ -795,7 +1129,9 @@ function receiverOnConclude(code, reason) {
|
||||
* @private
|
||||
*/
|
||||
function receiverOnDrain() {
|
||||
this[kWebSocket]._socket.resume();
|
||||
const websocket = this[kWebSocket];
|
||||
|
||||
if (!websocket.isPaused) websocket._socket.resume();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -807,10 +1143,18 @@ function receiverOnDrain() {
|
||||
function receiverOnError(err) {
|
||||
const websocket = this[kWebSocket];
|
||||
|
||||
websocket._socket.removeListener('data', socketOnData);
|
||||
websocket._socket.resume();
|
||||
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.close(err[kStatusCode]);
|
||||
websocket.emit('error', err);
|
||||
}
|
||||
|
||||
@@ -826,11 +1170,12 @@ function receiverOnFinish() {
|
||||
/**
|
||||
* The listener of the `Receiver` `'message'` event.
|
||||
*
|
||||
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
|
||||
* @param {Buffer|ArrayBuffer|Buffer[])} data The message
|
||||
* @param {Boolean} isBinary Specifies whether the message is binary or not
|
||||
* @private
|
||||
*/
|
||||
function receiverOnMessage(data) {
|
||||
this[kWebSocket].emit('message', data);
|
||||
function receiverOnMessage(data, isBinary) {
|
||||
this[kWebSocket].emit('message', data, isBinary);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -856,6 +1201,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.
|
||||
*
|
||||
@@ -865,10 +1220,13 @@ function socketOnClose() {
|
||||
const websocket = this[kWebSocket];
|
||||
|
||||
this.removeListener('close', socketOnClose);
|
||||
this.removeListener('data', socketOnData);
|
||||
this.removeListener('end', socketOnEnd);
|
||||
|
||||
websocket._readyState = WebSocket.CLOSING;
|
||||
|
||||
let chunk;
|
||||
|
||||
//
|
||||
// The close frame might not have been received or the `'end'` event emitted,
|
||||
// for example, if the socket was destroyed due to an error. Ensure that the
|
||||
@@ -876,13 +1234,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);
|
||||
|
Reference in New Issue
Block a user