@rebasepro/client 0.25.0-canary.ge06257e → 0.25.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.es.js CHANGED
@@ -2150,6 +2150,37 @@ function realtimeLostError() {
2150
2150
  function realtimeDebugOn() {
2151
2151
  return Reflect.get(globalThis, "__REBASE_DEBUG_REALTIME__") === true;
2152
2152
  }
2153
+ /**
2154
+ * Connection chatter: the socket opened, signed in, closed, is redialling.
2155
+ *
2156
+ * `console.debug` where that is a debug level — a browser's console hides it
2157
+ * unless Verbose is asked for. Not in Node, where `console.debug` is
2158
+ * `console.log`: a script's `listen()` opened its stdout with "Connected to
2159
+ * PostgreSQL backend" and "WebSocket auto-authenticated", ahead of what the
2160
+ * script prints. There it is written only while realtime debugging is on.
2161
+ */
2162
+ function connectionDebug(...args) {
2163
+ if (!(typeof window !== "undefined" || typeof document !== "undefined") && !realtimeDebugOn()) return;
2164
+ console.debug(...args);
2165
+ }
2166
+ /**
2167
+ * The codes an `AUTHENTICATE` is refused with when the server will not honour
2168
+ * the session: the token does not verify or has expired, the session was
2169
+ * revoked or its account is gone (`SESSION_REVOKED`), the account is disabled
2170
+ * (`ACCOUNT_DISABLED`) — the codes the HTTP routes answer the same token with.
2171
+ * An answer, not a failure: asking again with the same token gets the same one.
2172
+ */
2173
+ var SESSION_REFUSAL_CODES = /* @__PURE__ */ new Set([
2174
+ "INVALID_TOKEN",
2175
+ "TOKEN_EXPIRED",
2176
+ "JWT_EXPIRED",
2177
+ "SESSION_REVOKED",
2178
+ "ACCOUNT_DISABLED",
2179
+ "SESSION_ENDED"
2180
+ ]);
2181
+ function isSessionRefusal(error) {
2182
+ return error instanceof RebaseApiError$1 && typeof error.code === "string" && SESSION_REFUSAL_CODES.has(error.code);
2183
+ }
2153
2184
  /** The ceiling of the reconnect backoff. */
2154
2185
  var MAX_RECONNECT_DELAY_MS = 3e4;
2155
2186
  /**
@@ -2495,7 +2526,7 @@ var RebaseWebSocketClient = class {
2495
2526
  if (this.closedByCaller) return;
2496
2527
  if (this.connectionState !== "reconnecting" && this.connectionState !== "disconnected") return;
2497
2528
  if (this.ws) return;
2498
- console.debug(`${reason} — retrying the realtime connection`);
2529
+ connectionDebug(`${reason} — retrying the realtime connection`);
2499
2530
  if (this.reconnectTimeout) {
2500
2531
  clearTimeout(this.reconnectTimeout);
2501
2532
  this.reconnectTimeout = null;
@@ -2527,6 +2558,24 @@ var RebaseWebSocketClient = class {
2527
2558
  }
2528
2559
  onlineListener = null;
2529
2560
  visibilityListener = null;
2561
+ /** The session this socket's server refused, and why. Cleared when a socket opens. */
2562
+ refusedSession = null;
2563
+ /**
2564
+ * Whether `error` is the server refusing the session `token` signs in as —
2565
+ * revoked, its account gone or disabled, a token that does not verify. Said
2566
+ * once per socket and token, as a warning that names the code.
2567
+ */
2568
+ noteSessionRefused(token, error) {
2569
+ if (!isSessionRefusal(error)) return false;
2570
+ if (this.refusedSession?.token !== token) {
2571
+ this.refusedSession = {
2572
+ token,
2573
+ error
2574
+ };
2575
+ console.warn(`[Rebase] Realtime: the server refused this session (${error.code}: ${error.message}). The socket is connected but not signed in, and what needs an account is refused with that code.`);
2576
+ }
2577
+ return true;
2578
+ }
2530
2579
  /**
2531
2580
  * Authenticate the WebSocket connection
2532
2581
  */
@@ -2575,10 +2624,10 @@ var RebaseWebSocketClient = class {
2575
2624
  if (this.isConnected && !this.isAuthenticated && !this.authPromise) this.getAuthToken().then((token) => {
2576
2625
  if (!this.ws) return;
2577
2626
  if (token) this.authenticate(token).catch((e) => {
2578
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
2627
+ if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
2579
2628
  });
2580
2629
  }).catch((e) => {
2581
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
2630
+ if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
2582
2631
  });
2583
2632
  }
2584
2633
  /**
@@ -2594,6 +2643,7 @@ var RebaseWebSocketClient = class {
2594
2643
  if (permanent) this.removeNetworkListeners();
2595
2644
  this.isAuthenticated = false;
2596
2645
  this.authPromise = null;
2646
+ this.refusedSession = null;
2597
2647
  if (this.reconnectTimeout) {
2598
2648
  clearTimeout(this.reconnectTimeout);
2599
2649
  this.reconnectTimeout = null;
@@ -2628,7 +2678,7 @@ var RebaseWebSocketClient = class {
2628
2678
  const socket = new this.WebSocketConstructor(withOptIns(this.websocketUrl));
2629
2679
  this.ws = socket;
2630
2680
  this.ws.onopen = async () => {
2631
- console.debug("Connected to PostgreSQL backend");
2681
+ connectionDebug("Connected to PostgreSQL backend");
2632
2682
  const wasReconnect = this.hadConnection;
2633
2683
  this.hadConnection = true;
2634
2684
  this.isConnected = true;
@@ -2637,14 +2687,18 @@ var RebaseWebSocketClient = class {
2637
2687
  this.outageReported = false;
2638
2688
  this.gaveUp = false;
2639
2689
  this.setState("connected");
2640
- if (this.getAuthToken && !this.isAuthenticated) try {
2641
- const token = await this.getAuthToken();
2642
- if (token) {
2643
- await this.authenticate(token);
2644
- console.debug("WebSocket auto-authenticated");
2690
+ this.refusedSession = null;
2691
+ if (this.getAuthToken && !this.isAuthenticated) {
2692
+ let token = null;
2693
+ try {
2694
+ token = await this.getAuthToken();
2695
+ if (token) {
2696
+ await this.authenticate(token);
2697
+ connectionDebug("WebSocket auto-authenticated");
2698
+ }
2699
+ } catch (error) {
2700
+ if (!(token && this.noteSessionRefused(token, error))) connectionDebug("WebSocket connected, not signed in yet:", error?.message || error);
2645
2701
  }
2646
- } catch (error) {
2647
- console.debug("WebSocket connected without auth:", error?.message || error);
2648
2702
  }
2649
2703
  this.emit(wasReconnect ? "reconnect" : "connect");
2650
2704
  this.processMessageQueue(wasReconnect);
@@ -2661,7 +2715,7 @@ var RebaseWebSocketClient = class {
2661
2715
  }
2662
2716
  };
2663
2717
  const onClosed = () => {
2664
- console.debug("Disconnected from PostgreSQL backend");
2718
+ connectionDebug("Disconnected from PostgreSQL backend");
2665
2719
  this.stopLivenessWatch();
2666
2720
  this.forgetServerOptIns();
2667
2721
  if (this.ws === socket) this.ws = null;
@@ -2791,7 +2845,7 @@ var RebaseWebSocketClient = class {
2791
2845
  this.reconnectAttempts = Math.min(this.reconnectAttempts + 1, 30);
2792
2846
  const ceiling = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_DELAY_MS);
2793
2847
  const delay = MIN_RECONNECT_DELAY_MS + Math.floor(Math.random() * (ceiling - MIN_RECONNECT_DELAY_MS));
2794
- console.debug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
2848
+ connectionDebug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
2795
2849
  if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
2796
2850
  this.reconnectTimeout = setTimeout(() => {
2797
2851
  this.reconnectTimeout = null;
@@ -3060,31 +3114,36 @@ var RebaseWebSocketClient = class {
3060
3114
  }
3061
3115
  async runAuthentication(retryCount) {
3062
3116
  let lastError = null;
3063
- for (let attempt = 0; attempt < retryCount; attempt++) try {
3064
- const token = await this.getAuthToken();
3065
- if (!token) throw new Error("user not logged in");
3066
- await this.authenticate(token);
3067
- console.debug("WebSocket authenticated on demand");
3068
- return;
3069
- } catch (error) {
3070
- lastError = error;
3071
- const errMsg = error instanceof Error ? error.message : String(error);
3072
- if (errMsg.includes("not logged in") || errMsg.includes("Session expired")) {
3073
- console.warn("WebSocket auth failed: user not logged in");
3074
- throw error;
3075
- }
3076
- if (errMsg.includes("still loading")) {
3117
+ for (let attempt = 0; attempt < retryCount; attempt++) {
3118
+ let token = null;
3119
+ try {
3120
+ token = await this.getAuthToken();
3121
+ if (!token) throw new Error("user not logged in");
3122
+ if (this.refusedSession?.token === token) throw this.refusedSession.error;
3123
+ await this.authenticate(token);
3124
+ connectionDebug("WebSocket authenticated on demand");
3125
+ return;
3126
+ } catch (error) {
3127
+ lastError = error;
3128
+ if (token && this.noteSessionRefused(token, error)) throw error;
3129
+ const errMsg = error instanceof Error ? error.message : String(error);
3130
+ if (errMsg.includes("not logged in") || errMsg.includes("Session expired")) {
3131
+ console.warn("WebSocket auth failed: user not logged in");
3132
+ throw error;
3133
+ }
3134
+ if (errMsg.includes("still loading")) {
3135
+ if (attempt < retryCount - 1) {
3136
+ const delay = Math.min(500 * (attempt + 1), 2e3);
3137
+ await new Promise((resolve) => setTimeout(resolve, delay));
3138
+ continue;
3139
+ }
3140
+ }
3077
3141
  if (attempt < retryCount - 1) {
3078
- const delay = Math.min(500 * (attempt + 1), 2e3);
3142
+ const delay = Math.min(1e3 * (attempt + 1), 3e3);
3143
+ connectionDebug(`WebSocket auth attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
3079
3144
  await new Promise((resolve) => setTimeout(resolve, delay));
3080
- continue;
3081
3145
  }
3082
3146
  }
3083
- if (attempt < retryCount - 1) {
3084
- const delay = Math.min(1e3 * (attempt + 1), 3e3);
3085
- console.debug(`WebSocket auth attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
3086
- await new Promise((resolve) => setTimeout(resolve, delay));
3087
- }
3088
3147
  }
3089
3148
  console.warn("WebSocket on-demand auth failed after retries:", lastError);
3090
3149
  throw lastError;
@@ -3096,7 +3155,7 @@ var RebaseWebSocketClient = class {
3096
3155
  const token = await this.getAuthToken();
3097
3156
  if (!token) throw new Error("user not logged in");
3098
3157
  await this.authenticate(token);
3099
- console.debug("WebSocket reauthenticated successfully");
3158
+ connectionDebug("WebSocket reauthenticated successfully");
3100
3159
  } catch (error) {
3101
3160
  console.error("WebSocket reauthentication failed:", error);
3102
3161
  throw error;
@@ -3170,6 +3229,10 @@ var RebaseWebSocketClient = class {
3170
3229
  await this.ensureAuthenticated();
3171
3230
  } catch (error) {
3172
3231
  if (!(isSubscriptionFrame && isNotSignedIn(error))) {
3232
+ if (error instanceof RebaseApiError$1) {
3233
+ reject(error);
3234
+ return;
3235
+ }
3173
3236
  reject(new RebaseApiError$1(error instanceof Error ? error.message : "Authentication required"));
3174
3237
  return;
3175
3238
  }
@@ -3782,7 +3845,7 @@ var RebaseWebSocketClient = class {
3782
3845
  * we need to re-register everything to resume receiving updates.
3783
3846
  */
3784
3847
  resubscribeAll() {
3785
- console.debug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.singleSubscriptions.size} row(ies)`);
3848
+ connectionDebug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.singleSubscriptions.size} row(ies)`);
3786
3849
  for (const [key, sub] of this.collectionSubscriptions.entries()) {
3787
3850
  if (sub.subscribeInFlight) continue;
3788
3851
  const oldBackendId = sub.backendSubscriptionId;