@iam4x/reconnecting-websocket 1.3.0 → 1.4.1

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.
package/dist/index.d.ts CHANGED
@@ -34,6 +34,7 @@ export declare class ReconnectingWebSocket {
34
34
  constructor(url: string, options?: ReconnectOptions);
35
35
  connect(): void;
36
36
  emit(event: EventType, payload: any): void;
37
+ private runWithFinalizer;
37
38
  scheduleReconnect(): void;
38
39
  startHealthCheck(): void;
39
40
  stopHealthCheck(): void;
package/dist/index.js CHANGED
@@ -82,18 +82,21 @@ export class ReconnectingWebSocket {
82
82
  this.openFn = (event) => {
83
83
  // Only process if event is from the current socket
84
84
  if (event.target === currentWs && this.ws === currentWs) {
85
+ const isReconnect = this.wasConnected;
85
86
  this.clearTimers();
86
87
  this.retryCount = 0;
87
- this.emit("open", event);
88
- // Emit reconnect event if this was a reconnection after a disconnection
89
- if (this.wasConnected) {
90
- this.emit("reconnect", event);
91
- }
92
88
  this.wasConnected = true;
93
89
  this.startHealthCheck();
94
90
  this.startInactivityTimer();
95
- // Flush any queued messages now that socket is open
96
- this.flushMessageQueue();
91
+ this.runWithFinalizer(() => {
92
+ this.emit("open", event);
93
+ if (isReconnect) {
94
+ this.emit("reconnect", event);
95
+ }
96
+ }, () => {
97
+ // Flush queued messages even if a listener throws during open/reconnect.
98
+ this.flushMessageQueue();
99
+ });
97
100
  }
98
101
  };
99
102
  this.msgFn = (event) => {
@@ -106,12 +109,16 @@ export class ReconnectingWebSocket {
106
109
  this.closeFn = (event) => {
107
110
  // Only process if event is from the current socket
108
111
  if (event.target === currentWs && this.ws === currentWs) {
112
+ const shouldReconnect = !this.forcedClose;
109
113
  this.stopHealthCheck();
110
114
  this.stopInactivityTimer();
111
- this.emit("close", { code: event.code, reason: event.reason });
112
- if (!this.forcedClose) {
113
- this.scheduleReconnect();
114
- }
115
+ this.runWithFinalizer(() => {
116
+ this.emit("close", { code: event.code, reason: event.reason });
117
+ }, () => {
118
+ if (shouldReconnect) {
119
+ this.scheduleReconnect();
120
+ }
121
+ });
115
122
  }
116
123
  };
117
124
  this.errorFn = (event) => {
@@ -131,6 +138,33 @@ export class ReconnectingWebSocket {
131
138
  listener(payload);
132
139
  }
133
140
  }
141
+ runWithFinalizer(action, finalizer) {
142
+ let didThrow = false;
143
+ let thrown;
144
+ try {
145
+ action();
146
+ }
147
+ catch (error) {
148
+ didThrow = true;
149
+ thrown = error;
150
+ }
151
+ finally {
152
+ if (finalizer) {
153
+ try {
154
+ finalizer();
155
+ }
156
+ catch (error) {
157
+ if (!didThrow) {
158
+ didThrow = true;
159
+ thrown = error;
160
+ }
161
+ }
162
+ }
163
+ }
164
+ if (didThrow) {
165
+ throw thrown;
166
+ }
167
+ }
134
168
  scheduleReconnect() {
135
169
  // Clear any existing reconnect timeout first to prevent multiple reconnects
136
170
  if (this.reconnectTimeout) {
@@ -180,9 +214,34 @@ export class ReconnectingWebSocket {
180
214
  if (this.forcedClose) {
181
215
  return;
182
216
  }
183
- // Trigger reconnection due to inactivity
217
+ const shouldReconnect = !this.forcedClose;
218
+ // Proactively trigger reconnection due to inactivity
219
+ // Don't rely on the close event as it may never fire on a stalled connection
184
220
  if (this.ws) {
221
+ // Stop health check to prevent it from also triggering reconnection
222
+ this.stopHealthCheck();
223
+ // Remove event listeners to prevent any late events from interfering
224
+ if (this.openFn)
225
+ this.ws.removeEventListener("open", this.openFn);
226
+ if (this.msgFn)
227
+ this.ws.removeEventListener("message", this.msgFn);
228
+ if (this.closeFn)
229
+ this.ws.removeEventListener("close", this.closeFn);
230
+ if (this.errorFn)
231
+ this.ws.removeEventListener("error", this.errorFn);
232
+ // Try to close the socket (may hang on stalled connections, but we don't wait)
185
233
  this.ws.close();
234
+ // Clear the socket reference
235
+ this.ws = undefined;
236
+ this.runWithFinalizer(() => {
237
+ // Emit close event to listeners with a special code indicating inactivity timeout.
238
+ this.emit("close", { code: 4000, reason: "Inactivity timeout" });
239
+ }, () => {
240
+ if (shouldReconnect) {
241
+ // Schedule reconnection directly without waiting for close event.
242
+ this.scheduleReconnect();
243
+ }
244
+ });
186
245
  }
187
246
  }, this.options.watchingInactivityTimeout);
188
247
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "module": "src/index.ts",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "version": "1.3.0",
6
+ "version": "1.4.1",
7
7
  "private": false,
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {