JavaScript 65.5%
Python 17.8%
CSS 13%
HTML 3.7%
1/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex|Readable$", "caughtErrors": "none" }] */23'use strict';45const EventEmitter = require('events');6const https = require('https');7const http = require('http');8const net = require('net');9const tls = require('tls');10const { randomBytes, createHash } = require('crypto');11const { Duplex, Readable } = require('stream');12const { URL } = require('url');1314const PerMessageDeflate = require('./permessage-deflate');15const Receiver = require('./receiver');16const Sender = require('./sender');17const { isBlob } = require('./validation');1819const {20 BINARY_TYPES,21 CLOSE_TIMEOUT,22 EMPTY_BUFFER,23 GUID,24 kForOnEventAttribute,25 kListener,26 kStatusCode,27 kWebSocket,28 NOOP29} = require('./constants');30const {31 EventTarget: { addEventListener, removeEventListener }32} = require('./event-target');33const { format, parse } = require('./extension');34const { toBuffer } = require('./buffer-util');3536const kAborted = Symbol('kAborted');37const protocolVersions = [8, 13];38const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];39const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;4041/**42 * Class representing a WebSocket.43 *44 * @extends EventEmitter45 */46class WebSocket extends EventEmitter {47 /**48 * Create a new `WebSocket`.49 *50 * @param {(String|URL)} address The URL to which to connect51 * @param {(String|String[])} [protocols] The subprotocols52 * @param {Object} [options] Connection options53 */54 constructor(address, protocols, options) {55 super();5657 this._binaryType = BINARY_TYPES[0];58 this._closeCode = 1006;59 this._closeFrameReceived = false;60 this._closeFrameSent = false;61 this._closeMessage = EMPTY_BUFFER;62 this._closeTimer = null;63 this._errorEmitted = false;64 this._extensions = {};65 this._paused = false;66 this._protocol = '';67 this._readyState = WebSocket.CONNECTING;68 this._receiver = null;69 this._sender = null;70 this._socket = null;7172 if (address !== null) {73 this._bufferedAmount = 0;74 this._isServer = false;75 this._redirects = 0;7677 if (protocols === undefined) {78 protocols = [];79 } else if (!Array.isArray(protocols)) {80 if (typeof protocols === 'object' && protocols !== null) {81 options = protocols;82 protocols = [];83 } else {84 protocols = [protocols];85 }86 }8788 initAsClient(this, address, protocols, options);89 } else {90 this._autoPong = options.autoPong;91 this._closeTimeout = options.closeTimeout;92 this._isServer = true;93 }94 }9596 /**97 * For historical reasons, the custom "nodebuffer" type is used by the default98 * instead of "blob".99 *100 * @type {String}101 */102 get binaryType() {103 return this._binaryType;104 }105106 set binaryType(type) {107 if (!BINARY_TYPES.includes(type)) return;108109 this._binaryType = type;110111 //112 // Allow to change `binaryType` on the fly.113 //114 if (this._receiver) this._receiver._binaryType = type;115 }116117 /**118 * @type {Number}119 */120 get bufferedAmount() {121 if (!this._socket) return this._bufferedAmount;122123 return this._socket._writableState.length + this._sender._bufferedBytes;124 }125126 /**127 * @type {String}128 */129 get extensions() {130 return Object.keys(this._extensions).join();131 }132133 /**134 * @type {Boolean}135 */136 get isPaused() {137 return this._paused;138 }139140 /**141 * @type {Function}142 */143 /* istanbul ignore next */144 get onclose() {145 return null;146 }147148 /**149 * @type {Function}150 */151 /* istanbul ignore next */152 get onerror() {153 return null;154 }155156 /**157 * @type {Function}158 */159 /* istanbul ignore next */160 get onopen() {161 return null;162 }163164 /**165 * @type {Function}166 */167 /* istanbul ignore next */168 get onmessage() {169 return null;170 }171172 /**173 * @type {String}174 */175 get protocol() {176 return this._protocol;177 }178179 /**180 * @type {Number}181 */182 get readyState() {183 return this._readyState;184 }185186 /**187 * @type {String}188 */189 get url() {190 return this._url;191 }192193 /**194 * Set up the socket and the internal resources.195 *196 * @param {Duplex} socket The network socket between the server and client197 * @param {Buffer} head The first packet of the upgraded stream198 * @param {Object} options Options object199 * @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether200 * any of the `'message'`, `'ping'`, and `'pong'` events can be emitted201 * multiple times in the same tick202 * @param {Function} [options.generateMask] The function used to generate the203 * masking key204 * @param {Number} [options.maxBufferedChunks=0] The maximum number of205 * buffered data chunks206 * @param {Number} [options.maxFragments=0] The maximum number of message207 * fragments208 * @param {Number} [options.maxPayload=0] The maximum allowed message size209 * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or210 * not to skip UTF-8 validation for text and close messages211 * @private212 */213 setSocket(socket, head, options) {214 const receiver = new Receiver({215 allowSynchronousEvents: options.allowSynchronousEvents,216 binaryType: this.binaryType,217 extensions: this._extensions,218 isServer: this._isServer,219 maxBufferedChunks: options.maxBufferedChunks,220 maxFragments: options.maxFragments,221 maxPayload: options.maxPayload,222 skipUTF8Validation: options.skipUTF8Validation223 });224225 const sender = new Sender(socket, this._extensions, options.generateMask);226227 this._receiver = receiver;228 this._sender = sender;229 this._socket = socket;230231 receiver[kWebSocket] = this;232 sender[kWebSocket] = this;233 socket[kWebSocket] = this;234235 receiver.on('conclude', receiverOnConclude);236 receiver.on('drain', receiverOnDrain);237 receiver.on('error', receiverOnError);238 receiver.on('message', receiverOnMessage);239 receiver.on('ping', receiverOnPing);240 receiver.on('pong', receiverOnPong);241242 sender.onerror = senderOnError;243244 //245 // These methods may not be available if `socket` is just a `Duplex`.246 //247 if (socket.setTimeout) socket.setTimeout(0);248 if (socket.setNoDelay) socket.setNoDelay();249250 if (head.length > 0) socket.unshift(head);251252 socket.on('close', socketOnClose);253 socket.on('data', socketOnData);254 socket.on('end', socketOnEnd);255 socket.on('error', socketOnError);256257 this._readyState = WebSocket.OPEN;258 this.emit('open');259 }260261 /**262 * Emit the `'close'` event.263 *264 * @private265 */266 emitClose() {267 if (!this._socket) {268 this._readyState = WebSocket.CLOSED;269 this.emit('close', this._closeCode, this._closeMessage);270 return;271 }272273 if (this._extensions[PerMessageDeflate.extensionName]) {274 this._extensions[PerMessageDeflate.extensionName].cleanup();275 }276277 this._receiver.removeAllListeners();278 this._readyState = WebSocket.CLOSED;279 this.emit('close', this._closeCode, this._closeMessage);280 }281282 /**283 * Start a closing handshake.284 *285 * +----------+ +-----------+ +----------+286 * - - -|ws.close()|-->|close frame|-->|ws.close()|- - -287 * | +----------+ +-----------+ +----------+ |288 * +----------+ +-----------+ |289 * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING290 * +----------+ +-----------+ |291 * | | | +---+ |292 * +------------------------+-->|fin| - - - -293 * | +---+ | +---+294 * - - - - -|fin|<---------------------+295 * +---+296 *297 * @param {Number} [code] Status code explaining why the connection is closing298 * @param {(String|Buffer)} [data] The reason why the connection is299 * closing300 * @public301 */302 close(code, data) {303 if (this.readyState === WebSocket.CLOSED) return;304 if (this.readyState === WebSocket.CONNECTING) {305 const msg = 'WebSocket was closed before the connection was established';306 abortHandshake(this, this._req, msg);307 return;308 }309310 if (this.readyState === WebSocket.CLOSING) {311 if (312 this._closeFrameSent &&313 (this._closeFrameReceived || this._receiver._writableState.errorEmitted)314 ) {315 this._socket.end();316 }317318 return;319 }320321 this._readyState = WebSocket.CLOSING;322 this._sender.close(code, data, !this._isServer, (err) => {323 //324 // This error is handled by the `'error'` listener on the socket. We only325 // want to know if the close frame has been sent here.326 //327 if (err) return;328329 this._closeFrameSent = true;330331 if (332 this._closeFrameReceived ||333 this._receiver._writableState.errorEmitted334 ) {335 this._socket.end();336 }337 });338339 setCloseTimer(this);340 }341342 /**343 * Pause the socket.344 *345 * @public346 */347 pause() {348 if (349 this.readyState === WebSocket.CONNECTING ||350 this.readyState === WebSocket.CLOSED351 ) {352 return;353 }354355 this._paused = true;356 this._socket.pause();357 }358359 /**360 * Send a ping.361 *362 * @param {*} [data] The data to send363 * @param {Boolean} [mask] Indicates whether or not to mask `data`364 * @param {Function} [cb] Callback which is executed when the ping is sent365 * @public366 */367 ping(data, mask, cb) {368 if (this.readyState === WebSocket.CONNECTING) {369 throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');370 }371372 if (typeof data === 'function') {373 cb = data;374 data = mask = undefined;375 } else if (typeof mask === 'function') {376 cb = mask;377 mask = undefined;378 }379380 if (typeof data === 'number') data = data.toString();381382 if (this.readyState !== WebSocket.OPEN) {383 sendAfterClose(this, data, cb);384 return;385 }386387 if (mask === undefined) mask = !this._isServer;388 this._sender.ping(data || EMPTY_BUFFER, mask, cb);389 }390391 /**392 * Send a pong.393 *394 * @param {*} [data] The data to send395 * @param {Boolean} [mask] Indicates whether or not to mask `data`396 * @param {Function} [cb] Callback which is executed when the pong is sent397 * @public398 */399 pong(data, mask, cb) {400 if (this.readyState === WebSocket.CONNECTING) {401 throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');402 }403404 if (typeof data === 'function') {405 cb = data;406 data = mask = undefined;407 } else if (typeof mask === 'function') {408 cb = mask;409 mask = undefined;410 }411412 if (typeof data === 'number') data = data.toString();413414 if (this.readyState !== WebSocket.OPEN) {415 sendAfterClose(this, data, cb);416 return;417 }418419 if (mask === undefined) mask = !this._isServer;420 this._sender.pong(data || EMPTY_BUFFER, mask, cb);421 }422423 /**424 * Resume the socket.425 *426 * @public427 */428 resume() {429 if (430 this.readyState === WebSocket.CONNECTING ||431 this.readyState === WebSocket.CLOSED432 ) {433 return;434 }435436 this._paused = false;437 if (!this._receiver._writableState.needDrain) this._socket.resume();438 }439440 /**441 * Send a data message.442 *443 * @param {*} data The message to send444 * @param {Object} [options] Options object445 * @param {Boolean} [options.binary] Specifies whether `data` is binary or446 * text447 * @param {Boolean} [options.compress] Specifies whether or not to compress448 * `data`449 * @param {Boolean} [options.fin=true] Specifies whether the fragment is the450 * last one451 * @param {Boolean} [options.mask] Specifies whether or not to mask `data`452 * @param {Function} [cb] Callback which is executed when data is written out453 * @public454 */455 send(data, options, cb) {456 if (this.readyState === WebSocket.CONNECTING) {457 throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');458 }459460 if (typeof options === 'function') {461 cb = options;462 options = {};463 }464465 if (typeof data === 'number') data = data.toString();466467 if (this.readyState !== WebSocket.OPEN) {468 sendAfterClose(this, data, cb);469 return;470 }471472 const opts = {473 binary: typeof data !== 'string',474 mask: !this._isServer,475 compress: true,476 fin: true,477 ...options478 };479480 if (!this._extensions[PerMessageDeflate.extensionName]) {481 opts.compress = false;482 }483484 this._sender.send(data || EMPTY_BUFFER, opts, cb);485 }486487 /**488 * Forcibly close the connection.489 *490 * @public491 */492 terminate() {493 if (this.readyState === WebSocket.CLOSED) return;494 if (this.readyState === WebSocket.CONNECTING) {495 const msg = 'WebSocket was closed before the connection was established';496 abortHandshake(this, this._req, msg);497 return;498 }499500 if (this._socket) {501 this._readyState = WebSocket.CLOSING;502 this._socket.destroy();503 }504 }505}506507/**508 * @constant {Number} CONNECTING509 * @memberof WebSocket510 */511Object.defineProperty(WebSocket, 'CONNECTING', {512 enumerable: true,513 value: readyStates.indexOf('CONNECTING')514});515516/**517 * @constant {Number} CONNECTING518 * @memberof WebSocket.prototype519 */520Object.defineProperty(WebSocket.prototype, 'CONNECTING', {521 enumerable: true,522 value: readyStates.indexOf('CONNECTING')523});524525/**526 * @constant {Number} OPEN527 * @memberof WebSocket528 */529Object.defineProperty(WebSocket, 'OPEN', {530 enumerable: true,531 value: readyStates.indexOf('OPEN')532});533534/**535 * @constant {Number} OPEN536 * @memberof WebSocket.prototype537 */538Object.defineProperty(WebSocket.prototype, 'OPEN', {539 enumerable: true,540 value: readyStates.indexOf('OPEN')541});542543/**544 * @constant {Number} CLOSING545 * @memberof WebSocket546 */547Object.defineProperty(WebSocket, 'CLOSING', {548 enumerable: true,549 value: readyStates.indexOf('CLOSING')550});551552/**553 * @constant {Number} CLOSING554 * @memberof WebSocket.prototype555 */556Object.defineProperty(WebSocket.prototype, 'CLOSING', {557 enumerable: true,558 value: readyStates.indexOf('CLOSING')559});560561/**562 * @constant {Number} CLOSED563 * @memberof WebSocket564 */565Object.defineProperty(WebSocket, 'CLOSED', {566 enumerable: true,567 value: readyStates.indexOf('CLOSED')568});569570/**571 * @constant {Number} CLOSED572 * @memberof WebSocket.prototype573 */574Object.defineProperty(WebSocket.prototype, 'CLOSED', {575 enumerable: true,576 value: readyStates.indexOf('CLOSED')577});578579[580 'binaryType',581 'bufferedAmount',582 'extensions',583 'isPaused',584 'protocol',585 'readyState',586 'url'587].forEach((property) => {588 Object.defineProperty(WebSocket.prototype, property, { enumerable: true });589});590591//592// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.593// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface594//595['open', 'error', 'close', 'message'].forEach((method) => {596 Object.defineProperty(WebSocket.prototype, `on${method}`, {597 enumerable: true,598 get() {599 for (const listener of this.listeners(method)) {600 if (listener[kForOnEventAttribute]) return listener[kListener];601 }602603 return null;604 },605 set(handler) {606 for (const listener of this.listeners(method)) {607 if (listener[kForOnEventAttribute]) {608 this.removeListener(method, listener);609 break;610 }611 }612613 if (typeof handler !== 'function') return;614615 this.addEventListener(method, handler, {616 [kForOnEventAttribute]: true617 });618 }619 });620});621622WebSocket.prototype.addEventListener = addEventListener;623WebSocket.prototype.removeEventListener = removeEventListener;624625module.exports = WebSocket;626627/**628 * Initialize a WebSocket client.629 *630 * @param {WebSocket} websocket The client to initialize631 * @param {(String|URL)} address The URL to which to connect632 * @param {Array} protocols The subprotocols633 * @param {Object} [options] Connection options634 * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether any635 * of the `'message'`, `'ping'`, and `'pong'` events can be emitted multiple636 * times in the same tick637 * @param {Boolean} [options.autoPong=true] Specifies whether or not to638 * automatically send a pong in response to a ping639 * @param {Number} [options.closeTimeout=30000] Duration in milliseconds to wait640 * for the closing handshake to finish after `websocket.close()` is called641 * @param {Function} [options.finishRequest] A function which can be used to642 * customize the headers of each http request before it is sent643 * @param {Boolean} [options.followRedirects=false] Whether or not to follow644 * redirects645 * @param {Function} [options.generateMask] The function used to generate the646 * masking key647 * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the648 * handshake request649 * @param {Number} [options.maxBufferedChunks=262144] The maximum number of650 * buffered data chunks651 * @param {Number} [options.maxFragments=16384] The maximum number of message652 * fragments653 * @param {Number} [options.maxPayload=104857600] The maximum allowed message654 * size655 * @param {Number} [options.maxRedirects=10] The maximum number of redirects656 * allowed657 * @param {String} [options.origin] Value of the `Origin` or658 * `Sec-WebSocket-Origin` header659 * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable660 * permessage-deflate661 * @param {Number} [options.protocolVersion=13] Value of the662 * `Sec-WebSocket-Version` header663 * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or664 * not to skip UTF-8 validation for text and close messages665 * @private666 */667function initAsClient(websocket, address, protocols, options) {668 const opts = {669 allowSynchronousEvents: true,670 autoPong: true,671 closeTimeout: CLOSE_TIMEOUT,672 protocolVersion: protocolVersions[1],673 maxBufferedChunks: 256 * 1024,674 maxFragments: 16 * 1024,675 maxPayload: 100 * 1024 * 1024,676 skipUTF8Validation: false,677 perMessageDeflate: true,678 followRedirects: false,679 maxRedirects: 10,680 ...options,681 socketPath: undefined,682 hostname: undefined,683 protocol: undefined,684 timeout: undefined,685 method: 'GET',686 host: undefined,687 path: undefined,688 port: undefined689 };690691 websocket._autoPong = opts.autoPong;692 websocket._closeTimeout = opts.closeTimeout;693694 if (!protocolVersions.includes(opts.protocolVersion)) {695 throw new RangeError(696 `Unsupported protocol version: ${opts.protocolVersion} ` +697 `(supported versions: ${protocolVersions.join(', ')})`698 );699 }700701 let parsedUrl;702703 if (address instanceof URL) {704 parsedUrl = address;705 } else {706 try {707 parsedUrl = new URL(address);708 } catch {709 throw new SyntaxError(`Invalid URL: ${address}`);710 }711 }712713 if (parsedUrl.protocol === 'http:') {714 parsedUrl.protocol = 'ws:';715 } else if (parsedUrl.protocol === 'https:') {716 parsedUrl.protocol = 'wss:';717 }718719 websocket._url = parsedUrl.href;720721 const isSecure = parsedUrl.protocol === 'wss:';722 const isIpcUrl = parsedUrl.protocol === 'ws+unix:';723 let invalidUrlMessage;724725 if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {726 invalidUrlMessage =727 'The URL\'s protocol must be one of "ws:", "wss:", ' +728 '"http:", "https:", or "ws+unix:"';729 } else if (isIpcUrl && !parsedUrl.pathname) {730 invalidUrlMessage = "The URL's pathname is empty";731 } else if (parsedUrl.hash) {732 invalidUrlMessage = 'The URL contains a fragment identifier';733 }734735 if (invalidUrlMessage) {736 const err = new SyntaxError(invalidUrlMessage);737738 if (websocket._redirects === 0) {739 throw err;740 } else {741 emitErrorAndClose(websocket, err);742 return;743 }744 }745746 const defaultPort = isSecure ? 443 : 80;747 const key = randomBytes(16).toString('base64');748 const request = isSecure ? https.request : http.request;749 const protocolSet = new Set();750 let perMessageDeflate;751752 opts.createConnection =753 opts.createConnection || (isSecure ? tlsConnect : netConnect);754 opts.defaultPort = opts.defaultPort || defaultPort;755 opts.port = parsedUrl.port || defaultPort;756 opts.host = parsedUrl.hostname.startsWith('[')757 ? parsedUrl.hostname.slice(1, -1)758 : parsedUrl.hostname;759 opts.headers = {760 ...opts.headers,761 'Sec-WebSocket-Version': opts.protocolVersion,762 'Sec-WebSocket-Key': key,763 Connection: 'Upgrade',764 Upgrade: 'websocket'765 };766 opts.path = parsedUrl.pathname + parsedUrl.search;767 opts.timeout = opts.handshakeTimeout;768769 if (opts.perMessageDeflate) {770 perMessageDeflate = new PerMessageDeflate({771 ...opts.perMessageDeflate,772 isServer: false,773 maxPayload: opts.maxPayload774 });775 opts.headers['Sec-WebSocket-Extensions'] = format({776 [PerMessageDeflate.extensionName]: perMessageDeflate.offer()777 });778 }779 if (protocols.length) {780 for (const protocol of protocols) {781 if (782 typeof protocol !== 'string' ||783 !subprotocolRegex.test(protocol) ||784 protocolSet.has(protocol)785 ) {786 throw new SyntaxError(787 'An invalid or duplicated subprotocol was specified'788 );789 }790791 protocolSet.add(protocol);792 }793794 opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');795 }796 if (opts.origin) {797 if (opts.protocolVersion < 13) {798 opts.headers['Sec-WebSocket-Origin'] = opts.origin;799 } else {800 opts.headers.Origin = opts.origin;801 }802 }803 if (parsedUrl.username || parsedUrl.password) {804 opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;805 }806807 if (isIpcUrl) {808 const parts = opts.path.split(':');809810 opts.socketPath = parts[0];811 opts.path = parts[1];812 }813814 let req;815816 if (opts.followRedirects) {817 if (websocket._redirects === 0) {818 websocket._originalIpc = isIpcUrl;819 websocket._originalSecure = isSecure;820 websocket._originalHostOrSocketPath = isIpcUrl821 ? opts.socketPath822 : parsedUrl.host;823824 const headers = options && options.headers;825826 //827 // Shallow copy the user provided options so that headers can be changed828 // without mutating the original object.829 //830 options = { ...options, headers: {} };831832 if (headers) {833 for (const [key, value] of Object.entries(headers)) {834 options.headers[key.toLowerCase()] = value;835 }836 }837 } else if (websocket.listenerCount('redirect') === 0) {838 const isSameHost = isIpcUrl839 ? websocket._originalIpc840 ? opts.socketPath === websocket._originalHostOrSocketPath841 : false842 : websocket._originalIpc843 ? false844 : parsedUrl.host === websocket._originalHostOrSocketPath;845846 if (!isSameHost || (websocket._originalSecure && !isSecure)) {847 //848 // Match curl 7.77.0 behavior and drop the following headers. These849 // headers are also dropped when following a redirect to a subdomain.850 //851 delete opts.headers.authorization;852 delete opts.headers.cookie;853854 if (!isSameHost) delete opts.headers.host;855856 opts.auth = undefined;857 }858 }859860 //861 // Match curl 7.77.0 behavior and make the first `Authorization` header win.862 // If the `Authorization` header is set, then there is nothing to do as it863 // will take precedence.864 //865 if (opts.auth && !options.headers.authorization) {866 options.headers.authorization =867 'Basic ' + Buffer.from(opts.auth).toString('base64');868 }869870 req = websocket._req = request(opts);871872 if (websocket._redirects) {873 //874 // Unlike what is done for the `'upgrade'` event, no early exit is875 // triggered here if the user calls `websocket.close()` or876 // `websocket.terminate()` from a listener of the `'redirect'` event. This877 // is because the user can also call `request.destroy()` with an error878 // before calling `websocket.close()` or `websocket.terminate()` and this879 // would result in an error being emitted on the `request` object with no880 // `'error'` event listeners attached.881 //882 websocket.emit('redirect', websocket.url, req);883 }884 } else {885 req = websocket._req = request(opts);886 }887888 if (opts.timeout) {889 req.on('timeout', () => {890 abortHandshake(websocket, req, 'Opening handshake has timed out');891 });892 }893894 req.on('error', (err) => {895 if (req === null || req[kAborted]) return;896897 req = websocket._req = null;898 emitErrorAndClose(websocket, err);899 });900901 req.on('response', (res) => {902 const location = res.headers.location;903 const statusCode = res.statusCode;904905 if (906 location &&907 opts.followRedirects &&908 statusCode >= 300 &&909 statusCode < 400910 ) {911 if (++websocket._redirects > opts.maxRedirects) {912 abortHandshake(websocket, req, 'Maximum redirects exceeded');913 return;914 }915916 req.abort();917918 let addr;919920 try {921 addr = new URL(location, address);922 } catch (e) {923 const err = new SyntaxError(`Invalid URL: ${location}`);924 emitErrorAndClose(websocket, err);925 return;926 }927928 initAsClient(websocket, addr, protocols, options);929 } else if (!websocket.emit('unexpected-response', req, res)) {930 abortHandshake(931 websocket,932 req,933 `Unexpected server response: ${res.statusCode}`934 );935 }936 });937938 req.on('upgrade', (res, socket, head) => {939 websocket.emit('upgrade', res);940941 //942 // The user may have closed the connection from a listener of the943 // `'upgrade'` event.944 //945 if (websocket.readyState !== WebSocket.CONNECTING) return;946947 req = websocket._req = null;948949 const upgrade = res.headers.upgrade;950951 if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {952 abortHandshake(websocket, socket, 'Invalid Upgrade header');953 return;954 }955956 const digest = createHash('sha1')957 .update(key + GUID)958 .digest('base64');959960 if (res.headers['sec-websocket-accept'] !== digest) {961 abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');962 return;963 }964965 const serverProt = res.headers['sec-websocket-protocol'];966 let protError;967968 if (serverProt !== undefined) {969 if (!protocolSet.size) {970 protError = 'Server sent a subprotocol but none was requested';971 } else if (!protocolSet.has(serverProt)) {972 protError = 'Server sent an invalid subprotocol';973 }974 } else if (protocolSet.size) {975 protError = 'Server sent no subprotocol';976 }977978 if (protError) {979 abortHandshake(websocket, socket, protError);980 return;981 }982983 if (serverProt) websocket._protocol = serverProt;984985 const secWebSocketExtensions = res.headers['sec-websocket-extensions'];986987 if (secWebSocketExtensions !== undefined) {988 if (!perMessageDeflate) {989 const message =990 'Server sent a Sec-WebSocket-Extensions header but no extension ' +991 'was requested';992 abortHandshake(websocket, socket, message);993 return;994 }995996 let extensions;997998 try {999 extensions = parse(secWebSocketExtensions);1000 } catch (err) {1001 const message = 'Invalid Sec-WebSocket-Extensions header';1002 abortHandshake(websocket, socket, message);1003 return;1004 }10051006 const extensionNames = Object.keys(extensions);10071008 if (1009 extensionNames.length !== 1 ||1010 extensionNames[0] !== PerMessageDeflate.extensionName1011 ) {1012 const message = 'Server indicated an extension that was not requested';1013 abortHandshake(websocket, socket, message);1014 return;1015 }10161017 try {1018 perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);1019 } catch (err) {1020 const message = 'Invalid Sec-WebSocket-Extensions header';1021 abortHandshake(websocket, socket, message);1022 return;1023 }10241025 websocket._extensions[PerMessageDeflate.extensionName] =1026 perMessageDeflate;1027 }10281029 websocket.setSocket(socket, head, {1030 allowSynchronousEvents: opts.allowSynchronousEvents,1031 generateMask: opts.generateMask,1032 maxBufferedChunks: opts.maxBufferedChunks,1033 maxFragments: opts.maxFragments,1034 maxPayload: opts.maxPayload,1035 skipUTF8Validation: opts.skipUTF8Validation1036 });1037 });10381039 if (opts.finishRequest) {1040 opts.finishRequest(req, websocket);1041 } else {1042 req.end();1043 }1044}10451046/**1047 * Emit the `'error'` and `'close'` events.1048 *1049 * @param {WebSocket} websocket The WebSocket instance1050 * @param {Error} The error to emit1051 * @private1052 */1053function emitErrorAndClose(websocket, err) {1054 websocket._readyState = WebSocket.CLOSING;1055 //1056 // The following assignment is practically useless and is done only for1057 // consistency.1058 //1059 websocket._errorEmitted = true;1060 websocket.emit('error', err);1061 websocket.emitClose();1062}10631064/**1065 * Create a `net.Socket` and initiate a connection.1066 *1067 * @param {Object} options Connection options1068 * @return {net.Socket} The newly created socket used to start the connection1069 * @private1070 */1071function netConnect(options) {1072 options.path = options.socketPath;1073 return net.connect(options);1074}10751076/**1077 * Create a `tls.TLSSocket` and initiate a connection.1078 *1079 * @param {Object} options Connection options1080 * @return {tls.TLSSocket} The newly created socket used to start the connection1081 * @private1082 */1083function tlsConnect(options) {1084 options.path = undefined;10851086 if (!options.servername && options.servername !== '') {1087 options.servername = net.isIP(options.host) ? '' : options.host;1088 }10891090 return tls.connect(options);1091}10921093/**1094 * Abort the handshake and emit an error.1095 *1096 * @param {WebSocket} websocket The WebSocket instance1097 * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to1098 * abort or the socket to destroy1099 * @param {String} message The error message1100 * @private1101 */1102function abortHandshake(websocket, stream, message) {1103 websocket._readyState = WebSocket.CLOSING;11041105 const err = new Error(message);1106 Error.captureStackTrace(err, abortHandshake);11071108 if (stream.setHeader) {1109 stream[kAborted] = true;1110 stream.abort();11111112 if (stream.socket && !stream.socket.destroyed) {1113 //1114 // On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if1115 // called after the request completed. See1116 // https://github.com/websockets/ws/issues/1869.1117 //1118 stream.socket.destroy();1119 }11201121 process.nextTick(emitErrorAndClose, websocket, err);1122 } else {1123 stream.destroy(err);1124 stream.once('error', websocket.emit.bind(websocket, 'error'));1125 stream.once('close', websocket.emitClose.bind(websocket));1126 }1127}11281129/**1130 * Handle cases where the `ping()`, `pong()`, or `send()` methods are called1131 * when the `readyState` attribute is `CLOSING` or `CLOSED`.1132 *1133 * @param {WebSocket} websocket The WebSocket instance1134 * @param {*} [data] The data to send1135 * @param {Function} [cb] Callback1136 * @private1137 */1138function sendAfterClose(websocket, data, cb) {1139 if (data) {1140 const length = isBlob(data) ? data.size : toBuffer(data).length;11411142 //1143 // The `_bufferedAmount` property is used only when the peer is a client and1144 // the opening handshake fails. Under these circumstances, in fact, the1145 // `setSocket()` method is not called, so the `_socket` and `_sender`1146 // properties are set to `null`.1147 //1148 if (websocket._socket) websocket._sender._bufferedBytes += length;1149 else websocket._bufferedAmount += length;1150 }11511152 if (cb) {1153 const err = new Error(1154 `WebSocket is not open: readyState ${websocket.readyState} ` +1155 `(${readyStates[websocket.readyState]})`1156 );1157 process.nextTick(cb, err);1158 }1159}11601161/**1162 * The listener of the `Receiver` `'conclude'` event.1163 *1164 * @param {Number} code The status code1165 * @param {Buffer} reason The reason for closing1166 * @private1167 */1168function receiverOnConclude(code, reason) {1169 const websocket = this[kWebSocket];11701171 websocket._closeFrameReceived = true;1172 websocket._closeMessage = reason;1173 websocket._closeCode = code;11741175 if (websocket._socket[kWebSocket] === undefined) return;11761177 websocket._socket.removeListener('data', socketOnData);1178 process.nextTick(resume, websocket._socket);11791180 if (code === 1005) websocket.close();1181 else websocket.close(code, reason);1182}11831184/**1185 * The listener of the `Receiver` `'drain'` event.1186 *1187 * @private1188 */1189function receiverOnDrain() {1190 const websocket = this[kWebSocket];11911192 if (!websocket.isPaused) websocket._socket.resume();1193}11941195/**1196 * The listener of the `Receiver` `'error'` event.1197 *1198 * @param {(RangeError|Error)} err The emitted error1199 * @private1200 */1201function receiverOnError(err) {1202 const websocket = this[kWebSocket];12031204 if (websocket._socket[kWebSocket] !== undefined) {1205 websocket._socket.removeListener('data', socketOnData);12061207 //1208 // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See1209 // https://github.com/websockets/ws/issues/1940.1210 //1211 process.nextTick(resume, websocket._socket);12121213 websocket.close(err[kStatusCode]);1214 }12151216 if (!websocket._errorEmitted) {1217 websocket._errorEmitted = true;1218 websocket.emit('error', err);1219 }1220}12211222/**1223 * The listener of the `Receiver` `'finish'` event.1224 *1225 * @private1226 */1227function receiverOnFinish() {1228 this[kWebSocket].emitClose();1229}12301231/**1232 * The listener of the `Receiver` `'message'` event.1233 *1234 * @param {Buffer|ArrayBuffer|Buffer[])} data The message1235 * @param {Boolean} isBinary Specifies whether the message is binary or not1236 * @private1237 */1238function receiverOnMessage(data, isBinary) {1239 this[kWebSocket].emit('message', data, isBinary);1240}12411242/**1243 * The listener of the `Receiver` `'ping'` event.1244 *1245 * @param {Buffer} data The data included in the ping frame1246 * @private1247 */1248function receiverOnPing(data) {1249 const websocket = this[kWebSocket];12501251 if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP);1252 websocket.emit('ping', data);1253}12541255/**1256 * The listener of the `Receiver` `'pong'` event.1257 *1258 * @param {Buffer} data The data included in the pong frame1259 * @private1260 */1261function receiverOnPong(data) {1262 this[kWebSocket].emit('pong', data);1263}12641265/**1266 * Resume a readable stream1267 *1268 * @param {Readable} stream The readable stream1269 * @private1270 */1271function resume(stream) {1272 stream.resume();1273}12741275/**1276 * The `Sender` error event handler.1277 *1278 * @param {Error} The error1279 * @private1280 */1281function senderOnError(err) {1282 const websocket = this[kWebSocket];12831284 if (websocket.readyState === WebSocket.CLOSED) return;1285 if (websocket.readyState === WebSocket.OPEN) {1286 websocket._readyState = WebSocket.CLOSING;1287 setCloseTimer(websocket);1288 }12891290 //1291 // `socket.end()` is used instead of `socket.destroy()` to allow the other1292 // peer to finish sending queued data. There is no need to set a timer here1293 // because `CLOSING` means that it is already set or not needed.1294 //1295 this._socket.end();12961297 if (!websocket._errorEmitted) {1298 websocket._errorEmitted = true;1299 websocket.emit('error', err);1300 }1301}13021303/**1304 * Set a timer to destroy the underlying raw socket of a WebSocket.1305 *1306 * @param {WebSocket} websocket The WebSocket instance1307 * @private1308 */1309function setCloseTimer(websocket) {1310 websocket._closeTimer = setTimeout(1311 websocket._socket.destroy.bind(websocket._socket),1312 websocket._closeTimeout1313 );1314}13151316/**1317 * The listener of the socket `'close'` event.1318 *1319 * @private1320 */1321function socketOnClose() {1322 const websocket = this[kWebSocket];13231324 this.removeListener('close', socketOnClose);1325 this.removeListener('data', socketOnData);1326 this.removeListener('end', socketOnEnd);13271328 websocket._readyState = WebSocket.CLOSING;13291330 //1331 // The close frame might not have been received or the `'end'` event emitted,1332 // for example, if the socket was destroyed due to an error. Ensure that the1333 // `receiver` stream is closed after writing any remaining buffered data to1334 // it. If the readable side of the socket is in flowing mode then there is no1335 // buffered data as everything has been already written. If instead, the1336 // socket is paused, any possible buffered data will be read as a single1337 // chunk.1338 //1339 if (1340 !this._readableState.endEmitted &&1341 !websocket._closeFrameReceived &&1342 !websocket._receiver._writableState.errorEmitted &&1343 this._readableState.length !== 01344 ) {1345 const chunk = this.read(this._readableState.length);13461347 websocket._receiver.write(chunk);1348 }13491350 websocket._receiver.end();13511352 this[kWebSocket] = undefined;13531354 clearTimeout(websocket._closeTimer);13551356 if (1357 websocket._receiver._writableState.finished ||1358 websocket._receiver._writableState.errorEmitted1359 ) {1360 websocket.emitClose();1361 } else {1362 websocket._receiver.on('error', receiverOnFinish);1363 websocket._receiver.on('finish', receiverOnFinish);1364 }1365}13661367/**1368 * The listener of the socket `'data'` event.1369 *1370 * @param {Buffer} chunk A chunk of data1371 * @private1372 */1373function socketOnData(chunk) {1374 if (!this[kWebSocket]._receiver.write(chunk)) {1375 this.pause();1376 }1377}13781379/**1380 * The listener of the socket `'end'` event.1381 *1382 * @private1383 */1384function socketOnEnd() {1385 const websocket = this[kWebSocket];13861387 websocket._readyState = WebSocket.CLOSING;1388 websocket._receiver.end();1389 this.end();1390}13911392/**1393 * The listener of the socket `'error'` event.1394 *1395 * @private1396 */1397function socketOnError() {1398 const websocket = this[kWebSocket];13991400 this.removeListener('error', socketOnError);1401 this.on('error', NOOP);14021403 if (websocket) {1404 websocket._readyState = WebSocket.CLOSING;1405 this.destroy();1406 }1407}1408