polfan-server-js-client 0.4.3 → 0.4.4

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.
@@ -4,8 +4,14 @@
4
4
  <option name="autoReloadType" value="SELECTIVE" />
5
5
  </component>
6
6
  <component name="ChangeListManager">
7
- <list default="true" id="831dae43-0da1-47fd-a5f7-33dd5eec2992" name="Changes" comment="Bump version&#10;&#10;This reverts commit b9ff2004">
8
- <change beforePath="$PROJECT_DIR$/src/types" beforeDir="false" afterPath="$PROJECT_DIR$/src/types" afterDir="false" />
7
+ <list default="true" id="831dae43-0da1-47fd-a5f7-33dd5eec2992" name="Changes" comment="Bump version">
8
+ <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
9
+ <change beforePath="$PROJECT_DIR$/build/index.cjs.js" beforeDir="false" afterPath="$PROJECT_DIR$/build/index.cjs.js" afterDir="false" />
10
+ <change beforePath="$PROJECT_DIR$/build/index.cjs.js.map" beforeDir="false" afterPath="$PROJECT_DIR$/build/index.cjs.js.map" afterDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/build/index.umd.js" beforeDir="false" afterPath="$PROJECT_DIR$/build/index.umd.js" afterDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/build/index.umd.js.map" beforeDir="false" afterPath="$PROJECT_DIR$/build/index.umd.js.map" afterDir="false" />
13
+ <change beforePath="$PROJECT_DIR$/build/types/WebSocketChatClient.d.ts" beforeDir="false" afterPath="$PROJECT_DIR$/build/types/WebSocketChatClient.d.ts" afterDir="false" />
14
+ <change beforePath="$PROJECT_DIR$/src/WebSocketChatClient.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/WebSocketChatClient.ts" afterDir="false" />
9
15
  </list>
10
16
  <option name="SHOW_DIALOG" value="false" />
11
17
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -33,7 +39,7 @@
33
39
  <entry key="$PROJECT_DIR$/src/types" value="claude/chat-reactions-polls-system-hwea5i" />
34
40
  </map>
35
41
  </option>
36
- <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/src/types" />
42
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
37
43
  <option name="RESET_MODE" value="HARD" />
38
44
  <option name="ROOT_SYNC" value="DONT_SYNC" />
39
45
  </component>
@@ -85,7 +91,7 @@
85
91
  "RunOnceActivity.typescript.service.memoryLimit.init": "true",
86
92
  "codeWithMe.voiceChat.enabledByDefault": "false",
87
93
  "com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
88
- "git-widget-placeholder": "main",
94
+ "git-widget-placeholder": "master",
89
95
  "git.auto.fetch.suggestion.counter": "3",
90
96
  "ignore.virus.scanning.warn.message": "true",
91
97
  "junie.onboarding.icon.badge.shown": "true",
@@ -258,15 +264,7 @@
258
264
  <workItem from="1787648458595" duration="3034000" />
259
265
  <workItem from="1787994100244" duration="588000" />
260
266
  <workItem from="1788873818076" duration="1523000" />
261
- <workItem from="1788879390791" duration="635000" />
262
- </task>
263
- <task id="LOCAL-00068" summary="Improve reconnect on timeout">
264
- <option name="closed" value="true" />
265
- <created>1765145773196</created>
266
- <option name="number" value="00068" />
267
- <option name="presentableId" value="LOCAL-00068" />
268
- <option name="project" value="LOCAL" />
269
- <updated>1765145773196</updated>
267
+ <workItem from="1788879390791" duration="2740000" />
270
268
  </task>
271
269
  <task id="LOCAL-00069" summary="Bump version">
272
270
  <option name="closed" value="true" />
@@ -652,7 +650,15 @@
652
650
  <option name="project" value="LOCAL" />
653
651
  <updated>1787649696666</updated>
654
652
  </task>
655
- <option name="localTasksCounter" value="117" />
653
+ <task id="LOCAL-00117" summary="Bump version">
654
+ <option name="closed" value="true" />
655
+ <created>1788952429127</created>
656
+ <option name="number" value="00117" />
657
+ <option name="presentableId" value="LOCAL-00117" />
658
+ <option name="project" value="LOCAL" />
659
+ <updated>1788952429128</updated>
660
+ </task>
661
+ <option name="localTasksCounter" value="118" />
656
662
  <servers />
657
663
  </component>
