@neta-art/cohub 8.10.1 → 8.10.2
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/chunks/websocket.d.ts +3 -0
- package/dist/chunks/websocket.js +97 -26
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ type WebsocketClientOptions = {
|
|
|
18
18
|
autoReconnect?: boolean;
|
|
19
19
|
reconnectBaseDelayMs?: number;
|
|
20
20
|
reconnectMaxDelayMs?: number;
|
|
21
|
+
connectTimeoutMs?: number;
|
|
21
22
|
pingIntervalMs?: number;
|
|
22
23
|
pongTimeoutMs?: number;
|
|
23
24
|
debug?: boolean;
|
|
@@ -99,6 +100,7 @@ declare class WebsocketClient {
|
|
|
99
100
|
private readonly autoReconnect;
|
|
100
101
|
private readonly reconnectBaseDelayMs;
|
|
101
102
|
private readonly reconnectMaxDelayMs;
|
|
103
|
+
private readonly connectTimeoutMs;
|
|
102
104
|
private readonly pingIntervalMs;
|
|
103
105
|
private readonly pongTimeoutMs;
|
|
104
106
|
private readonly debug;
|
|
@@ -114,6 +116,7 @@ declare class WebsocketClient {
|
|
|
114
116
|
private retryAuthClose;
|
|
115
117
|
private manuallyClosed;
|
|
116
118
|
private connectPromise;
|
|
119
|
+
private pendingConnectReject;
|
|
117
120
|
private authWaiter;
|
|
118
121
|
private awaitingPong;
|
|
119
122
|
private lastPingRequestId;
|
package/dist/chunks/websocket.js
CHANGED
|
@@ -61,11 +61,16 @@ const normalizeOptions = (options = {}) => ({
|
|
|
61
61
|
autoReconnect: options.autoReconnect !== false,
|
|
62
62
|
reconnectBaseDelayMs: options.reconnectBaseDelayMs ?? 1e3,
|
|
63
63
|
reconnectMaxDelayMs: options.reconnectMaxDelayMs ?? 15e3,
|
|
64
|
+
connectTimeoutMs: normalizeConnectTimeoutMs(options.connectTimeoutMs ?? 15e3),
|
|
64
65
|
pingIntervalMs: options.pingIntervalMs ?? 2e4,
|
|
65
66
|
pongTimeoutMs: options.pongTimeoutMs ?? 15e3,
|
|
66
67
|
debug: options.debug === true
|
|
67
68
|
});
|
|
68
69
|
const formatCloseMessage = (code, reason) => `WebSocket closed: ${code ?? 0} ${reason || ""}`.trim();
|
|
70
|
+
const normalizeConnectTimeoutMs = (value) => {
|
|
71
|
+
if (!Number.isInteger(value) || value < 1 || value > 2147483647) throw new Error("connectTimeoutMs must be an integer between 1 and 2147483647");
|
|
72
|
+
return value;
|
|
73
|
+
};
|
|
69
74
|
const AUTH_CLOSE_CODE = 4003;
|
|
70
75
|
const AUTH_CLOSE_REASON = "authentication failed";
|
|
71
76
|
const isRetryableCloseCode = (code) => {
|
|
@@ -135,6 +140,7 @@ var WebsocketClient = class {
|
|
|
135
140
|
autoReconnect;
|
|
136
141
|
reconnectBaseDelayMs;
|
|
137
142
|
reconnectMaxDelayMs;
|
|
143
|
+
connectTimeoutMs;
|
|
138
144
|
pingIntervalMs;
|
|
139
145
|
pongTimeoutMs;
|
|
140
146
|
debug;
|
|
@@ -150,6 +156,7 @@ var WebsocketClient = class {
|
|
|
150
156
|
retryAuthClose = false;
|
|
151
157
|
manuallyClosed = false;
|
|
152
158
|
connectPromise = null;
|
|
159
|
+
pendingConnectReject = null;
|
|
153
160
|
authWaiter = null;
|
|
154
161
|
awaitingPong = false;
|
|
155
162
|
lastPingRequestId = null;
|
|
@@ -170,6 +177,7 @@ var WebsocketClient = class {
|
|
|
170
177
|
this.autoReconnect = normalized.autoReconnect;
|
|
171
178
|
this.reconnectBaseDelayMs = normalized.reconnectBaseDelayMs;
|
|
172
179
|
this.reconnectMaxDelayMs = normalized.reconnectMaxDelayMs;
|
|
180
|
+
this.connectTimeoutMs = normalized.connectTimeoutMs;
|
|
173
181
|
this.pingIntervalMs = normalized.pingIntervalMs;
|
|
174
182
|
this.pongTimeoutMs = normalized.pongTimeoutMs;
|
|
175
183
|
this.debug = normalized.debug;
|
|
@@ -204,19 +212,70 @@ var WebsocketClient = class {
|
|
|
204
212
|
const ws = new this.WebSocketImpl(this.url);
|
|
205
213
|
this.ws = ws;
|
|
206
214
|
let settled = false;
|
|
215
|
+
let closed = false;
|
|
216
|
+
let connectTimer = null;
|
|
217
|
+
const clearConnectTimer = () => {
|
|
218
|
+
if (connectTimer === null) return;
|
|
219
|
+
clearTimeout(connectTimer);
|
|
220
|
+
connectTimer = null;
|
|
221
|
+
};
|
|
207
222
|
const rejectOnce = (error) => {
|
|
208
223
|
if (settled) return;
|
|
224
|
+
clearConnectTimer();
|
|
209
225
|
settled = true;
|
|
210
226
|
this.connectPromise = null;
|
|
227
|
+
if (this.pendingConnectReject === rejectOnce) this.pendingConnectReject = null;
|
|
211
228
|
reject(error);
|
|
212
229
|
};
|
|
213
230
|
const resolveOnce = () => {
|
|
214
231
|
if (settled) return;
|
|
232
|
+
clearConnectTimer();
|
|
215
233
|
settled = true;
|
|
216
234
|
this.connectPromise = null;
|
|
235
|
+
if (this.pendingConnectReject === rejectOnce) this.pendingConnectReject = null;
|
|
217
236
|
resolve();
|
|
218
237
|
};
|
|
238
|
+
const isCurrentConnection = () => this.ws === ws && !closed;
|
|
239
|
+
const cleanupSocket = () => {
|
|
240
|
+
ws.onopen = null;
|
|
241
|
+
ws.onmessage = null;
|
|
242
|
+
ws.onerror = null;
|
|
243
|
+
ws.onclose = null;
|
|
244
|
+
};
|
|
245
|
+
const handleClose = (event) => {
|
|
246
|
+
if (closed || this.ws !== ws) return;
|
|
247
|
+
closed = true;
|
|
248
|
+
clearConnectTimer();
|
|
249
|
+
this.stopPingLoop();
|
|
250
|
+
const wasConnecting = this.state === "connecting" || this.state === "reconnecting";
|
|
251
|
+
this.state = "closed";
|
|
252
|
+
this.ws = null;
|
|
253
|
+
this.compactStreamContexts.clear();
|
|
254
|
+
this.patchStreamBuffers.clear();
|
|
255
|
+
this.resetRoomSubscriptions();
|
|
256
|
+
const closeError = new Error(formatCloseMessage(event.code, event.reason));
|
|
257
|
+
this.rejectAuthWaiter(closeError);
|
|
258
|
+
const retryAuthClose = event.code === AUTH_CLOSE_CODE && this.retryAuthClose;
|
|
259
|
+
this.retryAuthClose = false;
|
|
260
|
+
const willReconnect = !this.manuallyClosed && this.autoReconnect && (retryAuthClose || isRetryableCloseCode(event.code));
|
|
261
|
+
this.log("closed", {
|
|
262
|
+
code: event.code,
|
|
263
|
+
reason: event.reason,
|
|
264
|
+
willReconnect,
|
|
265
|
+
wasConnecting
|
|
266
|
+
});
|
|
267
|
+
this.emit("close", {
|
|
268
|
+
code: event.code,
|
|
269
|
+
reason: event.reason,
|
|
270
|
+
willReconnect
|
|
271
|
+
});
|
|
272
|
+
cleanupSocket();
|
|
273
|
+
if (wasConnecting) rejectOnce(closeError);
|
|
274
|
+
if (willReconnect) this.scheduleReconnect(event.code, event.reason);
|
|
275
|
+
};
|
|
219
276
|
ws.onopen = async () => {
|
|
277
|
+
if (!isCurrentConnection()) return;
|
|
278
|
+
clearConnectTimer();
|
|
220
279
|
try {
|
|
221
280
|
this.log("connected", {
|
|
222
281
|
url: this.url,
|
|
@@ -225,12 +284,14 @@ var WebsocketClient = class {
|
|
|
225
284
|
});
|
|
226
285
|
this.startPingLoop();
|
|
227
286
|
await this.authenticate();
|
|
287
|
+
if (!isCurrentConnection()) return;
|
|
228
288
|
this.state = "open";
|
|
229
289
|
this.restoreRoomSubscriptions();
|
|
230
290
|
this.reconnectAttempt = 0;
|
|
231
291
|
this.emit("open", { connectionId: this.connectionId });
|
|
232
292
|
resolveOnce();
|
|
233
293
|
} catch (error) {
|
|
294
|
+
if (!isCurrentConnection()) return;
|
|
234
295
|
const authError = error instanceof Error ? error : /* @__PURE__ */ new Error("authentication failed");
|
|
235
296
|
const recoverable = Boolean(this.getAccessToken && this.autoReconnect && !this.manuallyClosed && !this.authReconnectAttempted);
|
|
236
297
|
this.retryAuthClose = recoverable;
|
|
@@ -247,41 +308,37 @@ var WebsocketClient = class {
|
|
|
247
308
|
}
|
|
248
309
|
};
|
|
249
310
|
ws.onmessage = (event) => {
|
|
311
|
+
if (!isCurrentConnection()) return;
|
|
250
312
|
this.handleMessage(event.data);
|
|
251
313
|
};
|
|
252
314
|
ws.onerror = (error) => {
|
|
315
|
+
if (!isCurrentConnection()) return;
|
|
253
316
|
this.emit("error", {
|
|
254
317
|
error,
|
|
255
318
|
recoverable: !this.manuallyClosed
|
|
256
319
|
});
|
|
257
320
|
};
|
|
258
321
|
ws.onclose = (event) => {
|
|
259
|
-
|
|
260
|
-
const wasConnecting = this.state === "connecting" || this.state === "reconnecting";
|
|
261
|
-
this.state = "closed";
|
|
262
|
-
this.ws = null;
|
|
263
|
-
this.compactStreamContexts.clear();
|
|
264
|
-
this.patchStreamBuffers.clear();
|
|
265
|
-
this.resetRoomSubscriptions();
|
|
266
|
-
const closeError = new Error(formatCloseMessage(event.code, event.reason));
|
|
267
|
-
this.rejectAuthWaiter(closeError);
|
|
268
|
-
const retryAuthClose = event.code === AUTH_CLOSE_CODE && this.retryAuthClose;
|
|
269
|
-
this.retryAuthClose = false;
|
|
270
|
-
const willReconnect = !this.manuallyClosed && this.autoReconnect && (retryAuthClose || isRetryableCloseCode(event.code));
|
|
271
|
-
this.log("closed", {
|
|
272
|
-
code: event.code,
|
|
273
|
-
reason: event.reason,
|
|
274
|
-
willReconnect,
|
|
275
|
-
wasConnecting
|
|
276
|
-
});
|
|
277
|
-
this.emit("close", {
|
|
278
|
-
code: event.code,
|
|
279
|
-
reason: event.reason,
|
|
280
|
-
willReconnect
|
|
281
|
-
});
|
|
282
|
-
if (wasConnecting) rejectOnce(closeError);
|
|
283
|
-
if (willReconnect) this.scheduleReconnect(event.code, event.reason);
|
|
322
|
+
handleClose(event);
|
|
284
323
|
};
|
|
324
|
+
this.pendingConnectReject = rejectOnce;
|
|
325
|
+
connectTimer = setTimeout(() => {
|
|
326
|
+
if (!isCurrentConnection() || settled) return;
|
|
327
|
+
const reason = "connect timeout";
|
|
328
|
+
const error = /* @__PURE__ */ new Error(`WebSocket ${reason} after ${this.connectTimeoutMs}ms`);
|
|
329
|
+
this.emit("error", {
|
|
330
|
+
error,
|
|
331
|
+
recoverable: !this.manuallyClosed && this.autoReconnect
|
|
332
|
+
});
|
|
333
|
+
try {
|
|
334
|
+
ws.close(4002, reason);
|
|
335
|
+
} finally {
|
|
336
|
+
handleClose({
|
|
337
|
+
code: 4002,
|
|
338
|
+
reason
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}, this.connectTimeoutMs);
|
|
285
342
|
});
|
|
286
343
|
return this.connectPromise;
|
|
287
344
|
}
|
|
@@ -291,9 +348,23 @@ var WebsocketClient = class {
|
|
|
291
348
|
this.stopPingLoop();
|
|
292
349
|
this.state = "closed";
|
|
293
350
|
this.rejectAuthWaiter(/* @__PURE__ */ new Error("disconnected"));
|
|
294
|
-
this.ws
|
|
351
|
+
const ws = this.ws;
|
|
295
352
|
this.ws = null;
|
|
353
|
+
ws?.close(code, reason);
|
|
354
|
+
this.pendingConnectReject?.(/* @__PURE__ */ new Error(`WebSocket disconnected: ${reason}`));
|
|
355
|
+
this.pendingConnectReject = null;
|
|
296
356
|
this.connectPromise = null;
|
|
357
|
+
if (ws) {
|
|
358
|
+
ws.onopen = null;
|
|
359
|
+
ws.onmessage = null;
|
|
360
|
+
ws.onerror = null;
|
|
361
|
+
ws.onclose = null;
|
|
362
|
+
this.emit("close", {
|
|
363
|
+
code,
|
|
364
|
+
reason,
|
|
365
|
+
willReconnect: false
|
|
366
|
+
});
|
|
367
|
+
}
|
|
297
368
|
this.authReconnectAttempted = false;
|
|
298
369
|
this.forceRefreshOnNextAuth = false;
|
|
299
370
|
this.retryAuthClose = false;
|