@rebasepro/client 0.25.0 → 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 +99 -36
- package/dist/index.es.js.map +1 -1
- package/dist/websocket.d.ts +8 -0
- package/package.json +4 -4
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
|
-
|
|
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)
|
|
2627
|
+
if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
|
|
2579
2628
|
});
|
|
2580
2629
|
}).catch((e) => {
|
|
2581
|
-
if (this.ws)
|
|
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
|
-
|
|
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
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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++)
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
throw error;
|
|
3075
|
-
|
|
3076
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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;
|