@rebasepro/client 0.9.1-canary.97e305f → 0.9.1-canary.a639738
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 +2 -0
- package/dist/index.es.js +308 -24
- package/dist/index.es.js.map +1 -1
- package/dist/realtime-channel.d.ts +89 -0
- package/dist/websocket.d.ts +39 -2
- package/package.json +7 -5
- package/src/index.ts +47 -2
- package/src/realtime-channel.test.ts +285 -0
- package/src/realtime-channel.ts +238 -0
- package/src/realtime-optout.test.ts +128 -2
- package/src/websocket.ts +146 -18
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export type { CreateBackupsOptions } from "./backups";
|
|
|
27
27
|
export type { ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, CreateApiKeysOptions, UpdateApiKeyRequest } from "./api-keys";
|
|
28
28
|
export type { FunctionInvokeOptions, FunctionsClient } from "./functions";
|
|
29
29
|
export { RebaseWebSocketClient } from "./websocket";
|
|
30
|
+
export { RebaseRealtimeChannel } from "./realtime-channel";
|
|
31
|
+
export type { PresenceState, PresenceDiff, BroadcastEvent, ChannelTransport } from "./realtime-channel";
|
|
30
32
|
export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
31
33
|
auth?: CreateAuthOptions;
|
|
32
34
|
admin?: CreateAdminOptions;
|
package/dist/index.es.js
CHANGED
|
@@ -1405,6 +1405,20 @@ function extractMessageError(message) {
|
|
|
1405
1405
|
};
|
|
1406
1406
|
}
|
|
1407
1407
|
/**
|
|
1408
|
+
* Broadcast and presence frames.
|
|
1409
|
+
*
|
|
1410
|
+
* Fire-and-forget (the server sends no response envelope), and exempt from the
|
|
1411
|
+
* client-side auth gate — a public channel is usable without an account.
|
|
1412
|
+
*/
|
|
1413
|
+
var CHANNEL_MESSAGE_TYPES = new Set([
|
|
1414
|
+
"join_channel",
|
|
1415
|
+
"leave_channel",
|
|
1416
|
+
"broadcast",
|
|
1417
|
+
"presence_track",
|
|
1418
|
+
"presence_untrack",
|
|
1419
|
+
"presence_state"
|
|
1420
|
+
]);
|
|
1421
|
+
/**
|
|
1408
1422
|
* Low-level realtime WebSocket client.
|
|
1409
1423
|
*
|
|
1410
1424
|
* @internal Not a stable app-facing API. `createRebaseClient()` constructs and
|
|
@@ -1419,6 +1433,36 @@ var RebaseWebSocketClient = class {
|
|
|
1419
1433
|
getAuthToken;
|
|
1420
1434
|
subscriptions = /* @__PURE__ */ new Map();
|
|
1421
1435
|
listeners = /* @__PURE__ */ new Map();
|
|
1436
|
+
/** Channel-name → handlers, for broadcast and presence frames. */
|
|
1437
|
+
channelHandlers = /* @__PURE__ */ new Map();
|
|
1438
|
+
/** Set by `close()`. Blocks any later operation from silently redialling. */
|
|
1439
|
+
closedByCaller = false;
|
|
1440
|
+
/**
|
|
1441
|
+
* Whether a socket exists at all (open or still opening).
|
|
1442
|
+
*
|
|
1443
|
+
* Lets callers distinguish "authenticate the live socket" from "there is
|
|
1444
|
+
* nothing to authenticate yet", without that question forcing a dial.
|
|
1445
|
+
*/
|
|
1446
|
+
get hasSocket() {
|
|
1447
|
+
return this.ws !== null;
|
|
1448
|
+
}
|
|
1449
|
+
/** So the "no WebSocket in this environment" warning is said once, not per call. */
|
|
1450
|
+
warnedNoWebSocket = false;
|
|
1451
|
+
/** Subscribe to broadcast/presence frames for one channel. */
|
|
1452
|
+
onChannelMessage(channel, handler) {
|
|
1453
|
+
if (!this.channelHandlers.has(channel)) this.channelHandlers.set(channel, /* @__PURE__ */ new Set());
|
|
1454
|
+
this.channelHandlers.get(channel).add(handler);
|
|
1455
|
+
return () => {
|
|
1456
|
+
const handlers = this.channelHandlers.get(channel);
|
|
1457
|
+
if (!handlers) return;
|
|
1458
|
+
handlers.delete(handler);
|
|
1459
|
+
if (handlers.size === 0) this.channelHandlers.delete(channel);
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
/** Notified after the socket comes back, so channels can re-join. */
|
|
1463
|
+
onReconnect(handler) {
|
|
1464
|
+
return this.on("reconnect", handler);
|
|
1465
|
+
}
|
|
1422
1466
|
on(event, cb) {
|
|
1423
1467
|
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1424
1468
|
this.listeners.get(event).add(cb);
|
|
@@ -1449,8 +1493,25 @@ var RebaseWebSocketClient = class {
|
|
|
1449
1493
|
this.getAuthToken = config.getAuthToken;
|
|
1450
1494
|
this.onUnauthorized = config.onUnauthorized;
|
|
1451
1495
|
this.WebSocketConstructor = config.WebSocket || (typeof WebSocket !== "undefined" ? WebSocket : void 0);
|
|
1452
|
-
|
|
1453
|
-
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Open the socket if it is not open (or opening) already.
|
|
1499
|
+
*
|
|
1500
|
+
* Idempotent, synchronous, and safe to call on every operation that needs a
|
|
1501
|
+
* live socket — `initWebSocket` already no-ops on an open socket and is
|
|
1502
|
+
* re-entrant, since the reconnect path has always called it.
|
|
1503
|
+
*/
|
|
1504
|
+
ensureConnected() {
|
|
1505
|
+
if (this.closedByCaller) return;
|
|
1506
|
+
if (!this.WebSocketConstructor) {
|
|
1507
|
+
if (!this.warnedNoWebSocket) {
|
|
1508
|
+
this.warnedNoWebSocket = true;
|
|
1509
|
+
console.warn("WebSocket is not defined in this environment. Realtime subscriptions will not work unless you provide a WebSocket implementation in the config.");
|
|
1510
|
+
}
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
if (this.ws || this.reconnectTimeout) return;
|
|
1514
|
+
this.initWebSocket();
|
|
1454
1515
|
}
|
|
1455
1516
|
/**
|
|
1456
1517
|
* Authenticate the WebSocket connection
|
|
@@ -1500,7 +1561,16 @@ var RebaseWebSocketClient = class {
|
|
|
1500
1561
|
});
|
|
1501
1562
|
}
|
|
1502
1563
|
}
|
|
1503
|
-
|
|
1564
|
+
/**
|
|
1565
|
+
* Drop the socket.
|
|
1566
|
+
*
|
|
1567
|
+
* `permanent` distinguishes the two callers. Signing out drops the socket
|
|
1568
|
+
* but the client stays usable — a later subscribe should reconnect
|
|
1569
|
+
* anonymously. `client.close()` is the caller saying they are done, and
|
|
1570
|
+
* must not be undone by a stray queued frame.
|
|
1571
|
+
*/
|
|
1572
|
+
disconnect(permanent = false) {
|
|
1573
|
+
if (permanent) this.closedByCaller = true;
|
|
1504
1574
|
this.isAuthenticated = false;
|
|
1505
1575
|
this.authPromise = null;
|
|
1506
1576
|
if (this.reconnectTimeout) {
|
|
@@ -1685,6 +1755,15 @@ var RebaseWebSocketClient = class {
|
|
|
1685
1755
|
}
|
|
1686
1756
|
return;
|
|
1687
1757
|
}
|
|
1758
|
+
if (typeof message.channel === "string" && (type === "broadcast" || type === "presence_state" || type === "presence_diff")) {
|
|
1759
|
+
const handlers = this.channelHandlers.get(message.channel);
|
|
1760
|
+
if (handlers) for (const handler of [...handlers]) try {
|
|
1761
|
+
handler(message);
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
console.error("Error in channel handler:", error);
|
|
1764
|
+
}
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1688
1767
|
if (subscriptionId && type === "collection_update") {
|
|
1689
1768
|
const subscriptionKey = this.backendToCollectionKey.get(subscriptionId);
|
|
1690
1769
|
if (subscriptionKey) {
|
|
@@ -1873,21 +1952,28 @@ var RebaseWebSocketClient = class {
|
|
|
1873
1952
|
throw error;
|
|
1874
1953
|
}
|
|
1875
1954
|
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Public because `RebaseRealtimeChannel` sends channel frames through it.
|
|
1957
|
+
* Not part of the stable surface — prefer `client.realtime.channel(name)`.
|
|
1958
|
+
*/
|
|
1876
1959
|
sendMessage(message) {
|
|
1877
1960
|
const queuedMsg = message;
|
|
1878
1961
|
if (queuedMsg._queuedResolve && queuedMsg._queuedReject) return this.doSendMessage(message, queuedMsg._queuedResolve, queuedMsg._queuedReject);
|
|
1879
|
-
if (!this.isConnected || !this.ws)
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1962
|
+
if (!this.isConnected || !this.ws) {
|
|
1963
|
+
this.ensureConnected();
|
|
1964
|
+
return new Promise((resolve, reject) => {
|
|
1965
|
+
const queueable = message;
|
|
1966
|
+
queueable._queuedResolve = resolve;
|
|
1967
|
+
queueable._queuedReject = reject;
|
|
1968
|
+
this.messageQueue.push(message);
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1885
1971
|
return new Promise((resolve, reject) => {
|
|
1886
1972
|
this.doSendMessage(message, resolve, reject);
|
|
1887
1973
|
});
|
|
1888
1974
|
}
|
|
1889
1975
|
async doSendMessage(message, resolve, reject) {
|
|
1890
|
-
if (message.type !== "AUTHENTICATE" && this.getAuthToken && !this.isAuthenticated) try {
|
|
1976
|
+
if (message.type !== "AUTHENTICATE" && !CHANNEL_MESSAGE_TYPES.has(message.type) && this.getAuthToken && !this.isAuthenticated) try {
|
|
1891
1977
|
await this.ensureAuthenticated();
|
|
1892
1978
|
} catch (error) {
|
|
1893
1979
|
reject(new RebaseApiError$1(error instanceof Error ? error.message : "Authentication required"));
|
|
@@ -1895,17 +1981,7 @@ var RebaseWebSocketClient = class {
|
|
|
1895
1981
|
}
|
|
1896
1982
|
const requestId = message.requestId || `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1897
1983
|
message.requestId = requestId;
|
|
1898
|
-
const expectsResponse = !
|
|
1899
|
-
"subscribe_collection",
|
|
1900
|
-
"subscribe_one",
|
|
1901
|
-
"unsubscribe",
|
|
1902
|
-
"join_channel",
|
|
1903
|
-
"leave_channel",
|
|
1904
|
-
"broadcast",
|
|
1905
|
-
"presence_track",
|
|
1906
|
-
"presence_untrack",
|
|
1907
|
-
"presence_state"
|
|
1908
|
-
].includes(message.type);
|
|
1984
|
+
const expectsResponse = !(message.type === "subscribe_collection" || message.type === "subscribe_one" || message.type === "unsubscribe" || CHANNEL_MESSAGE_TYPES.has(message.type));
|
|
1909
1985
|
if (expectsResponse && !this.pendingRequests.has(requestId)) {
|
|
1910
1986
|
const timeoutHandle = setTimeout(() => {
|
|
1911
1987
|
if (this.pendingRequests.has(requestId)) {
|
|
@@ -2134,6 +2210,7 @@ var RebaseWebSocketClient = class {
|
|
|
2134
2210
|
});
|
|
2135
2211
|
}
|
|
2136
2212
|
listenCollection(props, onUpdate, onError) {
|
|
2213
|
+
this.ensureConnected();
|
|
2137
2214
|
const subscriptionKey = this.createCollectionSubscriptionKey(props);
|
|
2138
2215
|
const callbackId = `callback_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
2139
2216
|
const existingSubscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
@@ -2195,6 +2272,7 @@ var RebaseWebSocketClient = class {
|
|
|
2195
2272
|
};
|
|
2196
2273
|
}
|
|
2197
2274
|
listenOne(props, onUpdate, onError) {
|
|
2275
|
+
this.ensureConnected();
|
|
2198
2276
|
const subscriptionKey = this.createSingleSubscriptionKey(props);
|
|
2199
2277
|
const callbackId = `callback_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
2200
2278
|
const existingSubscription = this.singleSubscriptions.get(subscriptionKey);
|
|
@@ -2453,6 +2531,190 @@ var RebaseWebSocketClient = class {
|
|
|
2453
2531
|
}
|
|
2454
2532
|
};
|
|
2455
2533
|
//#endregion
|
|
2534
|
+
//#region src/realtime-channel.ts
|
|
2535
|
+
/**
|
|
2536
|
+
* Re-send presence comfortably inside the server's 30s expiry.
|
|
2537
|
+
*
|
|
2538
|
+
* Two-thirds of the window: one lost heartbeat still leaves time for the next
|
|
2539
|
+
* before the entry is reaped, so a single dropped frame is not a disappearance.
|
|
2540
|
+
*/
|
|
2541
|
+
var PRESENCE_HEARTBEAT_MS = 2e4;
|
|
2542
|
+
var RebaseRealtimeChannel = class {
|
|
2543
|
+
name;
|
|
2544
|
+
transport;
|
|
2545
|
+
presenceHandlers = /* @__PURE__ */ new Set();
|
|
2546
|
+
broadcastHandlers = /* @__PURE__ */ new Set();
|
|
2547
|
+
unsubscribers = [];
|
|
2548
|
+
/** Last known roster, kept so handlers always get a full picture. */
|
|
2549
|
+
presences = {};
|
|
2550
|
+
/** What this client last tracked, replayed on reconnect and heartbeat. */
|
|
2551
|
+
trackedState = null;
|
|
2552
|
+
heartbeat = null;
|
|
2553
|
+
joined = false;
|
|
2554
|
+
constructor(name, transport) {
|
|
2555
|
+
this.name = name;
|
|
2556
|
+
this.transport = transport;
|
|
2557
|
+
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Join the channel and ask for the current roster.
|
|
2560
|
+
*
|
|
2561
|
+
* Called automatically by `track`, `broadcast`, `onPresence` and
|
|
2562
|
+
* `onBroadcast`; calling it directly is only needed to start receiving
|
|
2563
|
+
* before there is anything to send.
|
|
2564
|
+
*/
|
|
2565
|
+
async join() {
|
|
2566
|
+
if (this.joined) return;
|
|
2567
|
+
this.joined = true;
|
|
2568
|
+
this.unsubscribers.push(this.transport.onChannelMessage(this.name, (message) => this.handle(message)));
|
|
2569
|
+
this.unsubscribers.push(this.transport.onReconnect(() => {
|
|
2570
|
+
this.rejoin();
|
|
2571
|
+
}));
|
|
2572
|
+
await this.transport.sendMessage({
|
|
2573
|
+
type: "join_channel",
|
|
2574
|
+
channel: this.name
|
|
2575
|
+
});
|
|
2576
|
+
await this.transport.sendMessage({
|
|
2577
|
+
type: "presence_state",
|
|
2578
|
+
channel: this.name
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
async rejoin() {
|
|
2582
|
+
try {
|
|
2583
|
+
await this.transport.sendMessage({
|
|
2584
|
+
type: "join_channel",
|
|
2585
|
+
channel: this.name
|
|
2586
|
+
});
|
|
2587
|
+
await this.transport.sendMessage({
|
|
2588
|
+
type: "presence_state",
|
|
2589
|
+
channel: this.name
|
|
2590
|
+
});
|
|
2591
|
+
if (this.trackedState) await this.transport.sendMessage({
|
|
2592
|
+
type: "presence_track",
|
|
2593
|
+
channel: this.name,
|
|
2594
|
+
state: this.trackedState
|
|
2595
|
+
});
|
|
2596
|
+
} catch {}
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Publish this client's presence state, and keep publishing it.
|
|
2600
|
+
*
|
|
2601
|
+
* Calling `track` again replaces the state (and restarts the heartbeat),
|
|
2602
|
+
* which is how you update e.g. a cursor position.
|
|
2603
|
+
*/
|
|
2604
|
+
async track(state) {
|
|
2605
|
+
await this.join();
|
|
2606
|
+
this.trackedState = state;
|
|
2607
|
+
await this.transport.sendMessage({
|
|
2608
|
+
type: "presence_track",
|
|
2609
|
+
channel: this.name,
|
|
2610
|
+
state
|
|
2611
|
+
});
|
|
2612
|
+
if (!this.heartbeat) {
|
|
2613
|
+
this.heartbeat = setInterval(() => {
|
|
2614
|
+
if (!this.trackedState) return;
|
|
2615
|
+
this.transport.sendMessage({
|
|
2616
|
+
type: "presence_track",
|
|
2617
|
+
channel: this.name,
|
|
2618
|
+
state: this.trackedState
|
|
2619
|
+
}).catch(() => {});
|
|
2620
|
+
}, PRESENCE_HEARTBEAT_MS);
|
|
2621
|
+
this.heartbeat.unref?.();
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
/** Stop publishing presence, without leaving the channel. */
|
|
2625
|
+
async untrack() {
|
|
2626
|
+
this.stopHeartbeat();
|
|
2627
|
+
this.trackedState = null;
|
|
2628
|
+
if (this.joined) await this.transport.sendMessage({
|
|
2629
|
+
type: "presence_untrack",
|
|
2630
|
+
channel: this.name
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Observe the roster. The handler fires immediately with what is already
|
|
2635
|
+
* known, then on every change.
|
|
2636
|
+
*/
|
|
2637
|
+
onPresence(handler) {
|
|
2638
|
+
this.presenceHandlers.add(handler);
|
|
2639
|
+
this.join();
|
|
2640
|
+
if (Object.keys(this.presences).length > 0) handler({ ...this.presences });
|
|
2641
|
+
return () => this.presenceHandlers.delete(handler);
|
|
2642
|
+
}
|
|
2643
|
+
/** Send a broadcast. The sender does not receive its own message. */
|
|
2644
|
+
async broadcast(event, payload) {
|
|
2645
|
+
await this.join();
|
|
2646
|
+
await this.transport.sendMessage({
|
|
2647
|
+
type: "broadcast",
|
|
2648
|
+
channel: this.name,
|
|
2649
|
+
event,
|
|
2650
|
+
payload
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
onBroadcast(eventOrHandler, maybeHandler) {
|
|
2654
|
+
const wrapped = typeof eventOrHandler === "string" ? (e) => {
|
|
2655
|
+
if (e.event === eventOrHandler) maybeHandler(e.payload);
|
|
2656
|
+
} : eventOrHandler;
|
|
2657
|
+
this.broadcastHandlers.add(wrapped);
|
|
2658
|
+
this.join();
|
|
2659
|
+
return () => this.broadcastHandlers.delete(wrapped);
|
|
2660
|
+
}
|
|
2661
|
+
/** Leave the channel and release every listener and timer. */
|
|
2662
|
+
async leave() {
|
|
2663
|
+
this.stopHeartbeat();
|
|
2664
|
+
this.trackedState = null;
|
|
2665
|
+
this.presences = {};
|
|
2666
|
+
this.presenceHandlers.clear();
|
|
2667
|
+
this.broadcastHandlers.clear();
|
|
2668
|
+
for (const off of this.unsubscribers) off();
|
|
2669
|
+
this.unsubscribers = [];
|
|
2670
|
+
if (this.joined) {
|
|
2671
|
+
this.joined = false;
|
|
2672
|
+
await this.transport.sendMessage({
|
|
2673
|
+
type: "leave_channel",
|
|
2674
|
+
channel: this.name
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
stopHeartbeat() {
|
|
2679
|
+
if (this.heartbeat) {
|
|
2680
|
+
clearInterval(this.heartbeat);
|
|
2681
|
+
this.heartbeat = null;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
/** Fold an incoming frame into the roster and fan it out. */
|
|
2685
|
+
handle(message) {
|
|
2686
|
+
switch (message.type) {
|
|
2687
|
+
case "presence_state":
|
|
2688
|
+
this.presences = message.presences ?? {};
|
|
2689
|
+
this.emitPresence();
|
|
2690
|
+
break;
|
|
2691
|
+
case "presence_diff": {
|
|
2692
|
+
const joins = message.joins ?? {};
|
|
2693
|
+
const leaves = message.leaves ?? {};
|
|
2694
|
+
for (const [id, state] of Object.entries(joins)) this.presences[id] = state;
|
|
2695
|
+
for (const id of Object.keys(leaves)) delete this.presences[id];
|
|
2696
|
+
this.emitPresence({
|
|
2697
|
+
joins,
|
|
2698
|
+
leaves
|
|
2699
|
+
});
|
|
2700
|
+
break;
|
|
2701
|
+
}
|
|
2702
|
+
case "broadcast": {
|
|
2703
|
+
const event = {
|
|
2704
|
+
event: message.event,
|
|
2705
|
+
payload: message.payload
|
|
2706
|
+
};
|
|
2707
|
+
for (const handler of this.broadcastHandlers) handler(event);
|
|
2708
|
+
break;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
emitPresence(diff) {
|
|
2713
|
+
const snapshot = { ...this.presences };
|
|
2714
|
+
for (const handler of this.presenceHandlers) handler(snapshot, diff);
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
//#endregion
|
|
2456
2718
|
//#region src/index.ts
|
|
2457
2719
|
/**
|
|
2458
2720
|
* Derive a WebSocket URL from an HTTP base URL.
|
|
@@ -2503,6 +2765,8 @@ function createRebaseClient(options) {
|
|
|
2503
2765
|
};
|
|
2504
2766
|
const resolvedWsUrl = options.realtime !== false ? options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl) : void 0;
|
|
2505
2767
|
let ws;
|
|
2768
|
+
/** One channel object per name — see `realtime.channel`. */
|
|
2769
|
+
const realtimeChannels = /* @__PURE__ */ new Map();
|
|
2506
2770
|
if (resolvedWsUrl) {
|
|
2507
2771
|
ws = new RebaseWebSocketClient({
|
|
2508
2772
|
websocketUrl: resolvedWsUrl,
|
|
@@ -2526,7 +2790,7 @@ function createRebaseClient(options) {
|
|
|
2526
2790
|
if (!ws) return;
|
|
2527
2791
|
if (event === "SIGNED_OUT") ws.disconnect();
|
|
2528
2792
|
else if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
|
|
2529
|
-
if (session?.accessToken) ws.authenticate(session.accessToken).catch(console.warn);
|
|
2793
|
+
if (session?.accessToken && ws.hasSocket) ws.authenticate(session.accessToken).catch(console.warn);
|
|
2530
2794
|
}
|
|
2531
2795
|
});
|
|
2532
2796
|
}
|
|
@@ -2613,6 +2877,24 @@ function createRebaseClient(options) {
|
|
|
2613
2877
|
createStorageSource,
|
|
2614
2878
|
fetchStorageSources,
|
|
2615
2879
|
ws,
|
|
2880
|
+
realtime: {
|
|
2881
|
+
/**
|
|
2882
|
+
* Join a broadcast/presence channel.
|
|
2883
|
+
*
|
|
2884
|
+
* Repeated calls with the same name return the same channel, so
|
|
2885
|
+
* separate components can attach handlers without each opening its
|
|
2886
|
+
* own membership — and `leave()` from one would otherwise silently
|
|
2887
|
+
* cut off the others.
|
|
2888
|
+
*/
|
|
2889
|
+
channel: (name) => {
|
|
2890
|
+
if (!ws) throw new RebaseClientError("Realtime is disabled on this client (realtime: false), so channels are unavailable.");
|
|
2891
|
+
let existing = realtimeChannels.get(name);
|
|
2892
|
+
if (!existing) {
|
|
2893
|
+
existing = new RebaseRealtimeChannel(name, ws);
|
|
2894
|
+
realtimeChannels.set(name, existing);
|
|
2895
|
+
}
|
|
2896
|
+
return existing;
|
|
2897
|
+
} },
|
|
2616
2898
|
/**
|
|
2617
2899
|
* Release the realtime socket and its reconnect timer.
|
|
2618
2900
|
*
|
|
@@ -2621,7 +2903,9 @@ function createRebaseClient(options) {
|
|
|
2621
2903
|
* was never started, and safe to call twice.
|
|
2622
2904
|
*/
|
|
2623
2905
|
close: () => {
|
|
2624
|
-
|
|
2906
|
+
for (const channel of realtimeChannels.values()) channel.leave();
|
|
2907
|
+
realtimeChannels.clear();
|
|
2908
|
+
ws?.disconnect(true);
|
|
2625
2909
|
},
|
|
2626
2910
|
setToken: transport.setToken,
|
|
2627
2911
|
setAuthTokenGetter: transport.setAuthTokenGetter,
|
|
@@ -2641,6 +2925,6 @@ function createRebaseClient(options) {
|
|
|
2641
2925
|
};
|
|
2642
2926
|
}
|
|
2643
2927
|
//#endregion
|
|
2644
|
-
export { QueryBuilder, RebaseApiError, RebaseClientError, RebaseWebSocketClient, and, cond, createBackups, createCookieStorage, createMemoryStorage, createRebaseClient, or };
|
|
2928
|
+
export { QueryBuilder, RebaseApiError, RebaseClientError, RebaseRealtimeChannel, RebaseWebSocketClient, and, cond, createBackups, createCookieStorage, createMemoryStorage, createRebaseClient, or };
|
|
2645
2929
|
|
|
2646
2930
|
//# sourceMappingURL=index.es.js.map
|