@neta-art/cohub 2.14.0 → 2.14.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.
@@ -2411,7 +2411,9 @@ type WebsocketClientOptions = {
2411
2411
  pingIntervalMs?: number;
2412
2412
  pongTimeoutMs?: number;
2413
2413
  debug?: boolean;
2414
- getAccessToken?: () => Promise<string | null> | string | null;
2414
+ getAccessToken?: (options?: {
2415
+ forceRefresh?: boolean;
2416
+ }) => Promise<string | null> | string | null;
2415
2417
  WebSocketImpl?: WebSocketConstructor;
2416
2418
  };
2417
2419
  type WebsocketClientState = "idle" | "connecting" | "reconnecting" | "open" | "closed";
@@ -2496,6 +2498,9 @@ declare class WebsocketClient {
2496
2498
  private reconnectTimer;
2497
2499
  private reconnectTimerResolver;
2498
2500
  private reconnectAttempt;
2501
+ private authReconnectAttempted;
2502
+ private forceRefreshOnNextAuth;
2503
+ private retryAuthClose;
2499
2504
  private manuallyClosed;
2500
2505
  private connectPromise;
2501
2506
  private authWaiter;
@@ -66,12 +66,13 @@ const normalizeOptions = (options = {}) => ({
66
66
  debug: options.debug === true
67
67
  });
68
68
  const formatCloseMessage = (code, reason) => `WebSocket closed: ${code ?? 0} ${reason || ""}`.trim();
69
+ const AUTH_CLOSE_CODE = 4003;
70
+ const AUTH_CLOSE_REASON = "authentication failed";
69
71
  const isRetryableCloseCode = (code) => {
70
72
  if (code === 1e3) return false;
71
- if (code === 4003) return false;
73
+ if (code === AUTH_CLOSE_CODE) return false;
72
74
  return true;
73
75
  };
74
- const AUTH_CLOSE_REASON = "authentication failed";
75
76
  const PATCH_STREAM_BUFFER_MAX_PENDING = 128;
76
77
  const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
77
78
  const isRealtimeCompactFrame = (value) => {
@@ -144,6 +145,9 @@ var WebsocketClient = class {
144
145
  reconnectTimer = null;
145
146
  reconnectTimerResolver = null;
146
147
  reconnectAttempt = 0;
148
+ authReconnectAttempted = false;
149
+ forceRefreshOnNextAuth = false;
150
+ retryAuthClose = false;
147
151
  manuallyClosed = false;
148
152
  connectPromise = null;
149
153
  authWaiter = null;
@@ -223,12 +227,18 @@ var WebsocketClient = class {
223
227
  resolveOnce();
224
228
  } catch (error) {
225
229
  const authError = error instanceof Error ? error : /* @__PURE__ */ new Error("authentication failed");
230
+ const recoverable = Boolean(this.getAccessToken && this.autoReconnect && !this.manuallyClosed && !this.authReconnectAttempted);
231
+ this.retryAuthClose = recoverable;
232
+ if (recoverable) {
233
+ this.authReconnectAttempted = true;
234
+ this.forceRefreshOnNextAuth = true;
235
+ }
226
236
  this.emit("error", {
227
237
  error: authError,
228
- recoverable: false
238
+ recoverable
229
239
  });
230
240
  rejectOnce(authError);
231
- ws.close(4003, AUTH_CLOSE_REASON);
241
+ ws.close(AUTH_CLOSE_CODE, AUTH_CLOSE_REASON);
232
242
  }
233
243
  };
234
244
  ws.onmessage = (event) => {
@@ -249,7 +259,9 @@ var WebsocketClient = class {
249
259
  this.patchStreamBuffers.clear();
250
260
  const closeError = new Error(formatCloseMessage(event.code, event.reason));
251
261
  this.rejectAuthWaiter(closeError);
252
- const willReconnect = !this.manuallyClosed && this.autoReconnect && isRetryableCloseCode(event.code);
262
+ const retryAuthClose = event.code === AUTH_CLOSE_CODE && this.retryAuthClose;
263
+ this.retryAuthClose = false;
264
+ const willReconnect = !this.manuallyClosed && this.autoReconnect && (retryAuthClose || isRetryableCloseCode(event.code));
253
265
  this.log("closed", {
254
266
  code: event.code,
255
267
  reason: event.reason,
@@ -276,6 +288,9 @@ var WebsocketClient = class {
276
288
  this.ws?.close(code, reason);
277
289
  this.ws = null;
278
290
  this.connectPromise = null;
291
+ this.authReconnectAttempted = false;
292
+ this.forceRefreshOnNextAuth = false;
293
+ this.retryAuthClose = false;
279
294
  this.compactStreamContexts.clear();
280
295
  this.patchStreamBuffers.clear();
281
296
  for (const state of this.roomSubscriptions.values()) {
@@ -399,7 +414,9 @@ var WebsocketClient = class {
399
414
  ws.send(JSON.stringify(event));
400
415
  }
401
416
  async authenticate() {
402
- const token = this.getAccessToken ? await this.getAccessToken() : null;
417
+ const forceRefresh = this.forceRefreshOnNextAuth;
418
+ this.forceRefreshOnNextAuth = false;
419
+ const token = this.getAccessToken ? await this.getAccessToken(forceRefresh ? { forceRefresh: true } : void 0) : null;
403
420
  if (!token) throw new WebsocketAuthError("missing access token");
404
421
  const waiter = this.createAuthWaiter();
405
422
  this.send({
@@ -411,6 +428,7 @@ var WebsocketClient = class {
411
428
  });
412
429
  await waiter.promise;
413
430
  await this.restoreRoomSubscriptions();
431
+ this.authReconnectAttempted = false;
414
432
  }
415
433
  flushRoomSubscriptions() {
416
434
  if (this.ws?.readyState !== WebSocket.OPEN) return;
@@ -808,7 +826,7 @@ var WebsocketClient = class {
808
826
  await this.connect().catch((error) => {
809
827
  this.emit("error", {
810
828
  error,
811
- recoverable: true
829
+ recoverable: this.state === "reconnecting"
812
830
  });
813
831
  });
814
832
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub",
3
- "version": "2.14.0",
3
+ "version": "2.14.1",
4
4
  "description": "Cohub SDK for spaces, sessions, checkpoints, and realtime agent collaboration.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,