SPB Git forge

spb/admin-ka

Public
41commits 1branches 0releases
172.9 MBsize
maindefault branch
19 days agolast push
JavaScript 65.5% Python 17.8% CSS 13% HTML 3.7%
36.5 KB · 1,408 lines javascript
Raw Blame History
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