658
664
  <component name="TypeScriptGeneratedFilesManager">
@@ -744,7 +750,6 @@
744
750
  <MESSAGE value="Fixes in WebApiChatClient" />
745
751
  <MESSAGE value="Fix resolve window status after jump to message" />
746
752
  <MESSAGE value="Fix jumpTo when window is in LIVE state" />
747
- <MESSAGE value="Bump version" />
748
753
  <MESSAGE value="Unread topic counter in summary, bump version" />
749
754
  <MESSAGE value="Unread room counter in summary, bump version" />
750
755
  <MESSAGE value="Reconcile tracked state on reconnect" />
@@ -757,7 +762,8 @@
757
762
  <MESSAGE value="Revert &quot;User presence status support&quot;&#10;&#10;This reverts commit b9ff2004" />
758
763
  <MESSAGE value="Bump version&#10;&#10;This reverts commit b9ff2004" />
759
764
  <MESSAGE value="Role flags and the MentionAllRoles permission" />
760
- <option name="LAST_COMMIT_MESSAGE" value="Role flags and the MentionAllRoles permission" />
765
+ <MESSAGE value="Bump version" />
766
+ <option name="LAST_COMMIT_MESSAGE" value="Bump version" />
761
767
  </component>
762
768
  <component name="XSLT-Support.FileAssociations.UIState">
763
769
  <expand />
package/README.md CHANGED
@@ -24,6 +24,26 @@ TypeScript client library to handle connection with Devana (new Polfan) chat ser
24
24
  })();
