CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
websocket.js1389 linesDownload Raw Back to lib
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  }

Showing the first 1,200 of 1389 lines. Download the file for the rest.