@andersbakken/fisk 4.0.52 → 4.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,38 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var blessed = require('@andersbakken/blessed');
5
- var require$$0$4 = require('events');
4
+ var require$$0$3 = require('events');
6
5
  var require$$1 = require('https');
7
6
  var require$$2$1 = require('http');
8
7
  var require$$3 = require('net');
9
8
  var require$$4 = require('tls');
10
- var require$$0$3 = require('crypto');
11
- var require$$6 = require('url');
9
+ var require$$5 = require('crypto');
10
+ var require$$0$2 = require('stream');
11
+ var require$$7 = require('url');
12
12
  var require$$0$1 = require('zlib');
13
13
  var require$$0 = require('fs');
14
14
  var path = require('path');
15
15
  var require$$2 = require('os');
16
- var require$$0$2 = require('stream');
17
16
  var assert = require('assert');
17
+ var blessed = require('@andersbakken/blessed');
18
18
  var require$$1$1 = require('module');
19
19
 
20
20
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
21
21
 
22
- var blessed__default = /*#__PURE__*/_interopDefaultLegacy(blessed);
23
- var require$$0__default$4 = /*#__PURE__*/_interopDefaultLegacy(require$$0$4);
22
+ var require$$0__default$3 = /*#__PURE__*/_interopDefaultLegacy(require$$0$3);
24
23
  var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
25
24
  var require$$2__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$2$1);
26
25
  var require$$3__default = /*#__PURE__*/_interopDefaultLegacy(require$$3);
27
26
  var require$$4__default = /*#__PURE__*/_interopDefaultLegacy(require$$4);
28
- var require$$0__default$3 = /*#__PURE__*/_interopDefaultLegacy(require$$0$3);
29
- var require$$6__default = /*#__PURE__*/_interopDefaultLegacy(require$$6);
27
+ var require$$5__default = /*#__PURE__*/_interopDefaultLegacy(require$$5);
28
+ var require$$0__default$2 = /*#__PURE__*/_interopDefaultLegacy(require$$0$2);
29
+ var require$$7__default = /*#__PURE__*/_interopDefaultLegacy(require$$7);
30
30
  var require$$0__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$0$1);
31
31
  var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
32
32
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
33
33
  var require$$2__default = /*#__PURE__*/_interopDefaultLegacy(require$$2);
34
- var require$$0__default$2 = /*#__PURE__*/_interopDefaultLegacy(require$$0$2);
35
34
  var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert);
35
+ var blessed__default = /*#__PURE__*/_interopDefaultLegacy(blessed);
36
36
  var require$$1__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$1$1);
37
37
 
38
38
  var bufferUtilExports = {};
@@ -1028,6 +1028,7 @@ function inflateOnData(chunk) {
1028
1028
  }
1029
1029
 
1030
1030
  this[kError] = new RangeError('Max payload size exceeded');
1031
+ this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
1031
1032
  this[kError][kStatusCode$2] = 1009;
1032
1033
  this.removeListener('data', inflateOnData);
1033
1034
  this.reset();
@@ -1272,7 +1273,7 @@ const INFLATING = 5;
1272
1273
  /**
1273
1274
  * HyBi Receiver implementation.
1274
1275
  *
1275
- * @extends stream.Writable
1276
+ * @extends Writable
1276
1277
  */