25
25
  ```
26
26
 
27
+ ## Connection maintenance
28
+
29
+ `WebSocketChatClient` keeps the connection alive: after any loss (error, connecting timeout, missing pong, closure with
30
+ a code other than 1000) it reconnects indefinitely, with an exponential backoff between attempts. The `disconnect` event
31
+ tells whether a retry follows. A pending `connect()` promise resolves once an attempt authenticates, and is rejected by
32
+ `disconnect()`, which also stops reconnecting. An invalid token (`Bye` with `AuthenticationException`) rejects `connect()`
33
+ with the `Bye` data, emits an `error` event and stops reconnecting as well.
34
+
35
+ ```js
36
+ const wsClient = new PServ.WebSocketChatClient({
37
+ token: 'your-access-token',
38
+ url: 'wss://pserv-websocket-address',
39
+ connectingTimeoutMs: 10000, // Per attempt (default)
40
+ reconnect: {
41
+ minDelayMs: 1000, // Delay before the first retry, doubled on every next one (default)
42
+ maxDelayMs: 30000, // Maximum delay between retries (default)
43
+ },
44
+ });
45
+ ```
46
+
27
47
  ## State management
28
48
 
29
49
  The library provides built-in state management for WebSocket client. To enable it, just pass `stateTracking`
@@ -5439,6 +5439,9 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5439
5439
  WebSocketChatClient_defineProperty(_this, "pingMonitorInterval", void 0);
5440
5440
  WebSocketChatClient_defineProperty(_this, "inFlightPingTimeout", void 0);
5441
5441
  WebSocketChatClient_defineProperty(_this, "lastReceivedMessageAt", void 0);
5442
+ WebSocketChatClient_defineProperty(_this, "reconnectEnabled", false);
5443
+ WebSocketChatClient_defineProperty(_this, "reconnectTimeoutId", void 0);
5444
+ WebSocketChatClient_defineProperty(_this, "reconnectAttempts", 0);
5442
5445
  _this.options = options;
5443
5446
  if ((_this$options$stateTr = _this.options.stateTracking) !== null && _this$options$stateTr !== void 0 ? _this$options$stateTr : true) {
5444
5447
  _this.state = new ChatStateTracker(_this);
@@ -5454,14 +5457,14 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5454
5457
  key: "connect",
5455
5458
  value: function () {
5456
5459
  var _connect = WebSocketChatClient_asyncToGenerator(/*#__PURE__*/WebSocketChatClient_regenerator().m(function _callee() {
5457
- var _this$connectPromise2,
5458
- _this2 = this,
5459
- _this$options$queryPa,
5460
- _this$options$connect;
5461
- var _this$connectPromise, params;
5460
+ var _this2 = this;
5461
+ var _this$connectPromise, connectPromise;
5462
5462
  return WebSocketChatClient_regenerator().w(function (_context) {
5463
5463
  while (1) switch (_context.n) {
5464
5464
  case 0:
5465
+ this.reconnectEnabled = true;
5466
+ // A manual call does not wait for a scheduled retry.
5467
+ this.cancelScheduledReconnect();
5465
5468
  if (!(this.isOpenWsState() || this.isConnectingWsState())) {
5466
5469
  _context.n = 1;
5467
5470
  break;
@@ -5471,26 +5474,23 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5471
5474
  // Reuse the promise of an attempt that has not settled yet (an
5472
5475
  // automatic reconnect), so the caller that started connecting is
5473
5476
  // resolved by whichever attempt eventually authenticates.
5474
- (_this$connectPromise2 = this.connectPromise) !== null && _this$connectPromise2 !== void 0 ? _this$connectPromise2 : this.connectPromise = new Promise(function () {
5475
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
5476
- args[_key] = arguments[_key];
5477
- }
5478
- return _this2.authenticatedResolvers = args;
5479
- });
5480
- params = new URLSearchParams((_this$options$queryPa = this.options.queryParams) !== null && _this$options$queryPa !== void 0 ? _this$options$queryPa : {});
5481
- params.set('token', this.options.token);
5482
- this.ws = new WebSocket("".concat(this.options.url, "?").concat(params));
5483
- this.ws.onclose = function (ev) {
5484
- return _this2.onClose(ev);
5485
- };
5486
- this.ws.onmessage = function (ev) {
5487
- return _this2.onMessage(ev);
5488
- };
5489
- this.connectingTimeoutId = setTimeout(function () {
5490
- return _this2.triggerConnectionTimeout();
5491
- }, (_this$options$connect = this.options.connectingTimeoutMs) !== null && _this$options$connect !== void 0 ? _this$options$connect : 10000);
5492
- this.authenticated = false;
5493
- return _context.a(2, this.connectPromise);
5477
+ if (!this.connectPromise) {
5478
+ this.connectPromise = new Promise(function () {
5479
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
5480
+ args[_key] = arguments[_key];
5481
+ }
5482
+ return _this2.authenticatedResolvers = args;
5483
+ });
5484
+ // Automatic reconnects create this promise with no one awaiting it,
5485
+ // so its rejection must not surface as an unhandled one. Callers
5486
+ // awaiting it still receive the rejection.
5487
+ this.connectPromise["catch"](function () {
5488
+ return undefined;
5489
+ });
5490
+ }
5491
+ connectPromise = this.connectPromise;
5492
+ this.openSocket();
5493
+ return _context.a(2, connectPromise);
5494
5494
  }
5495
5495
  }, _callee, this);
5496
5496
  }));
@@ -5502,10 +5502,16 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5502
5502
  }, {
5503
5503
  key: "disconnect",
5504
5504
  value: function disconnect() {
5505
- var _this$ws;
5505
+ var wasActive = this.ws !== null || this.reconnectTimeoutId !== undefined;
5506
+ this.reconnectEnabled = false;
5507
+ this.reconnectAttempts = 0;
5508
+ this.cancelScheduledReconnect();
5509
+ this.releaseSocket(1000); // Normal closure
5506
5510
  this.failPendingCommands(new Error('Client disconnected before the command was answered'));
5507
- (_this$ws = this.ws) === null || _this$ws === void 0 || _this$ws.close(1000); // Normal closure
5508
- this.ws = null;
5511
+ this.settleConnect(new Error('Client disconnected before authentication'));
5512
+ if (wasActive) {
5513
+ this.emit(this.Event.disconnect, false);
5514
+ }
5509
5515
  }
5510
5516
  }, {
5511
5517
  key: "send",
@@ -5539,15 +5545,52 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5539
5545
  get: function get() {
5540
5546
  return this.isOpenWsState() && this.authenticated;
5541
5547
  }
5548
+ }, {
5549
+ key: "openSocket",
5550
+ value: function openSocket() {
5551
+ var _this$options$queryPa,
5552
+ _this3 = this,
5553
+ _this$options$connect;
5554
+ // Never leave a previous socket attached (e.g. one still closing).
5555
+ this.releaseSocket(1000);
5556
+ var params = new URLSearchParams((_this$options$queryPa = this.options.queryParams) !== null && _this$options$queryPa !== void 0 ? _this$options$queryPa : {});
5557
+ params.set('token', this.options.token);
5558
+ var ws;
5559
+ try {
5560
+ ws = new WebSocket("".concat(this.options.url, "?").concat(params));
5561
+ } catch (error) {
5562
+ // Invalid URL - no retry can fix that.
5563
+ this.settleConnect(error);
5564
+ return;
5565
+ }
5566
+
5567
+ // Events of an abandoned socket must not touch the current connection.
5568
+ ws.onmessage = function (ev) {
5569
+ return ws === _this3.ws && _this3.onMessage(ev);
5570
+ };
5571
+ ws.onclose = function (ev) {
5572
+ return ws === _this3.ws && _this3.onClose(ev);
5573
+ };
5574
+ // Not every implementation follows a failed handshake with a close
5575
+ // event (e.g. Node.js), so an error alone means the connection is lost.
5576
+ ws.onerror = function () {
5577
+ return ws === _this3.ws && _this3.handleConnectionLoss(true);
5578
+ };
5579
+ this.ws = ws;
5580
+ this.authenticated = false;
5581
+ this.connectingTimeoutId = setTimeout(function () {
5582
+ return _this3.triggerConnectionTimeout();
5583
+ }, (_this$options$connect = this.options.connectingTimeoutMs) !== null && _this$options$connect !== void 0 ? _this$options$connect : 10000);
5584
+ }
5542
5585
  }, {
5543
5586
  key: "sendEnvelope",
5544
5587
  value: function sendEnvelope(envelope) {
5545
- var _this$ws$readyState, _this$ws2;
5588
+ var _this$ws$readyState, _this$ws;
5546
5589
  if (this.isReady) {
5547
5590
  this.ws.send(JSON.stringify(envelope));
5548
5591
  return;
5549
5592
  }
5550
- this.handleEnvelopeSendError(envelope, new Error("Cannot send - client is not ready (state=".concat((_this$ws$readyState = (_this$ws2 = this.ws) === null || _this$ws2 === void 0 ? void 0 : _this$ws2.readyState) !== null && _this$ws$readyState !== void 0 ? _this$ws$readyState : '[no connection]', "; authenticated=").concat(this.authenticated, ")")));
5593
+ this.handleEnvelopeSendError(envelope, new Error("Cannot send - client is not ready (state=".concat((_this$ws$readyState = (_this$ws = this.ws) === null || _this$ws === void 0 ? void 0 : _this$ws.readyState) !== null && _this$ws$readyState !== void 0 ? _this$ws$readyState : '[no connection]', "; authenticated=").concat(this.authenticated, ")")));
5551
5594
  }
5552
5595
  }, {
5553
5596
  key: "onMessage",
@@ -5563,35 +5606,110 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5563
5606
  var isAuthenticated = envelope.type !== 'Bye';
5564
5607
  this.authenticated = isAuthenticated;
5565
5608
  if (isAuthenticated) {
5609
+ this.reconnectAttempts = 0;
5566
5610
  this.startConnectionMonitor();
5567
5611
  this.settleConnect();
5568
5612
  this.emit(this.Event.connect);
5569
5613
  this.sendFromQueue();
5570
5614
  } else {
5615
+ var _envelope$data;
5571
5616
  this.settleConnect(envelope.data);
5617
+ var error = (_envelope$data = envelope.data) === null || _envelope$data === void 0 || (_envelope$data = _envelope$data.reason) === null || _envelope$data === void 0 ? void 0 : _envelope$data.error;
5618
+ if ((error === null || error === void 0 ? void 0 : error.code) === 'AuthenticationException') {
5619
+ // Invalid token - retrying would be rejected the same way.
5620
+ this.handleConnectionLoss(false);
5621
+ this.emit(this.Event.error, new Error("Authentication rejected: ".concat(error.message)));
5622
+ }
5572
5623
  }
5573
5624
  }
5574
5625
  }
5575
5626
  }, {
5576
5627
  key: "onClose",
5577
5628
  value: function onClose(event) {
5578
- this.stopConnectionMonitor();
5579
- clearTimeout(this.connectingTimeoutId);
5580
- var reconnect = event.code !== 1000; // Connection was closed because of error
5629
+ // Connection was closed because of error
5630
+ this.handleConnectionLoss(event.code !== 1000);
5631
+ }
5632
+
5633
+ /**
5634
+ * Abandon the current socket without waiting for its close event (which
5635
+ * may never come, or take minutes on a dead TCP connection), settle
5636
+ * everything that depended on it and schedule a retry when requested.
5637
+ */
5638
+ }, {
5639
+ key: "handleConnectionLoss",
5640
+ value: function handleConnectionLoss(reconnect) {
5641
+ this.releaseSocket(reconnect ? 3000 : 1000);
5581
5642
 
5582
5643
  // The server can no longer answer anything that was queued or in
5583
5644
  // flight, so settle those promises instead of leaving them pending.
5584
5645
  this.failPendingCommands(new Error('Connection closed before the command was answered'));
5646
+ reconnect && (reconnect = this.reconnectEnabled);
5585
5647
  if (reconnect) {
5586
5648
  // Keep a pending connect() promise unsettled - the retry below is
5587
5649
  // expected to authenticate and will resolve it.
5588
- void this.connect();
5650
+ this.scheduleReconnect();
5589
5651
  } else {
5652
+ this.reconnectEnabled = false;
5590
5653
  this.settleConnect(new Error('Connection closed before authentication'));
5591
5654
  }
5592
5655
  this.emit(this.Event.disconnect, reconnect);
5593
5656
  }
5594
5657
 
5658
+ /**
5659
+ * Detach the current socket from the client and close it if it is still
5660
+ * alive, together with all timers bound to it.
5661
+ */
5662
+ }, {
5663
+ key: "releaseSocket",
5664
+ value: function releaseSocket(closeCode) {
5665
+ this.stopConnectionMonitor();
5666
+ clearTimeout(this.connectingTimeoutId);
5667
+ this.connectingTimeoutId = undefined;
5668
+ this.authenticated = false;
5669
+ var ws = this.ws;
5670
+ this.ws = null;
5671
+ if (!ws) {
5672
+ return;
5673
+ }
5674
+ ws.onmessage = ws.onclose = ws.onerror = null;
5675
+ if (ws.readyState === ws.CONNECTING || ws.readyState === ws.OPEN) {
5676
+ try {
5677
+ ws.close(closeCode);
5678
+ } catch (_unused) {
5679
+ // Nothing more can be done with a broken socket.
5680
+ }
5681
+ }
5682
+ }
5683
+ }, {
5684
+ key: "scheduleReconnect",
5685
+ value: function scheduleReconnect() {
5686
+ var _this$options$reconne,
5687
+ _this$options$reconne2,
5688
+ _this$options$reconne3,
5689
+ _this$options$reconne4,
5690
+ _this4 = this;
5691
+ this.cancelScheduledReconnect();
5692
+ var minDelay = Math.max(0, (_this$options$reconne = (_this$options$reconne2 = this.options.reconnect) === null || _this$options$reconne2 === void 0 ? void 0 : _this$options$reconne2.minDelayMs) !== null && _this$options$reconne !== void 0 ? _this$options$reconne : 1000);
5693
+ var maxDelay = Math.max(minDelay, (_this$options$reconne3 = (_this$options$reconne4 = this.options.reconnect) === null || _this$options$reconne4 === void 0 ? void 0 : _this$options$reconne4.maxDelayMs) !== null && _this$options$reconne3 !== void 0 ? _this$options$reconne3 : 30000);
5694
+ var delay = Math.min(maxDelay, minDelay * Math.pow(2, Math.min(this.reconnectAttempts, 30)));
5695
+ this.reconnectAttempts++;
5696
+
5697
+ // Random jitter (50-100% of the delay) keeps clients from reconnecting
5698
+ // in lockstep after a server restart.
5699
+ this.reconnectTimeoutId = setTimeout(function () {
5700
+ _this4.reconnectTimeoutId = undefined;
5701
+ _this4.connect()["catch"](function () {
5702
+ return undefined;
5703
+ });
5704
+ }, delay / 2 + Math.random() * delay / 2);
5705
+ }
5706
+ }, {
5707
+ key: "cancelScheduledReconnect",
5708
+ value: function cancelScheduledReconnect() {
5709
+ clearTimeout(this.reconnectTimeoutId);
5710
+ this.reconnectTimeoutId = undefined;
5711
+ }
5712
+
5595
5713
  /**
5596
5714
  * Resolve (or reject, when an error is given) a pending connect() promise.
5597
5715
  * No-op when there is nothing pending.
@@ -5634,16 +5752,16 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5634
5752
  }, {
5635
5753
  key: "sendFromQueue",
5636
5754
  value: function sendFromQueue() {
5637
- var _this3 = this;
5755
+ var _this5 = this;
5638
5756
  // Send awaiting data to server
5639
5757
  var lastDelay = 0;
5640
5758
  var _loop = function _loop() {
5641
- var _this3$options$awaitQ;
5642
- var envelope = _this3.sendQueue[dataIndex];
5759
+ var _this5$options$awaitQ;
5760
+ var envelope = _this5.sendQueue[dataIndex];
5643
5761
  setTimeout(function () {
5644
- return _this3.sendEnvelope(envelope);
5762
+ return _this5.sendEnvelope(envelope);
5645
5763
  }, lastDelay);
5646
- lastDelay += (_this3$options$awaitQ = _this3.options.awaitQueueSendDelayMs) !== null && _this3$options$awaitQ !== void 0 ? _this3$options$awaitQ : 500;
5764
+ lastDelay += (_this5$options$awaitQ = _this5.options.awaitQueueSendDelayMs) !== null && _this5$options$awaitQ !== void 0 ? _this5$options$awaitQ : 500;
5647
5765
  };
5648
5766
  for (var dataIndex in this.sendQueue) {
5649
5767
  _loop();
@@ -5654,7 +5772,7 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5654
5772
  }, {
5655
5773
  key: "triggerConnectionTimeout",
5656
5774
  value: function triggerConnectionTimeout() {
5657
- this.disconnect();
5775
+ this.handleConnectionLoss(true);
5658
5776
  this.emit(this.Event.error, new Error('Connection timeout'));
5659
5777
  }
5660
5778
  }, {
@@ -5670,7 +5788,8 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5670
5788
  }, {
5671
5789
  key: "startConnectionMonitor",
5672
5790
  value: function startConnectionMonitor() {
5673
- var _this4 = this;
5791
+ var _this6 = this;
5792
+ this.stopConnectionMonitor();
5674
5793
  if (!this.options.ping.enabled) {
5675
5794
  return;
5676
5795
  }
@@ -5679,30 +5798,32 @@ var WebSocketChatClient = /*#__PURE__*/function (_AbstractChatClient) {
5679
5798
  return WebSocketChatClient_regenerator().w(function (_context3) {
5680
5799
  while (1) switch (_context3.n) {
5681
5800
  case 0:
5682
- if (!(!_this4.isReady || _this4.inFlightPingTimeout)) {
5801
+ if (!(!_this6.isReady || _this6.inFlightPingTimeout)) {
5683
5802
  _context3.n = 1;
5684
5803
  break;
5685
5804
  }
5686
5805
  return _context3.a(2);
5687
5806
  case 1:
5688
- if (!(Date.now() - _this4.lastReceivedMessageAt < _this4.options.ping.noActivityTimeoutMs)) {
5807
+ if (!(Date.now() - _this6.lastReceivedMessageAt < _this6.options.ping.noActivityTimeoutMs)) {
5689
5808
  _context3.n = 2;
5690
5809
  break;
5691
5810
  }
5692
5811
  return _context3.a(2);
5693
5812
  case 2:
5694
- _this4.inFlightPingTimeout = setTimeout(function () {
5695
- _this4.inFlightPingTimeout = undefined;
5696
- _this4.ws.close(3000); // Service Restart (reconnect)
5697
- }, _this4.options.ping.pongBackTimeoutMs);
5813
+ _this6.inFlightPingTimeout = setTimeout(function () {
5814
+ _this6.inFlightPingTimeout = undefined;
5815
+ // Closing a dead connection can hang in CLOSING for minutes,
5816
+ // so drop it right away instead of waiting for the close event.
5817
+ _this6.handleConnectionLoss(true);
5818
+ }, _this6.options.ping.pongBackTimeoutMs);
5698
5819
 
5699
5820
  // A rejection here means the connection dropped while the ping was
5700
- // in flight; onClose already handles that, so just stop waiting.
5701
- _this4.send('Ping', {})["catch"](function () {
5821
+ // in flight; the loss is already handled, so just stop waiting.
5822
+ _this6.send('Ping', {})["catch"](function () {
5702
5823
  return undefined;
5703
5824
  }).then(function () {
5704
- clearTimeout(_this4.inFlightPingTimeout);
5705
- _this4.inFlightPingTimeout = undefined;
5825
+ clearTimeout(_this6.inFlightPingTimeout);
5826
+ _this6.inFlightPingTimeout = undefined;
5706
5827
  });
5707
5828
  case 3:
5708
5829
  return _context3.a(2);