strong-tie/inbound-calls
0
1/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex|Readable$", "caughtErrors": "none" }] */2 3'use strict';4 5const 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');13 14const PerMessageDeflate = require('./permessage-deflate');15const Receiver = require('./receiver');16const Sender = require('./sender');17const { isBlob } = require('./validation');18 19const {20 BINARY_TYPES,21 EMPTY_BUFFER,22 GUID,23 kForOnEventAttribute,24 kListener,25 kStatusCode,26 kWebSocket,27 NOOP28} = require('./constants');29const {30 EventTarget: { addEventListener, removeEventListener }31} = require('./event-target');32const { format, parse } = require('./extension');33const { toBuffer } = require('./buffer-util');34 35const closeTimeout = 30 * 1000;36const kAborted = Symbol('kAborted');37const protocolVersions = [8, 13];38const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];39const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;40 41/**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();56 57 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;71 72 if (address !== null) {73 this._bufferedAmount = 0;74 this._isServer = false;75 this._redirects = 0;76 77 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 }87 88 initAsClient(this, address, protocols, options);89 } else {90 this._autoPong = options.autoPong;91 this._isServer = true;92 }93 }94 95 /**96 * For historical reasons, the custom "nodebuffer" type is used by the default97 * instead of "blob".98 *99 * @type {String}100 */101 get binaryType() {102 return this._binaryType;103 }104 105 set binaryType(type) {106 if (!BINARY_TYPES.includes(type)) return;107 108 this._binaryType = type;109 110 //111 // Allow to change `binaryType` on the fly.112 //113 if (this._receiver) this._receiver._binaryType = type;114 }115 116 /**117 * @type {Number}118 */119 get bufferedAmount() {120 if (!this._socket) return this._bufferedAmount;121 122 return this._socket._writableState.length + this._sender._bufferedBytes;123 }124 125 /**126 * @type {String}127 */128 get extensions() {129 return Object.keys(this._extensions).join();130 }131 132 /**133 * @type {Boolean}134 */135 get isPaused() {136 return this._paused;137 }138 139 /**140 * @type {Function}141 */142 /* istanbul ignore next */143 get onclose() {144 return null;145 }146 147 /**148 * @type {Function}149 */150 /* istanbul ignore next */151 get onerror() {152 return null;153 }154 155 /**156 * @type {Function}157 */158 /* istanbul ignore next */159 get onopen() {160 return null;161 }162 163 /**164 * @type {Function}165 */166 /* istanbul ignore next */167 get onmessage() {168 return null;169 }170 171 /**172 * @type {String}173 */174 get protocol() {175 return this._protocol;176 }177 178 /**179 * @type {Number}180 */181 get readyState() {182 return this._readyState;183 }184 185 /**186 * @type {String}187 */188 get url() {189 return this._url;190 }191 192 /**193 * Set up the socket and the internal resources.194 *195 * @param {Duplex} socket The network socket between the server and client196 * @param {Buffer} head The first packet of the upgraded stream197 * @param {Object} options Options object198 * @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether199 * any of the `'message'`, `'ping'`, and `'pong'` events can be emitted200 * multiple times in the same tick201 * @param {Function} [options.generateMask] The function used to generate the202 * masking key203 * @param {Number} [options.maxPayload=0] The maximum allowed message size204 * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or205 * not to skip UTF-8 validation for text and close messages206 * @private207 */208 setSocket(socket, head, options) {209 const receiver = new Receiver({210 allowSynchronousEvents: options.allowSynchronousEvents,211 binaryType: this.binaryType,212 extensions: this._extensions,213 isServer: this._isServer,214 maxPayload: options.maxPayload,215 skipUTF8Validation: options.skipUTF8Validation216 });217 218 const sender = new Sender(socket, this._extensions, options.generateMask);219 220 this._receiver = receiver;221 this._sender = sender;222 this._socket = socket;223 224 receiver[kWebSocket] = this;225 sender[kWebSocket] = this;226 socket[kWebSocket] = this;227 228 receiver.on('conclude', receiverOnConclude);229 receiver.on('drain', receiverOnDrain);230 receiver.on('error', receiverOnError);231 receiver.on('message', receiverOnMessage);232 receiver.on('ping', receiverOnPing);233 receiver.on('pong', receiverOnPong);234 235 sender.onerror = senderOnError;236 237 //238 // These methods may not be available if `socket` is just a `Duplex`.239 //240 if (socket.setTimeout) socket.setTimeout(0);241 if (socket.setNoDelay) socket.setNoDelay();242 243 if (head.length > 0) socket.unshift(head);244 245 socket.on('close', socketOnClose);246 socket.on('data', socketOnData);247 socket.on('end', socketOnEnd);248 socket.on('error', socketOnError);249 250 this._readyState = WebSocket.OPEN;251 this.emit('open');252 }253 254 /**255 * Emit the `'close'` event.256 *257 * @private258 */259 emitClose() {260 if (!this._socket) {261 this._readyState = WebSocket.CLOSED;262 this.emit('close', this._closeCode, this._closeMessage);263 return;264 }265 266 if (this._extensions[PerMessageDeflate.extensionName]) {267 this._extensions[PerMessageDeflate.extensionName].cleanup();268 }269 270 this._receiver.removeAllListeners();271 this._readyState = WebSocket.CLOSED;272 this.emit('close', this._closeCode, this._closeMessage);273 }274 275 /**276 * Start a closing handshake.277 *278 * +----------+ +-----------+ +----------+279 * - - -|ws.close()|-->|close frame|-->|ws.close()|- - -280 * | +----------+ +-----------+ +----------+ |281 * +----------+ +-----------+ |282 * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING283 * +----------+ +-----------+ |284 * | | | +---+ |285 * +------------------------+-->|fin| - - - -286 * | +---+ | +---+287 * - - - - -|fin|<---------------------+288 * +---+289 *290 * @param {Number} [code] Status code explaining why the connection is closing291 * @param {(String|Buffer)} [data] The reason why the connection is292 * closing293 * @public294 */295 close(code, data) {296 if (this.readyState === WebSocket.CLOSED) return;297 if (this.readyState === WebSocket.CONNECTING) {298 const msg = 'WebSocket was closed before the connection was established';299 abortHandshake(this, this._req, msg);300 return;301 }302 303 if (this.readyState === WebSocket.CLOSING) {304 if (305 this._closeFrameSent &&306 (this._closeFrameReceived || this._receiver._writableState.errorEmitted)307 ) {308 this._socket.end();309 }310 311 return;312 }313 314 this._readyState = WebSocket.CLOSING;315 this._sender.close(code, data, !this._isServer, (err) => {316 //317 // This error is handled by the `'error'` listener on the socket. We only318 // want to know if the close frame has been sent here.319 //320 if (err) return;321 322 this._closeFrameSent = true;323 324 if (325 this._closeFrameReceived ||326 this._receiver._writableState.errorEmitted327 ) {328 this._socket.end();329 }330 });331 332 setCloseTimer(this);333 }334 335 /**336 * Pause the socket.337 *338 * @public339 */340 pause() {341 if (342 this.readyState === WebSocket.CONNECTING ||343 this.readyState === WebSocket.CLOSED344 ) {345 return;346 }347 348 this._paused = true;349 this._socket.pause();350 }351 352 /**353 * Send a ping.354 *355 * @param {*} [data] The data to send356 * @param {Boolean} [mask] Indicates whether or not to mask `data`357 * @param {Function} [cb] Callback which is executed when the ping is sent358 * @public359 */360 ping(data, mask, cb) {361 if (this.readyState === WebSocket.CONNECTING) {362 throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');363 }364 365 if (typeof data === 'function') {366 cb = data;367 data = mask = undefined;368 } else if (typeof mask === 'function') {369 cb = mask;370 mask = undefined;371 }372 373 if (typeof data === 'number') data = data.toString();374 375 if (this.readyState !== WebSocket.OPEN) {376 sendAfterClose(this, data, cb);377 return;378 }379 380 if (mask === undefined) mask = !this._isServer;381 this._sender.ping(data || EMPTY_BUFFER, mask, cb);382 }383 384 /**385 * Send a pong.386 *387 * @param {*} [data] The data to send388 * @param {Boolean} [mask] Indicates whether or not to mask `data`389 * @param {Function} [cb] Callback which is executed when the pong is sent390 * @public391 */392 pong(data, mask, cb) {393 if (this.readyState === WebSocket.CONNECTING) {394 throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');395 }396 397 if (typeof data === 'function') {398 cb = data;399 data = mask = undefined;400 } else if (typeof mask === 'function') {401 cb = mask;402 mask = undefined;403 }404 405 if (typeof data === 'number') data = data.toString();406 407 if (this.readyState !== WebSocket.OPEN) {408 sendAfterClose(this, data, cb);409 return;410 }411 412 if (mask === undefined) mask = !this._isServer;413 this._sender.pong(data || EMPTY_BUFFER, mask, cb);414 }415 416 /**417 * Resume the socket.418 *419 * @public420 */421 resume() {422 if (423 this.readyState === WebSocket.CONNECTING ||424 this.readyState === WebSocket.CLOSED425 ) {426 return;427 }428 429 this._paused = false;430 if (!this._receiver._writableState.needDrain) this._socket.resume();431 }432 433 /**434 * Send a data message.435 *436 * @param {*} data The message to send437 * @param {Object} [options] Options object438 * @param {Boolean} [options.binary] Specifies whether `data` is binary or439 * text440 * @param {Boolean} [options.compress] Specifies whether or not to compress441 * `data`442 * @param {Boolean} [options.fin=true] Specifies whether the fragment is the443 * last one444 * @param {Boolean} [options.mask] Specifies whether or not to mask `data`445 * @param {Function} [cb] Callback which is executed when data is written out446 * @public447 */448 send(data, options, cb) {449 if (this.readyState === WebSocket.CONNECTING) {450 throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');451 }452 453 if (typeof options === 'function') {454 cb = options;455 options = {};456 }457 458 if (typeof data === 'number') data = data.toString();459 460 if (this.readyState !== WebSocket.OPEN) {461 sendAfterClose(this, data, cb);462 return;463 }464 465 const opts = {466 binary: typeof data !== 'string',467 mask: !this._isServer,468 compress: true,469 fin: true,470 ...options471 };472 473 if (!this._extensions[PerMessageDeflate.extensionName]) {474 opts.compress = false;475 }476 477 this._sender.send(data || EMPTY_BUFFER, opts, cb);478 }479 480 /**481 * Forcibly close the connection.482 *483 * @public484 */485 terminate() {486 if (this.readyState === WebSocket.CLOSED) return;487 if (this.readyState === WebSocket.CONNECTING) {488 const msg = 'WebSocket was closed before the connection was established';489 abortHandshake(this, this._req, msg);490 return;491 }492 493 if (this._socket) {494 this._readyState = WebSocket.CLOSING;495 this._socket.destroy();496 }497 }498}499 500/**501 * @constant {Number} CONNECTING502 * @memberof WebSocket503 */504Object.defineProperty(WebSocket, 'CONNECTING', {505 enumerable: true,506 value: readyStates.indexOf('CONNECTING')507});508 509/**510 * @constant {Number} CONNECTING511 * @memberof WebSocket.prototype512 */513Object.defineProperty(WebSocket.prototype, 'CONNECTING', {514 enumerable: true,515 value: readyStates.indexOf('CONNECTING')516});517 518/**519 * @constant {Number} OPEN520 * @memberof WebSocket521 */522Object.defineProperty(WebSocket, 'OPEN', {523 enumerable: true,524 value: readyStates.indexOf('OPEN')525});526 527/**528 * @constant {Number} OPEN529 * @memberof WebSocket.prototype530 */531Object.defineProperty(WebSocket.prototype, 'OPEN', {532 enumerable: true,533 value: readyStates.indexOf('OPEN')534});535 536/**537 * @constant {Number} CLOSING538 * @memberof WebSocket539 */540Object.defineProperty(WebSocket, 'CLOSING', {541 enumerable: true,542 value: readyStates.indexOf('CLOSING')543});544 545/**546 * @constant {Number} CLOSING547 * @memberof WebSocket.prototype548 */549Object.defineProperty(WebSocket.prototype, 'CLOSING', {550 enumerable: true,551 value: readyStates.indexOf('CLOSING')552});553 554/**555 * @constant {Number} CLOSED556 * @memberof WebSocket557 */558Object.defineProperty(WebSocket, 'CLOSED', {559 enumerable: true,560 value: readyStates.indexOf('CLOSED')561});562 563/**564 * @constant {Number} CLOSED565 * @memberof WebSocket.prototype566 */567Object.defineProperty(WebSocket.prototype, 'CLOSED', {568 enumerable: true,569 value: readyStates.indexOf('CLOSED')570});571 572[573 'binaryType',574 'bufferedAmount',575 'extensions',576 'isPaused',577 'protocol',578 'readyState',579 'url'580].forEach((property) => {581 Object.defineProperty(WebSocket.prototype, property, { enumerable: true });582});583 584//585// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.586// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface587//588['open', 'error', 'close', 'message'].forEach((method) => {589 Object.defineProperty(WebSocket.prototype, `on${method}`, {590 enumerable: true,591 get() {592 for (const listener of this.listeners(method)) {593 if (listener[kForOnEventAttribute]) return listener[kListener];594 }595 596 return null;597 },598 set(handler) {599 for (const listener of this.listeners(method)) {600 if (listener[kForOnEventAttribute]) {601 this.removeListener(method, listener);602 break;603 }604 }605 606 if (typeof handler !== 'function') return;607 608 this.addEventListener(method, handler, {609 [kForOnEventAttribute]: true610 });611 }612 });613});614 615WebSocket.prototype.addEventListener = addEventListener;616WebSocket.prototype.removeEventListener = removeEventListener;617 618module.exports = WebSocket;619 620/**621 * Initialize a WebSocket client.622 *623 * @param {WebSocket} websocket The client to initialize624 * @param {(String|URL)} address The URL to which to connect625 * @param {Array} protocols The subprotocols626 * @param {Object} [options] Connection options627 * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether any628 * of the `'message'`, `'ping'`, and `'pong'` events can be emitted multiple629 * times in the same tick630 * @param {Boolean} [options.autoPong=true] Specifies whether or not to631 * automatically send a pong in response to a ping632 * @param {Function} [options.finishRequest] A function which can be used to633 * customize the headers of each http request before it is sent634 * @param {Boolean} [options.followRedirects=false] Whether or not to follow635 * redirects636 * @param {Function} [options.generateMask] The function used to generate the637 * masking key638 * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the639 * handshake request640 * @param {Number} [options.maxPayload=104857600] The maximum allowed message641 * size642 * @param {Number} [options.maxRedirects=10] The maximum number of redirects643 * allowed644 * @param {String} [options.origin] Value of the `Origin` or645 * `Sec-WebSocket-Origin` header646 * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable647 * permessage-deflate648 * @param {Number} [options.protocolVersion=13] Value of the649 * `Sec-WebSocket-Version` header650 * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or651 * not to skip UTF-8 validation for text and close messages652 * @private653 */654function initAsClient(websocket, address, protocols, options) {655 const opts = {656 allowSynchronousEvents: true,657 autoPong: true,658 protocolVersion: protocolVersions[1],659 maxPayload: 100 * 1024 * 1024,660 skipUTF8Validation: false,661 perMessageDeflate: true,662 followRedirects: false,663 maxRedirects: 10,664 ...options,665 socketPath: undefined,666 hostname: undefined,667 protocol: undefined,668 timeout: undefined,669 method: 'GET',670 host: undefined,671 path: undefined,672 port: undefined673 };674 675 websocket._autoPong = opts.autoPong;676 677 if (!protocolVersions.includes(opts.protocolVersion)) {678 throw new RangeError(679 `Unsupported protocol version: ${opts.protocolVersion} ` +680 `(supported versions: ${protocolVersions.join(', ')})`681 );682 }683 684 let parsedUrl;685 686 if (address instanceof URL) {687 parsedUrl = address;688 } else {689 try {690 parsedUrl = new URL(address);691 } catch (e) {692 throw new SyntaxError(`Invalid URL: ${address}`);693 }694 }695 696 if (parsedUrl.protocol === 'http:') {697 parsedUrl.protocol = 'ws:';698 } else if (parsedUrl.protocol === 'https:') {699 parsedUrl.protocol = 'wss:';700 }701 702 websocket._url = parsedUrl.href;703 704 const isSecure = parsedUrl.protocol === 'wss:';705 const isIpcUrl = parsedUrl.protocol === 'ws+unix:';706 let invalidUrlMessage;707 708 if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {709 invalidUrlMessage =710 'The URL\'s protocol must be one of "ws:", "wss:", ' +711 '"http:", "https", or "ws+unix:"';712 } else if (isIpcUrl && !parsedUrl.pathname) {713 invalidUrlMessage = "The URL's pathname is empty";714 } else if (parsedUrl.hash) {715 invalidUrlMessage = 'The URL contains a fragment identifier';716 }717 718 if (invalidUrlMessage) {719 const err = new SyntaxError(invalidUrlMessage);720 721 if (websocket._redirects === 0) {722 throw err;723 } else {724 emitErrorAndClose(websocket, err);725 return;726 }727 }728 729 const defaultPort = isSecure ? 443 : 80;730 const key = randomBytes(16).toString('base64');731 const request = isSecure ? https.request : http.request;732 const protocolSet = new Set();733 let perMessageDeflate;734 735 opts.createConnection =736 opts.createConnection || (isSecure ? tlsConnect : netConnect);737 opts.defaultPort = opts.defaultPort || defaultPort;738 opts.port = parsedUrl.port || defaultPort;739 opts.host = parsedUrl.hostname.startsWith('[')740 ? parsedUrl.hostname.slice(1, -1)741 : parsedUrl.hostname;742 opts.headers = {743 ...opts.headers,744 'Sec-WebSocket-Version': opts.protocolVersion,745 'Sec-WebSocket-Key': key,746 Connection: 'Upgrade',747 Upgrade: 'websocket'748 };749 opts.path = parsedUrl.pathname + parsedUrl.search;750 opts.timeout = opts.handshakeTimeout;751 752 if (opts.perMessageDeflate) {753 perMessageDeflate = new PerMessageDeflate(754 opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},755 false,756 opts.maxPayload757 );758 opts.headers['Sec-WebSocket-Extensions'] = format({759 [PerMessageDeflate.extensionName]: perMessageDeflate.offer()760 });761 }762 if (protocols.length) {763 for (const protocol of protocols) {764 if (765 typeof protocol !== 'string' ||766 !subprotocolRegex.test(protocol) ||767 protocolSet.has(protocol)768 ) {769 throw new SyntaxError(770 'An invalid or duplicated subprotocol was specified'771 );772 }773 774 protocolSet.add(protocol);775 }776 777 opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');778 }779 if (opts.origin) {780 if (opts.protocolVersion < 13) {781 opts.headers['Sec-WebSocket-Origin'] = opts.origin;782 } else {783 opts.headers.Origin = opts.origin;784 }785 }786 if (parsedUrl.username || parsedUrl.password) {787 opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;788 }789 790 if (isIpcUrl) {791 const parts = opts.path.split(':');792 793 opts.socketPath = parts[0];794 opts.path = parts[1];795 }796 797 let req;798 799 if (opts.followRedirects) {800 if (websocket._redirects === 0) {801 websocket._originalIpc = isIpcUrl;802 websocket._originalSecure = isSecure;803 websocket._originalHostOrSocketPath = isIpcUrl804 ? opts.socketPath805 : parsedUrl.host;806 807 const headers = options && options.headers;808 809 //810 // Shallow copy the user provided options so that headers can be changed811 // without mutating the original object.812 //813 options = { ...options, headers: {} };814 815 if (headers) {816 for (const [key, value] of Object.entries(headers)) {817 options.headers[key.toLowerCase()] = value;818 }819 }820 } else if (websocket.listenerCount('redirect') === 0) {821 const isSameHost = isIpcUrl822 ? websocket._originalIpc823 ? opts.socketPath === websocket._originalHostOrSocketPath824 : false825 : websocket._originalIpc826 ? false827 : parsedUrl.host === websocket._originalHostOrSocketPath;828 829 if (!isSameHost || (websocket._originalSecure && !isSecure)) {830 //831 // Match curl 7.77.0 behavior and drop the following headers. These832 // headers are also dropped when following a redirect to a subdomain.833 //834 delete opts.headers.authorization;835 delete opts.headers.cookie;836 837 if (!isSameHost) delete opts.headers.host;838 839 opts.auth = undefined;840 }841 }842 843 //844 // Match curl 7.77.0 behavior and make the first `Authorization` header win.845 // If the `Authorization` header is set, then there is nothing to do as it846 // will take precedence.847 //848 if (opts.auth && !options.headers.authorization) {849 options.headers.authorization =850 'Basic ' + Buffer.from(opts.auth).toString('base64');851 }852 853 req = websocket._req = request(opts);854 855 if (websocket._redirects) {856 //857 // Unlike what is done for the `'upgrade'` event, no early exit is858 // triggered here if the user calls `websocket.close()` or859 // `websocket.terminate()` from a listener of the `'redirect'` event. This860 // is because the user can also call `request.destroy()` with an error861 // before calling `websocket.close()` or `websocket.terminate()` and this862 // would result in an error being emitted on the `request` object with no863 // `'error'` event listeners attached.864 //865 websocket.emit('redirect', websocket.url, req);866 }867 } else {868 req = websocket._req = request(opts);869 }870 871 if (opts.timeout) {872 req.on('timeout', () => {873 abortHandshake(websocket, req, 'Opening handshake has timed out');874 });875 }876 877 req.on('error', (err) => {878 if (req === null || req[kAborted]) return;879 880 req = websocket._req = null;881 emitErrorAndClose(websocket, err);882 });883 884 req.on('response', (res) => {885 const location = res.headers.location;886 const statusCode = res.statusCode;887 888 if (889 location &&890 opts.followRedirects &&891 statusCode >= 300 &&892 statusCode < 400893 ) {894 if (++websocket._redirects > opts.maxRedirects) {895 abortHandshake(websocket, req, 'Maximum redirects exceeded');896 return;897 }898 899 req.abort();900 901 let addr;902 903 try {904 addr = new URL(location, address);905 } catch (e) {906 const err = new SyntaxError(`Invalid URL: ${location}`);907 emitErrorAndClose(websocket, err);908 return;909 }910 911 initAsClient(websocket, addr, protocols, options);912 } else if (!websocket.emit('unexpected-response', req, res)) {913 abortHandshake(914 websocket,915 req,916 `Unexpected server response: ${res.statusCode}`917 );918 }919 });920 921 req.on('upgrade', (res, socket, head) => {922 websocket.emit('upgrade', res);923 924 //925 // The user may have closed the connection from a listener of the926 // `'upgrade'` event.927 //928 if (websocket.readyState !== WebSocket.CONNECTING) return;929 930 req = websocket._req = null;931 932 const upgrade = res.headers.upgrade;933 934 if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {935 abortHandshake(websocket, socket, 'Invalid Upgrade header');936 return;937 }938 939 const digest = createHash('sha1')940 .update(key + GUID)941 .digest('base64');942 943 if (res.headers['sec-websocket-accept'] !== digest) {944 abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');945 return;946 }947 948 const serverProt = res.headers['sec-websocket-protocol'];949 let protError;950 951 if (serverProt !== undefined) {952 if (!protocolSet.size) {953 protError = 'Server sent a subprotocol but none was requested';954 } else if (!protocolSet.has(serverProt)) {955 protError = 'Server sent an invalid subprotocol';956 }957 } else if (protocolSet.size) {958 protError = 'Server sent no subprotocol';959 }960 961 if (protError) {962 abortHandshake(websocket, socket, protError);963 return;964 }965 966 if (serverProt) websocket._protocol = serverProt;967 968 const secWebSocketExtensions = res.headers['sec-websocket-extensions'];969 970 if (secWebSocketExtensions !== undefined) {971 if (!perMessageDeflate) {972 const message =973 'Server sent a Sec-WebSocket-Extensions header but no extension ' +974 'was requested';975 abortHandshake(websocket, socket, message);976 return;977 }978 979 let extensions;980 981 try {982 extensions = parse(secWebSocketExtensions);983 } catch (err) {984 const message = 'Invalid Sec-WebSocket-Extensions header';985 abortHandshake(websocket, socket, message);986 return;987 }988 989 const extensionNames = Object.keys(extensions);990 991 if (992 extensionNames.length !== 1 ||993 extensionNames[0] !== PerMessageDeflate.extensionName994 ) {995 const message = 'Server indicated an extension that was not requested';996 abortHandshake(websocket, socket, message);997 return;998 }999 1000 try {1001 perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);1002 } catch (err) {1003 const message = 'Invalid Sec-WebSocket-Extensions header';1004 abortHandshake(websocket, socket, message);1005 return;1006 }1007 1008 websocket._extensions[PerMessageDeflate.extensionName] =1009 perMessageDeflate;1010 }1011 1012 websocket.setSocket(socket, head, {1013 allowSynchronousEvents: opts.allowSynchronousEvents,1014 generateMask: opts.generateMask,1015 maxPayload: opts.maxPayload,1016 skipUTF8Validation: opts.skipUTF8Validation1017 });1018 });1019 1020 if (opts.finishRequest) {1021 opts.finishRequest(req, websocket);1022 } else {1023 req.end();1024 }1025}1026 1027/**1028 * Emit the `'error'` and `'close'` events.1029 *1030 * @param {WebSocket} websocket The WebSocket instance1031 * @param {Error} The error to emit1032 * @private1033 */1034function emitErrorAndClose(websocket, err) {1035 websocket._readyState = WebSocket.CLOSING;1036 //1037 // The following assignment is practically useless and is done only for1038 // consistency.1039 //1040 websocket._errorEmitted = true;1041 websocket.emit('error', err);1042 websocket.emitClose();1043}1044 1045/**1046 * Create a `net.Socket` and initiate a connection.1047 *1048 * @param {Object} options Connection options1049 * @return {net.Socket} The newly created socket used to start the connection1050 * @private1051 */1052function netConnect(options) {1053 options.path = options.socketPath;1054 return net.connect(options);1055}1056 1057/**1058 * Create a `tls.TLSSocket` and initiate a connection.1059 *1060 * @param {Object} options Connection options1061 * @return {tls.TLSSocket} The newly created socket used to start the connection1062 * @private1063 */1064function tlsConnect(options) {1065 options.path = undefined;1066 1067 if (!options.servername && options.servername !== '') {1068 options.servername = net.isIP(options.host) ? '' : options.host;1069 }1070 1071 return tls.connect(options);1072}1073 1074/**1075 * Abort the handshake and emit an error.1076 *1077 * @param {WebSocket} websocket The WebSocket instance1078 * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to1079 * abort or the socket to destroy1080 * @param {String} message The error message1081 * @private1082 */1083function abortHandshake(websocket, stream, message) {1084 websocket._readyState = WebSocket.CLOSING;1085 1086 const err = new Error(message);1087 Error.captureStackTrace(err, abortHandshake);1088 1089 if (stream.setHeader) {1090 stream[kAborted] = true;1091 stream.abort();1092 1093 if (stream.socket && !stream.socket.destroyed) {1094 //1095 // On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if1096 // called after the request completed. See1097 // https://github.com/websockets/ws/issues/1869.1098 //1099 stream.socket.destroy();1100 }1101 1102 process.nextTick(emitErrorAndClose, websocket, err);1103 } else {1104 stream.destroy(err);1105 stream.once('error', websocket.emit.bind(websocket, 'error'));1106 stream.once('close', websocket.emitClose.bind(websocket));1107 }1108}1109 1110/**1111 * Handle cases where the `ping()`, `pong()`, or `send()` methods are called1112 * when the `readyState` attribute is `CLOSING` or `CLOSED`.1113 *1114 * @param {WebSocket} websocket The WebSocket instance1115 * @param {*} [data] The data to send1116 * @param {Function} [cb] Callback1117 * @private1118 */1119function sendAfterClose(websocket, data, cb) {1120 if (data) {1121 const length = isBlob(data) ? data.size : toBuffer(data).length;1122 1123 //1124 // The `_bufferedAmount` property is used only when the peer is a client and1125 // the opening handshake fails. Under these circumstances, in fact, the1126 // `setSocket()` method is not called, so the `_socket` and `_sender`1127 // properties are set to `null`.1128 //1129 if (websocket._socket) websocket._sender._bufferedBytes += length;1130 else websocket._bufferedAmount += length;1131 }1132 1133 if (cb) {1134 const err = new Error(1135 `WebSocket is not open: readyState ${websocket.readyState} ` +1136 `(${readyStates[websocket.readyState]})`1137 );1138 process.nextTick(cb, err);1139 }1140}1141 1142/**1143 * The listener of the `Receiver` `'conclude'` event.1144 *1145 * @param {Number} code The status code1146 * @param {Buffer} reason The reason for closing1147 * @private1148 */1149function receiverOnConclude(code, reason) {1150 const websocket = this[kWebSocket];1151 1152 websocket._closeFrameReceived = true;1153 websocket._closeMessage = reason;1154 websocket._closeCode = code;1155 1156 if (websocket._socket[kWebSocket] === undefined) return;1157 1158 websocket._socket.removeListener('data', socketOnData);1159 process.nextTick(resume, websocket._socket);1160 1161 if (code === 1005) websocket.close();1162 else websocket.close(code, reason);1163}1164 1165/**1166 * The listener of the `Receiver` `'drain'` event.1167 *1168 * @private1169 */1170function receiverOnDrain() {1171 const websocket = this[kWebSocket];1172 1173 if (!websocket.isPaused) websocket._socket.resume();1174}1175 1176/**1177 * The listener of the `Receiver` `'error'` event.1178 *1179 * @param {(RangeError|Error)} err The emitted error1180 * @private1181 */1182function receiverOnError(err) {1183 const websocket = this[kWebSocket];1184 1185 if (websocket._socket[kWebSocket] !== undefined) {1186 websocket._socket.removeListener('data', socketOnData);1187 1188 //1189 // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See1190 // https://github.com/websockets/ws/issues/1940.1191 //1192 process.nextTick(resume, websocket._socket);1193 1194 websocket.close(err[kStatusCode]);1195 }1196 1197 if (!websocket._errorEmitted) {1198 websocket._errorEmitted = true;1199 websocket.emit('error', err);1200 }