1277
1278
  class Receiver$1 extends Writable {
1278
1279
  /**
@@ -1418,14 +1419,26 @@ class Receiver$1 extends Writable {
1418
1419
 
1419
1420
  if ((buf[0] & 0x30) !== 0x00) {
1420
1421
  this._loop = false;
1421
- return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002);
1422
+ return error(
1423
+ RangeError,
1424
+ 'RSV2 and RSV3 must be clear',
1425
+ true,
1426
+ 1002,
1427
+ 'WS_ERR_UNEXPECTED_RSV_2_3'
1428
+ );
1422
1429
  }
1423
1430
 
1424
1431
  const compressed = (buf[0] & 0x40) === 0x40;
1425
1432
 
1426
1433
  if (compressed && !this._extensions[PerMessageDeflate$3.extensionName]) {
1427
1434
  this._loop = false;
1428
- return error(RangeError, 'RSV1 must be clear', true, 1002);
1435
+ return error(
1436
+ RangeError,
1437
+ 'RSV1 must be clear',
1438
+ true,
1439
+ 1002,
1440
+ 'WS_ERR_UNEXPECTED_RSV_1'
1441
+ );
1429
1442
  }
1430
1443
 
1431
1444
  this._fin = (buf[0] & 0x80) === 0x80;
@@ -1435,31 +1448,61 @@ class Receiver$1 extends Writable {
1435
1448
  if (this._opcode === 0x00) {
1436
1449
  if (compressed) {
1437
1450
  this._loop = false;
1438
- return error(RangeError, 'RSV1 must be clear', true, 1002);
1451
+ return error(
1452
+ RangeError,
1453
+ 'RSV1 must be clear',
1454
+ true,
1455
+ 1002,
1456
+ 'WS_ERR_UNEXPECTED_RSV_1'
1457
+ );
1439
1458
  }
1440
1459
 
1441
1460
  if (!this._fragmented) {
1442
1461
  this._loop = false;
1443
- return error(RangeError, 'invalid opcode 0', true, 1002);
1462
+ return error(
1463
+ RangeError,
1464
+ 'invalid opcode 0',
1465
+ true,
1466
+ 1002,
1467
+ 'WS_ERR_INVALID_OPCODE'
1468
+ );
1444
1469
  }
1445
1470
 
1446
1471
  this._opcode = this._fragmented;
1447
1472
  } else if (this._opcode === 0x01 || this._opcode === 0x02) {
1448
1473
  if (this._fragmented) {
1449
1474
  this._loop = false;
1450
- return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
1475
+ return error(
1476
+ RangeError,
1477
+ `invalid opcode ${this._opcode}`,
1478
+ true,
1479
+ 1002,
1480
+ 'WS_ERR_INVALID_OPCODE'
1481
+ );
1451
1482
  }
1452
1483
 
1453
1484
  this._compressed = compressed;
1454
1485
  } else if (this._opcode > 0x07 && this._opcode < 0x0b) {
1455
1486
  if (!this._fin) {
1456
1487
  this._loop = false;
1457
- return error(RangeError, 'FIN must be set', true, 1002);
1488
+ return error(
1489
+ RangeError,
1490
+ 'FIN must be set',
1491
+ true,
1492
+ 1002,
1493
+ 'WS_ERR_EXPECTED_FIN'
1494
+ );
1458
1495
  }
1459
1496
 
1460
1497
  if (compressed) {
1461
1498
  this._loop = false;
1462
- return error(RangeError, 'RSV1 must be clear', true, 1002);
1499
+ return error(
1500
+ RangeError,
1501
+ 'RSV1 must be clear',
1502
+ true,
1503
+ 1002,
1504
+ 'WS_ERR_UNEXPECTED_RSV_1'
1505
+ );
1463
1506
  }
1464
1507
 
1465
1508
  if (this._payloadLength > 0x7d) {
@@ -1468,12 +1511,19 @@ class Receiver$1 extends Writable {
1468
1511
  RangeError,
1469
1512
  `invalid payload length ${this._payloadLength}`,
1470
1513
  true,
1471
- 1002
1514
+ 1002,
1515
+ 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
1472
1516
  );
1473
1517
  }
1474
1518
  } else {
1475
1519
  this._loop = false;
1476
- return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
1520
+ return error(
1521
+ RangeError,
1522
+ `invalid opcode ${this._opcode}`,
1523
+ true,
1524
+ 1002,
1525
+ 'WS_ERR_INVALID_OPCODE'
1526
+ );
1477
1527
  }
1478
1528
 
1479
1529
  if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
@@ -1482,11 +1532,23 @@ class Receiver$1 extends Writable {
1482
1532
  if (this._isServer) {
1483
1533
  if (!this._masked) {
1484
1534
  this._loop = false;
1485
- return error(RangeError, 'MASK must be set', true, 1002);
1535
+ return error(
1536
+ RangeError,
1537
+ 'MASK must be set',
1538
+ true,
1539
+ 1002,
1540
+ 'WS_ERR_EXPECTED_MASK'
1541
+ );
1486
1542
  }
1487
1543
  } else if (this._masked) {
1488
1544
  this._loop = false;
1489
- return error(RangeError, 'MASK must be clear', true, 1002);
1545
+ return error(
1546
+ RangeError,
1547
+ 'MASK must be clear',
1548
+ true,
1549
+ 1002,
1550
+ 'WS_ERR_UNEXPECTED_MASK'
1551
+ );
1490
1552
  }
1491
1553
 
1492
1554
  if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
@@ -1535,7 +1597,8 @@ class Receiver$1 extends Writable {
1535
1597
  RangeError,
1536
1598
  'Unsupported WebSocket frame: payload length > 2^53 - 1',
1537
1599
  false,
1538
- 1009
1600
+ 1009,
1601
+ 'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'
1539
1602
  );
1540
1603
  }
1541
1604
 
@@ -1554,7 +1617,13 @@ class Receiver$1 extends Writable {
1554
1617
  this._totalPayloadLength += this._payloadLength;
1555
1618
  if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
1556
1619
  this._loop = false;
1557
- return error(RangeError, 'Max payload size exceeded', false, 1009);
1620
+ return error(
1621
+ RangeError,
1622
+ 'Max payload size exceeded',
1623
+ false,
1624
+ 1009,
1625
+ 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
1626
+ );
1558
1627
  }
1559
1628
  }
1560
1629
 
@@ -1634,7 +1703,13 @@ class Receiver$1 extends Writable {
1634
1703
  this._messageLength += buf.length;
1635
1704
  if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
1636
1705
  return cb(
1637
- error(RangeError, 'Max payload size exceeded', false, 1009)
1706
+ error(
1707
+ RangeError,
1708
+ 'Max payload size exceeded',
1709
+ false,
1710
+ 1009,
1711
+ 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
1712
+ )
1638
1713
  );
1639
1714
  }
1640
1715
 
@@ -1681,7 +1756,13 @@ class Receiver$1 extends Writable {
1681
1756
 
1682
1757
  if (!isValidUTF8(buf)) {
1683
1758
  this._loop = false;
1684
- return error(Error, 'invalid UTF-8 sequence', true, 1007);
1759
+ return error(
1760
+ Error,
1761
+ 'invalid UTF-8 sequence',
1762
+ true,
1763
+ 1007,
1764
+ 'WS_ERR_INVALID_UTF8'
1765
+ );
1685
1766
  }
1686
1767
 
1687
1768
  this.emit('message', buf.toString());
@@ -1706,18 +1787,36 @@ class Receiver$1 extends Writable {
1706
1787
  this.emit('conclude', 1005, '');
1707
1788
  this.end();
1708
1789
  } else if (data.length === 1) {
1709
- return error(RangeError, 'invalid payload length 1', true, 1002);
1790
+ return error(
1791
+ RangeError,
1792
+ 'invalid payload length 1',
1793
+ true,
1794
+ 1002,
1795
+ 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
1796
+ );
1710
1797
  } else {
1711
1798
  const code = data.readUInt16BE(0);
1712
1799
 
1713
1800
  if (!isValidStatusCode$1(code)) {
1714
- return error(RangeError, `invalid status code ${code}`, true, 1002);
1801
+ return error(
1802
+ RangeError,
1803
+ `invalid status code ${code}`,
1804
+ true,
1805
+ 1002,
1806
+ 'WS_ERR_INVALID_CLOSE_CODE'
1807
+ );
1715
1808
  }
1716
1809
 
1717
1810
  const buf = data.slice(2);
1718
1811
 
1719
1812
  if (!isValidUTF8(buf)) {
1720
- return error(Error, 'invalid UTF-8 sequence', true, 1007);
1813
+ return error(
1814
+ Error,
1815
+ 'invalid UTF-8 sequence',
1816
+ true,
1817
+ 1007,
1818
+ 'WS_ERR_INVALID_UTF8'
1819
+ );
1721
1820
  }
1722
1821
 
1723
1822
  this.emit('conclude', code, buf.toString());
@@ -1738,25 +1837,28 @@ var receiver = Receiver$1;
1738
1837
  /**
1739
1838
  * Builds an error object.
1740
1839
  *
1741
- * @param {(Error|RangeError)} ErrorCtor The error constructor
1840
+ * @param {function(new:Error|RangeError)} ErrorCtor The error constructor
1742
1841
  * @param {String} message The error message
1743
1842
  * @param {Boolean} prefix Specifies whether or not to add a default prefix to
1744
1843
  * `message`
1745
1844
  * @param {Number} statusCode The status code
1845
+ * @param {String} errorCode The exposed error code
1746
1846
  * @return {(Error|RangeError)} The error
1747
1847
  * @private
1748
1848
  */
1749
- function error(ErrorCtor, message, prefix, statusCode) {
1849
+ function error(ErrorCtor, message, prefix, statusCode, errorCode) {
1750
1850
  const err = new ErrorCtor(
1751
1851
  prefix ? `Invalid WebSocket frame: ${message}` : message
1752
1852
  );
1753
1853
 
1754
1854
  Error.captureStackTrace(err, error);
1855
+ err.code = errorCode;
1755
1856
  err[kStatusCode$1] = statusCode;
1756
1857
  return err;
1757
1858
  }
1758
1859
 
1759
- const { randomFillSync } = require$$0__default$3["default"];
1860
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
1861
+ const { randomFillSync } = require$$5__default["default"];
1760
1862
 
1761
1863
  const PerMessageDeflate$2 = permessageDeflate;
1762
1864
  const { EMPTY_BUFFER: EMPTY_BUFFER$1 } = constants;
@@ -1772,7 +1874,7 @@ class Sender$1 {
1772
1874
  /**
1773
1875
  * Creates a Sender instance.
1774
1876
  *
1775
- * @param {net.Socket} socket The connection socket
1877
+ * @param {(net.Socket|tls.Socket)} socket The connection socket
1776
1878
  * @param {Object} [extensions] An object containing the negotiated extensions
1777
1879
  */
1778
1880
  constructor(socket, extensions) {
@@ -2565,13 +2667,15 @@ function format$2(extensions) {
2565
2667
 
2566
2668
  var extension = { format: format$2, parse: parse$2 };
2567
2669
 
2568
- const EventEmitter$1 = require$$0__default$4["default"];
2670
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Readable$" }] */
2671
+
2672
+ const EventEmitter$1 = require$$0__default$3["default"];
2569
2673
  const https = require$$1__default["default"];
2570
- const http = require$$2__default$1["default"];
2674
+ const http$1 = require$$2__default$1["default"];
2571
2675
  const net = require$$3__default["default"];
2572
2676
  const tls = require$$4__default["default"];
2573
- const { randomBytes, createHash: createHash$1 } = require$$0__default$3["default"];
2574
- const { URL } = require$$6__default["default"];
2677
+ const { randomBytes, createHash: createHash$1 } = require$$5__default["default"];
2678
+ const { URL } = require$$7__default["default"];
2575
2679
 
2576
2680
  const PerMessageDeflate$1 = permessageDeflate;
2577
2681
  const Receiver = receiver;
@@ -2601,7 +2705,7 @@ class WebSocket$2 extends EventEmitter$1 {
2601
2705
  /**
2602
2706
  * Create a new `WebSocket`.
2603
2707
  *
2604
- * @param {(String|url.URL)} address The URL to which to connect
2708
+ * @param {(String|URL)} address The URL to which to connect
2605
2709
  * @param {(String|String[])} [protocols] The subprotocols
2606
2710
  * @param {Object} [options] Connection options
2607
2711
  */
@@ -2677,6 +2781,50 @@ class WebSocket$2 extends EventEmitter$1 {
2677
2781
  return Object.keys(this._extensions).join();
2678
2782
  }
2679
2783
 
2784
+ /**
2785
+ * @type {Function}
2786
+ */
2787
+ /* istanbul ignore next */
2788
+ get onclose() {
2789
+ return undefined;
2790
+ }
2791
+
2792
+ /* istanbul ignore next */
2793
+ set onclose(listener) {}
2794
+
2795
+ /**
2796
+ * @type {Function}
2797
+ */
2798
+ /* istanbul ignore next */
2799
+ get onerror() {
2800
+ return undefined;
2801
+ }
2802
+
2803
+ /* istanbul ignore next */
2804
+ set onerror(listener) {}
2805
+
2806
+ /**
2807
+ * @type {Function}
2808
+ */
2809
+ /* istanbul ignore next */
2810
+ get onopen() {
2811
+ return undefined;
2812
+ }
2813
+
2814
+ /* istanbul ignore next */
2815
+ set onopen(listener) {}
2816
+
2817
+ /**
2818
+ * @type {Function}
2819
+ */
2820
+ /* istanbul ignore next */
2821
+ get onmessage() {
2822
+ return undefined;
2823
+ }
2824
+
2825
+ /* istanbul ignore next */
2826
+ set onmessage(listener) {}
2827
+
2680
2828
  /**
2681
2829
  * @type {String}
2682
2830
  */
@@ -2701,7 +2849,8 @@ class WebSocket$2 extends EventEmitter$1 {
2701
2849
  /**
2702
2850
  * Set up the socket and the internal resources.
2703
2851
  *
2704
- * @param {net.Socket} socket The network socket between the server and client
2852
+ * @param {(net.Socket|tls.Socket)} socket The network socket between the
2853
+ * server and client
2705
2854
  * @param {Buffer} head The first packet of the upgraded stream
2706
2855
  * @param {Number} [maxPayload=0] The maximum allowed message size
2707
2856
  * @private
@@ -2790,7 +2939,13 @@ class WebSocket$2 extends EventEmitter$1 {
2790
2939
  }
2791
2940
 
2792
2941
  if (this.readyState === WebSocket$2.CLOSING) {
2793
- if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
2942
+ if (
2943
+ this._closeFrameSent &&
2944
+ (this._closeFrameReceived || this._receiver._writableState.errorEmitted)
2945
+ ) {
2946
+ this._socket.end();
2947
+ }
2948
+
2794
2949
  return;
2795
2950
  }
2796
2951
 
@@ -2803,7 +2958,13 @@ class WebSocket$2 extends EventEmitter$1 {
2803
2958
  if (err) return;
2804
2959
 
2805
2960
  this._closeFrameSent = true;
2806
- if (this._closeFrameReceived) this._socket.end();
2961
+
2962
+ if (
2963
+ this._closeFrameReceived ||
2964
+ this._receiver._writableState.errorEmitted
2965
+ ) {
2966
+ this._socket.end();
2967
+ }
2807
2968
  });
2808
2969
 
2809
2970
  //
@@ -2945,11 +3106,76 @@ class WebSocket$2 extends EventEmitter$1 {
2945
3106
  }
2946
3107
  }
2947
3108
 
2948
- readyStates.forEach((readyState, i) => {
2949
- const descriptor = { enumerable: true, value: i };
3109
+ /**
3110
+ * @constant {Number} CONNECTING
3111
+ * @memberof WebSocket
3112
+ */
3113
+ Object.defineProperty(WebSocket$2, 'CONNECTING', {
3114
+ enumerable: true,
3115
+ value: readyStates.indexOf('CONNECTING')
3116
+ });
3117
+
3118
+ /**
3119
+ * @constant {Number} CONNECTING
3120
+ * @memberof WebSocket.prototype
3121
+ */
3122
+ Object.defineProperty(WebSocket$2.prototype, 'CONNECTING', {
3123
+ enumerable: true,
3124
+ value: readyStates.indexOf('CONNECTING')
3125
+ });
3126
+
3127
+ /**
3128
+ * @constant {Number} OPEN
3129
+ * @memberof WebSocket
3130
+ */
3131
+ Object.defineProperty(WebSocket$2, 'OPEN', {
3132
+ enumerable: true,
3133
+ value: readyStates.indexOf('OPEN')
3134
+ });
3135
+
3136
+ /**
3137
+ * @constant {Number} OPEN
3138
+ * @memberof WebSocket.prototype
3139
+ */
3140
+ Object.defineProperty(WebSocket$2.prototype, 'OPEN', {
3141
+ enumerable: true,
3142
+ value: readyStates.indexOf('OPEN')
3143
+ });
3144
+
3145
+ /**
3146
+ * @constant {Number} CLOSING
3147
+ * @memberof WebSocket
3148
+ */
3149
+ Object.defineProperty(WebSocket$2, 'CLOSING', {
3150
+ enumerable: true,
3151
+ value: readyStates.indexOf('CLOSING')
3152
+ });
3153
+
3154
+ /**
3155
+ * @constant {Number} CLOSING
3156
+ * @memberof WebSocket.prototype
3157
+ */
3158
+ Object.defineProperty(WebSocket$2.prototype, 'CLOSING', {
3159
+ enumerable: true,
3160
+ value: readyStates.indexOf('CLOSING')
3161
+ });
3162
+
3163
+ /**
3164
+ * @constant {Number} CLOSED
3165
+ * @memberof WebSocket
3166
+ */
3167
+ Object.defineProperty(WebSocket$2, 'CLOSED', {
3168
+ enumerable: true,
3169
+ value: readyStates.indexOf('CLOSED')
3170
+ });
2950
3171
 
2951
- Object.defineProperty(WebSocket$2.prototype, readyState, descriptor);
2952
- Object.defineProperty(WebSocket$2, readyState, descriptor);
3172
+ /**
3173
+ * @constant {Number} CLOSED
3174
+ * @memberof WebSocket.prototype
3175
+ */
3176
+ Object.defineProperty(WebSocket$2.prototype, 'CLOSED', {
3177
+ enumerable: true,
3178
+ value: readyStates.indexOf('CLOSED')
2953
3179
  });
2954
3180
 
2955
3181
  [
@@ -2969,14 +3195,7 @@ readyStates.forEach((readyState, i) => {
2969
3195
  //
2970
3196
  ['open', 'error', 'close', 'message'].forEach((method) => {
2971
3197
  Object.defineProperty(WebSocket$2.prototype, `on${method}`, {
2972
- configurable: true,
2973
3198
  enumerable: true,
2974
- /**
2975
- * Return the listener of the event.
2976
- *
2977
- * @return {(Function|undefined)} The event listener or `undefined`
2978
- * @public
2979
- */
2980
3199
  get() {
2981
3200
  const listeners = this.listeners(method);
2982
3201
  for (let i = 0; i < listeners.length; i++) {
@@ -2985,12 +3204,6 @@ readyStates.forEach((readyState, i) => {
2985
3204
 
2986
3205
  return undefined;
2987
3206
  },
2988
- /**
2989
- * Add a listener for the event.
2990
- *
2991
- * @param {Function} listener The listener to add
2992
- * @public
2993
- */
2994
3207
  set(listener) {
2995
3208
  const listeners = this.listeners(method);
2996
3209
  for (let i = 0; i < listeners.length; i++) {
@@ -3013,7 +3226,7 @@ var websocket = WebSocket$2;
3013
3226
  * Initialize a WebSocket client.
3014
3227
  *
3015
3228
  * @param {WebSocket} websocket The client to initialize
3016
- * @param {(String|url.URL)} address The URL to which to connect
3229
+ * @param {(String|URL)} address The URL to which to connect
3017
3230
  * @param {String} [protocols] The subprotocols
3018
3231
  * @param {Object} [options] Connection options
3019
3232
  * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
@@ -3071,14 +3284,21 @@ function initAsClient(websocket, address, protocols, options) {
3071
3284
  const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
3072
3285
 
3073
3286
  if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
3074
- throw new Error(`Invalid URL: ${websocket.url}`);
3287
+ const err = new Error(`Invalid URL: ${websocket.url}`);
3288
+
3289
+ if (websocket._redirects === 0) {
3290
+ throw err;
3291
+ } else {
3292
+ emitErrorAndClose(websocket, err);
3293
+ return;
3294
+ }
3075
3295
  }
3076
3296
 
3077
3297
  const isSecure =
3078
3298
  parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
3079
3299
  const defaultPort = isSecure ? 443 : 80;
3080
3300
  const key = randomBytes(16).toString('base64');
3081
- const get = isSecure ? https.get : http.get;
3301
+ const get = isSecure ? https.get : http$1.get;
3082
3302
  let perMessageDeflate;
3083
3303
 
3084
3304
  opts.createConnection = isSecure ? tlsConnect : netConnect;
@@ -3128,6 +3348,61 @@ function initAsClient(websocket, address, protocols, options) {
3128
3348
  opts.path = parts[1];
3129
3349
  }
3130
3350
 
3351
+ if (opts.followRedirects) {
3352
+ if (websocket._redirects === 0) {
3353
+ websocket._originalUnixSocket = isUnixSocket;
3354
+ websocket._originalSecure = isSecure;
3355
+ websocket._originalHostOrSocketPath = isUnixSocket
3356
+ ? opts.socketPath
3357
+ : parsedUrl.host;
3358
+
3359
+ const headers = options && options.headers;
3360
+
3361
+ //
3362
+ // Shallow copy the user provided options so that headers can be changed
3363
+ // without mutating the original object.
3364
+ //
3365
+ options = { ...options, headers: {} };
3366
+
3367
+ if (headers) {
3368
+ for (const [key, value] of Object.entries(headers)) {
3369
+ options.headers[key.toLowerCase()] = value;
3370
+ }
3371
+ }
3372
+ } else {
3373
+ const isSameHost = isUnixSocket
3374
+ ? websocket._originalUnixSocket
3375
+ ? opts.socketPath === websocket._originalHostOrSocketPath
3376
+ : false
3377
+ : websocket._originalUnixSocket
3378
+ ? false
3379
+ : parsedUrl.host === websocket._originalHostOrSocketPath;
3380
+
3381
+ if (!isSameHost || (websocket._originalSecure && !isSecure)) {
3382
+ //
3383
+ // Match curl 7.77.0 behavior and drop the following headers. These
3384
+ // headers are also dropped when following a redirect to a subdomain.
3385
+ //
3386
+ delete opts.headers.authorization;
3387
+ delete opts.headers.cookie;
3388
+
3389
+ if (!isSameHost) delete opts.headers.host;
3390
+
3391
+ opts.auth = undefined;
3392
+ }
3393
+ }
3394
+
3395
+ //
3396
+ // Match curl 7.77.0 behavior and make the first `Authorization` header win.
3397
+ // If the `Authorization` header is set, then there is nothing to do as it
3398
+ // will take precedence.
3399
+ //
3400
+ if (opts.auth && !options.headers.authorization) {
3401
+ options.headers.authorization =
3402
+ 'Basic ' + Buffer.from(opts.auth).toString('base64');
3403
+ }
3404
+ }
3405
+
3131
3406
  let req = (websocket._req = get(opts));
3132
3407
 
3133
3408
  if (opts.timeout) {
@@ -3140,9 +3415,7 @@ function initAsClient(websocket, address, protocols, options) {
3140
3415
  if (req === null || req.aborted) return;
3141
3416
 
3142
3417
  req = websocket._req = null;
3143
- websocket._readyState = WebSocket$2.CLOSING;
3144
- websocket.emit('error', err);
3145
- websocket.emitClose();
3418
+ emitErrorAndClose(websocket, err);
3146
3419
  });
3147
3420
 
3148
3421
  req.on('response', (res) => {
@@ -3162,7 +3435,14 @@ function initAsClient(websocket, address, protocols, options) {
3162
3435
 
3163
3436
  req.abort();
3164
3437
 
3165
- const addr = new URL(location, address);
3438
+ let addr;
3439
+
3440
+ try {
3441
+ addr = new URL(location, address);
3442
+ } catch (err) {
3443
+ emitErrorAndClose(websocket, err);
3444
+ return;
3445
+ }
3166
3446
 
3167
3447
  initAsClient(websocket, addr, protocols, options);
3168
3448
  } else if (!websocket.emit('unexpected-response', req, res)) {
@@ -3185,6 +3465,13 @@ function initAsClient(websocket, address, protocols, options) {
3185
3465
 
3186
3466
  req = websocket._req = null;
3187
3467
 
3468
+ const upgrade = res.headers.upgrade;
3469
+
3470
+ if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {
3471
+ abortHandshake$1(websocket, socket, 'Invalid Upgrade header');
3472
+ return;
3473
+ }
3474
+
3188
3475
  const digest = createHash$1('sha1')
3189
3476
  .update(key + GUID$1)
3190
3477
  .digest('base64');
@@ -3213,22 +3500,50 @@ function initAsClient(websocket, address, protocols, options) {
3213
3500
 
3214
3501
  if (serverProt) websocket._protocol = serverProt;
3215
3502
 
3216
- if (perMessageDeflate) {
3503
+ const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
3504
+
3505
+ if (secWebSocketExtensions !== undefined) {
3506
+ if (!perMessageDeflate) {
3507
+ const message =
3508
+ 'Server sent a Sec-WebSocket-Extensions header but no extension ' +
3509
+ 'was requested';
3510
+ abortHandshake$1(websocket, socket, message);
3511
+ return;
3512
+ }
3513
+
3514
+ let extensions;
3515
+
3217
3516
  try {
3218
- const extensions = parse$1(res.headers['sec-websocket-extensions']);
3517
+ extensions = parse$1(secWebSocketExtensions);
3518
+ } catch (err) {
3519
+ const message = 'Invalid Sec-WebSocket-Extensions header';
3520
+ abortHandshake$1(websocket, socket, message);
3521
+ return;
3522
+ }
3523
+
3524
+ const extensionNames = Object.keys(extensions);
3219
3525
 
3220
- if (extensions[PerMessageDeflate$1.extensionName]) {
3526
+ if (extensionNames.length) {
3527
+ if (
3528
+ extensionNames.length !== 1 ||
3529
+ extensionNames[0] !== PerMessageDeflate$1.extensionName
3530
+ ) {
3531
+ const message =
3532
+ 'Server indicated an extension that was not requested';
3533
+ abortHandshake$1(websocket, socket, message);
3534
+ return;
3535
+ }
3536
+
3537
+ try {
3221
3538
  perMessageDeflate.accept(extensions[PerMessageDeflate$1.extensionName]);
3222
- websocket._extensions[PerMessageDeflate$1.extensionName] =
3223
- perMessageDeflate;
3539
+ } catch (err) {
3540
+ const message = 'Invalid Sec-WebSocket-Extensions header';
3541
+ abortHandshake$1(websocket, socket, message);
3542
+ return;
3224
3543
  }
3225
- } catch (err) {
3226
- abortHandshake$1(
3227
- websocket,
3228
- socket,
3229
- 'Invalid Sec-WebSocket-Extensions header'
3230
- );
3231
- return;
3544
+
3545
+ websocket._extensions[PerMessageDeflate$1.extensionName] =
3546
+ perMessageDeflate;
3232
3547
  }
3233
3548
  }
3234
3549
 
@@ -3236,6 +3551,19 @@ function initAsClient(websocket, address, protocols, options) {
3236
3551
  });
3237
3552
  }
3238
3553
 
3554
+ /**
3555
+ * Emit the `'error'` and `'close'` event.
3556
+ *
3557
+ * @param {WebSocket} websocket The WebSocket instance
3558
+ * @param {Error} The error to emit
3559
+ * @private
3560
+ */
3561
+ function emitErrorAndClose(websocket, err) {
3562
+ websocket._readyState = WebSocket$2.CLOSING;
3563
+ websocket.emit('error', err);
3564
+ websocket.emitClose();
3565
+ }
3566
+
3239
3567
  /**
3240
3568
  * Create a `net.Socket` and initiate a connection.
3241
3569
  *
@@ -3269,8 +3597,8 @@ function tlsConnect(options) {
3269
3597
  * Abort the handshake and emit an error.
3270
3598
  *
3271
3599
  * @param {WebSocket} websocket The WebSocket instance
3272
- * @param {(http.ClientRequest|net.Socket)} stream The request to abort or the
3273
- * socket to destroy
3600
+ * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to
3601
+ * abort or the socket to destroy
3274
3602
  * @param {String} message The error message
3275
3603
  * @private
3276
3604
  */
@@ -3343,13 +3671,15 @@ function sendAfterClose(websocket, data, cb) {
3343
3671
  function receiverOnConclude(code, reason) {
3344
3672
  const websocket = this[kWebSocket$1];
3345
3673
 
3346
- websocket._socket.removeListener('data', socketOnData);
3347
- websocket._socket.resume();
3348
-
3349
3674
  websocket._closeFrameReceived = true;
3350
3675
  websocket._closeMessage = reason;
3351
3676
  websocket._closeCode = code;
3352
3677
 
3678
+ if (websocket._socket[kWebSocket$1] === undefined) return;
3679
+
3680
+ websocket._socket.removeListener('data', socketOnData);
3681
+ process.nextTick(resume, websocket._socket);
3682
+
3353
3683
  if (code === 1005) websocket.close();
3354
3684
  else websocket.close(code, reason);
3355
3685
  }
@@ -3372,12 +3702,19 @@ function receiverOnDrain() {
3372
3702
  function receiverOnError(err) {
3373
3703
  const websocket = this[kWebSocket$1];
3374
3704
 
3375
- websocket._socket.removeListener('data', socketOnData);
3705
+ if (websocket._socket[kWebSocket$1] !== undefined) {
3706
+ websocket._socket.removeListener('data', socketOnData);
3707
+
3708
+ //
3709
+ // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
3710
+ // https://github.com/websockets/ws/issues/1940.
3711
+ //
3712
+ process.nextTick(resume, websocket._socket);
3713
+
3714
+ websocket.close(err[kStatusCode]);
3715
+ }
3376
3716
 
3377
- websocket._readyState = WebSocket$2.CLOSING;
3378
- websocket._closeCode = err[kStatusCode];
3379
3717
  websocket.emit('error', err);
3380
- websocket._socket.destroy();
3381
3718
  }
3382
3719
 
3383
3720
  /**
@@ -3422,6 +3759,16 @@ function receiverOnPong(data) {
3422
3759
  this[kWebSocket$1].emit('pong', data);
3423
3760
  }
3424
3761
 
3762
+ /**
3763
+ * Resume a readable stream
3764
+ *
3765
+ * @param {Readable} stream The readable stream
3766
+ * @private
3767
+ */
3768
+ function resume(stream) {
3769
+ stream.resume();
3770
+ }
3771
+
3425
3772
  /**
3426
3773
  * The listener of the `net.Socket` `'close'` event.
3427
3774
  *
@@ -3431,10 +3778,13 @@ function socketOnClose() {
3431
3778
  const websocket = this[kWebSocket$1];
3432
3779
 
3433
3780
  this.removeListener('close', socketOnClose);
3781
+ this.removeListener('data', socketOnData);
3434
3782
  this.removeListener('end', socketOnEnd);
3435
3783
 
3436
3784
  websocket._readyState = WebSocket$2.CLOSING;
3437
3785
 
3786
+ let chunk;
3787
+
3438
3788
  //
3439
3789
  // The close frame might not have been received or the `'end'` event emitted,
3440
3790
  // for example, if the socket was destroyed due to an error. Ensure that the
@@ -3442,13 +3792,19 @@ function socketOnClose() {
3442
3792
  // it. If the readable side of the socket is in flowing mode then there is no
3443
3793
  // buffered data as everything has been already written and `readable.read()`
3444
3794
  // will return `null`. If instead, the socket is paused, any possible buffered
3445
- // data will be read as a single chunk and emitted synchronously in a single
3446
- // `'data'` event.
3795
+ // data will be read as a single chunk.
3447
3796
  //
3448
- websocket._socket.read();
3797
+ if (
3798
+ !this._readableState.endEmitted &&
3799
+ !websocket._closeFrameReceived &&
3800
+ !websocket._receiver._writableState.errorEmitted &&
3801
+ (chunk = websocket._socket.read()) !== null
3802
+ ) {
3803
+ websocket._receiver.write(chunk);
3804
+ }
3805
+
3449
3806
  websocket._receiver.end();
3450
3807
 
3451
- this.removeListener('data', socketOnData);
3452
3808
  this[kWebSocket$1] = undefined;
3453
3809
 
3454
3810
  clearTimeout(websocket._closeTimer);
@@ -3511,7 +3867,7 @@ const { Duplex } = require$$0__default$2["default"];
3511
3867
  /**
3512
3868
  * Emits the `'close'` event on a stream.
3513
3869
  *
3514
- * @param {stream.Duplex} The stream.
3870
+ * @param {Duplex} stream The stream.
3515
3871
  * @private
3516
3872
  */
3517
3873
  function emitClose$1(stream) {
@@ -3549,11 +3905,12 @@ function duplexOnError(err) {
3549
3905
  *
3550
3906
  * @param {WebSocket} ws The `WebSocket` to wrap
3551
3907
  * @param {Object} [options] The options for the `Duplex` constructor
3552
- * @return {stream.Duplex} The duplex stream
3908
+ * @return {Duplex} The duplex stream
3553
3909
  * @public
3554
3910
  */
3555
3911
  function createWebSocketStream(ws, options) {
3556
3912
  let resumeOnReceiverDrain = true;
3913
+ let terminateOnDestroy = true;
3557
3914
 
3558
3915
  function receiverOnDrain() {
3559
3916
  if (resumeOnReceiverDrain) ws._socket.resume();
@@ -3587,6 +3944,16 @@ function createWebSocketStream(ws, options) {
3587
3944
  ws.once('error', function error(err) {
3588
3945
  if (duplex.destroyed) return;
3589
3946
 
3947
+ // Prevent `ws.terminate()` from being called by `duplex._destroy()`.
3948
+ //
3949
+ // - If the `'error'` event is emitted before the `'open'` event, then
3950
+ // `ws.terminate()` is a noop as no socket is assigned.
3951
+ // - Otherwise, the error is re-emitted by the listener of the `'error'`
3952
+ // event of the `Receiver` object. The listener already closes the
3953
+ // connection by calling `ws.close()`. This allows a close frame to be
3954
+ // sent to the other peer. If `ws.terminate()` is called right after this,
3955
+ // then the close frame might not be sent.
3956
+ terminateOnDestroy = false;
3590
3957
  duplex.destroy(err);
3591
3958
  });
3592
3959
 
@@ -3614,7 +3981,8 @@ function createWebSocketStream(ws, options) {
3614
3981
  if (!called) callback(err);
3615
3982
  process.nextTick(emitClose$1, duplex);
3616
3983
  });
3617
- ws.terminate();
3984
+
3985
+ if (terminateOnDestroy) ws.terminate();
3618
3986
  };
3619
3987
 
3620
3988
  duplex._final = function (callback) {
@@ -3646,7 +4014,10 @@ function createWebSocketStream(ws, options) {
3646
4014
  };
3647
4015
 
3648
4016
  duplex._read = function () {
3649
- if (ws.readyState === ws.OPEN && !resumeOnReceiverDrain) {
4017
+ if (
4018
+ (ws.readyState === ws.OPEN || ws.readyState === ws.CLOSING) &&
4019
+ !resumeOnReceiverDrain
4020
+ ) {
3650
4021
  resumeOnReceiverDrain = true;
3651
4022
  if (!ws._receiver._writableState.needDrain) ws._socket.resume();
3652
4023
  }
@@ -3670,9 +4041,11 @@ function createWebSocketStream(ws, options) {
3670
4041
 
3671
4042
  var stream = createWebSocketStream;
3672
4043
 
3673
- const EventEmitter = require$$0__default$4["default"];
3674
- const { createHash } = require$$0__default$3["default"];
3675
- const { createServer, STATUS_CODES } = require$$2__default$1["default"];
4044
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls|https$" }] */
4045
+
4046
+ const EventEmitter = require$$0__default$3["default"];
4047
+ const http = require$$2__default$1["default"];
4048
+ const { createHash } = require$$5__default["default"];
3676
4049
 
3677
4050
  const PerMessageDeflate = permessageDeflate;
3678
4051
  const WebSocket$1 = websocket;
@@ -3681,6 +4054,10 @@ const { GUID, kWebSocket } = constants;
3681
4054
 
3682
4055
  const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
3683
4056
 
4057
+ const RUNNING = 0;
4058
+ const CLOSING = 1;
4059
+ const CLOSED = 2;
4060
+
3684
4061
  /**
3685
4062
  * Class representing a WebSocket server.
3686
4063
  *
@@ -3704,7 +4081,8 @@ class WebSocketServer extends EventEmitter {
3704
4081
  * @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable
3705
4082
  * permessage-deflate
3706
4083
  * @param {Number} [options.port] The port where to bind the server
3707
- * @param {http.Server} [options.server] A pre-created HTTP/S server to use
4084
+ * @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
4085
+ * server to use
3708
4086
  * @param {Function} [options.verifyClient] A hook to reject connections
3709
4087
  * @param {Function} [callback] A listener for the `listening` event
3710
4088
  */
@@ -3726,15 +4104,20 @@ class WebSocketServer extends EventEmitter {
3726
4104
  ...options
3727
4105
  };
3728
4106
 
3729
- if (options.port == null && !options.server && !options.noServer) {
4107
+ if (
4108
+ (options.port == null && !options.server && !options.noServer) ||
4109
+ (options.port != null && (options.server || options.noServer)) ||
4110
+ (options.server && options.noServer)
4111
+ ) {
3730
4112
  throw new TypeError(
3731
- 'One of the "port", "server", or "noServer" options must be specified'
4113
+ 'One and only one of the "port", "server", or "noServer" options ' +
4114
+ 'must be specified'
3732
4115
  );
3733
4116
  }
3734
4117
 
3735
4118
  if (options.port != null) {
3736
- this._server = createServer((req, res) => {
3737
- const body = STATUS_CODES[426];
4119
+ this._server = http.createServer((req, res) => {
4120
+ const body = http.STATUS_CODES[426];
3738
4121
 
3739
4122
  res.writeHead(426, {
3740
4123
  'Content-Length': body.length,
@@ -3767,6 +4150,7 @@ class WebSocketServer extends EventEmitter {
3767
4150
  if (options.perMessageDeflate === true) options.perMessageDeflate = {};
3768
4151
  if (options.clientTracking) this.clients = new Set();
3769
4152
  this.options = options;
4153
+ this._state = RUNNING;
3770
4154
  }
3771
4155
 
3772
4156
  /**
@@ -3796,6 +4180,14 @@ class WebSocketServer extends EventEmitter {
3796
4180
  close(cb) {
3797
4181
  if (cb) this.once('close', cb);
3798
4182
 
4183
+ if (this._state === CLOSED) {
4184
+ process.nextTick(emitClose, this);
4185
+ return;
4186
+ }
4187
+
4188
+ if (this._state === CLOSING) return;
4189
+ this._state = CLOSING;
4190
+
3799
4191
  //
3800
4192
  // Terminate all associated clients.
3801
4193
  //
@@ -3813,7 +4205,7 @@ class WebSocketServer extends EventEmitter {
3813
4205
  // Close the http server if it was internally created.
3814
4206
  //
3815
4207
  if (this.options.port != null) {
3816
- server.close(() => this.emit('close'));
4208
+ server.close(emitClose.bind(undefined, this));
3817
4209
  return;
3818
4210
  }
3819
4211
  }
@@ -3843,7 +4235,8 @@ class WebSocketServer extends EventEmitter {
3843
4235
  * Handle a HTTP Upgrade request.
3844
4236
  *
3845
4237
  * @param {http.IncomingMessage} req The request object
3846
- * @param {net.Socket} socket The network socket between the server and client
4238
+ * @param {(net.Socket|tls.Socket)} socket The network socket between the
4239
+ * server and client
3847
4240
  * @param {Buffer} head The first packet of the upgraded stream
3848
4241
  * @param {Function} cb Callback
3849
4242
  * @public
@@ -3855,12 +4248,14 @@ class WebSocketServer extends EventEmitter {
3855
4248
  req.headers['sec-websocket-key'] !== undefined
3856
4249
  ? req.headers['sec-websocket-key'].trim()
3857
4250
  : false;
4251
+ const upgrade = req.headers.upgrade;
3858
4252
  const version = +req.headers['sec-websocket-version'];
3859
4253
  const extensions = {};
3860
4254
 
3861
4255
  if (
3862
4256
  req.method !== 'GET' ||
3863
- req.headers.upgrade.toLowerCase() !== 'websocket' ||
4257
+ upgrade === undefined ||
4258
+ upgrade.toLowerCase() !== 'websocket' ||
3864
4259
  !key ||
3865
4260
  !keyRegex.test(key) ||
3866
4261
  (version !== 8 && version !== 13) ||
@@ -3922,7 +4317,8 @@ class WebSocketServer extends EventEmitter {
3922
4317
  * @param {String} key The value of the `Sec-WebSocket-Key` header
3923
4318
  * @param {Object} extensions The accepted extensions
3924
4319
  * @param {http.IncomingMessage} req The request object
3925
- * @param {net.Socket} socket The network socket between the server and client
4320
+ * @param {(net.Socket|tls.Socket)} socket The network socket between the
4321
+ * server and client
3926
4322
  * @param {Buffer} head The first packet of the upgraded stream
3927
4323
  * @param {Function} cb Callback
3928
4324
  * @throws {Error} If called more than once with the same socket
@@ -3941,6 +4337,8 @@ class WebSocketServer extends EventEmitter {
3941
4337
  );
3942
4338
  }
3943
4339
 
4340
+ if (this._state > RUNNING) return abortHandshake(socket, 503);
4341
+
3944
4342
  const digest = createHash('sha1')
3945
4343
  .update(key + GUID)
3946
4344
  .digest('base64');
@@ -4030,6 +4428,7 @@ function addListeners(server, map) {
4030
4428
  * @private
4031
4429
  */
4032
4430
  function emitClose(server) {
4431
+ server._state = CLOSED;
4033
4432
  server.emit('close');
4034
4433
  }
4035
4434
 
@@ -4045,7 +4444,7 @@ function socketOnError() {
4045
4444
  /**
4046
4445
  * Close the connection when preconditions are not fulfilled.
4047
4446
  *
4048
- * @param {net.Socket} socket The socket of the upgrade request
4447
+ * @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
4049
4448
  * @param {Number} code The HTTP response status code
4050
4449
  * @param {String} [message] The HTTP response body
4051
4450
  * @param {Object} [headers] Additional HTTP response headers
@@ -4053,7 +4452,7 @@ function socketOnError() {
4053
4452
  */
4054
4453
  function abortHandshake(socket, code, message, headers) {
4055
4454
  if (socket.writable) {
4056
- message = message || STATUS_CODES[code];
4455
+ message = message || http.STATUS_CODES[code];
4057
4456
  headers = {
4058
4457
  Connection: 'close',
4059
4458
  'Content-Type': 'text/html',
@@ -4062,7 +4461,7 @@ function abortHandshake(socket, code, message, headers) {
4062
4461
  };
4063
4462
 
4064
4463
  socket.write(
4065
- `HTTP/1.1 ${code} ${STATUS_CODES[code]}\r\n` +
4464
+ `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
4066
4465
  Object.keys(headers)
4067
4466
  .map((h) => `${h}: ${headers[h]}`)
4068
4467
  .join('\r\n') +
@@ -5491,6 +5890,16 @@ function options (optionsOptions, argv) {
5491
5890
  });
5492
5891
  }
5493
5892
 
5893
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
5894
+ console.log(`Usage: fisk-monitor [options]
5895
+
5896
+ Options:
5897
+ --scheduler=URL Scheduler URL (default: ws://localhost:8097)
5898
+
5899
+ Config files: ~/.config/fisk/monitor.conf, /etc/xdg/fisk/monitor.conf
5900
+ Environment variables: FISK_MONITOR_SCHEDULER, etc.`);
5901
+ process.exit(0);
5902
+ }
5494
5903
  const option = options({
5495
5904
  prefix: "fisk/monitor",
5496
5905
  noApplicationPath: true,
@@ -5740,9 +6149,10 @@ clientBox.on("select", (ev) => {
5740
6149
  if (jobKey === "total") {
5741
6150
  continue;
5742
6151
  }
5743
- widest[0] = Math.max(jobValue.sourceFile.length + 1, widest[0]);
6152
+ const sourceBasename = path__default["default"].basename(jobValue.sourcePath || jobValue.sourceFile || "");
6153
+ widest[0] = Math.max(sourceBasename.length + 1, widest[0]);
5744
6154
  data.push([
5745
- jobValue.sourceFile,
6155
+ sourceBasename,
5746
6156
  jobValue.builder.ip + ":" + jobValue.builder.port,
5747
6157
  humanizeDuration(now - jobValue.time)
5748
6158
  ]);
@@ -5929,6 +6339,7 @@ function formatCell(str, num, prefix, suffix) {
5929
6339
  return (prefix || "") + (" " + str).padEnd(num, " ").substr(0, num) + (suffix || "");
5930
6340
  }
5931
6341
  let updateTimer;
6342
+ let refreshTimer;
5932
6343
  let timeout = 0;
5933
6344
  function updateBuilderBox() {
5934
6345
  const builderWidth = builderContainer.width - 3;
@@ -6009,11 +6420,14 @@ function updateBuilderBox() {
6009
6420
  if (currentFocus !== builderBox) {
6010
6421
  builderBox.scrollTo(0);
6011
6422
  }
6423
+ if (refreshTimer) {
6424
+ clearTimeout(refreshTimer);
6425
+ }
6012
6426
  if (newest < 60000) {
6013
- setTimeout(update, 1000);
6427
+ refreshTimer = setTimeout(update, 1000);
6014
6428
  }
6015
6429
  else {
6016
- setTimeout(update, 60000);
6430
+ refreshTimer = setTimeout(update, 60000);
6017
6431
  }
6018
6432
  }
6019
6433
  function updateClientBox() {