@arkstack/realtime 0.18.12 → 0.19.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.
|
@@ -23,7 +23,16 @@ interface RealtimeNotification {
|
|
|
23
23
|
read_at: string | null;
|
|
24
24
|
created_at: string;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Client transports, matching the server's `drivers.realtime.transport`.
|
|
28
|
+
*
|
|
29
|
+
* - `pusher`: Pusher Channels, or a self-hosted Pusher-protocol server via `wsHost`.
|
|
30
|
+
* - `firebase`: Firebase Cloud Messaging foreground messages.
|
|
31
|
+
* - `firebase-database`: Firebase Realtime Database channels.
|
|
32
|
+
* - `polling`: nothing is pushed; use {@link RealtimeClient.onPoll} to refetch.
|
|
33
|
+
* - `memory`: an in-process bus, for tests.
|
|
34
|
+
*/
|
|
35
|
+
type RealtimeTransportName = 'pusher' | 'firebase' | 'firebase-database' | 'polling' | 'memory';
|
|
27
36
|
type NotificationHandler = (notification: RealtimeNotification) => void;
|
|
28
37
|
/** Handles an arbitrary realtime event payload. */
|
|
29
38
|
type RealtimeEventHandler<Payload = unknown> = (payload: Payload) => void;
|
|
@@ -58,6 +67,16 @@ interface RealtimeTransport {
|
|
|
58
67
|
* Disconnect from a connected realtime channel
|
|
59
68
|
*/
|
|
60
69
|
disconnect(): void | Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* This device's push registration token, when the transport can issue one.
|
|
72
|
+
*/
|
|
73
|
+
deviceToken?(): Promise<string | null>;
|
|
74
|
+
/**
|
|
75
|
+
* Set by transports that push nothing; clients should poll this often instead.
|
|
76
|
+
*/
|
|
77
|
+
polling?: {
|
|
78
|
+
interval: number;
|
|
79
|
+
};
|
|
61
80
|
}
|
|
62
81
|
interface PusherClientConfig {
|
|
63
82
|
key: string;
|
|
@@ -80,6 +99,11 @@ interface PusherClientConfig {
|
|
|
80
99
|
params?: Record<string, string>;
|
|
81
100
|
};
|
|
82
101
|
forceTLS?: boolean;
|
|
102
|
+
/** A self-hosted Pusher-protocol server such as soketi. */
|
|
103
|
+
wsHost?: string;
|
|
104
|
+
wsPort?: number;
|
|
105
|
+
wssPort?: number;
|
|
106
|
+
enabledTransports?: Array<'ws' | 'wss'>;
|
|
83
107
|
}
|
|
84
108
|
interface FirebaseClientConfig {
|
|
85
109
|
apiKey: string;
|
|
@@ -93,6 +117,29 @@ interface FirebaseClientConfig {
|
|
|
93
117
|
/** Web push VAPID key used when requesting a messaging token. */
|
|
94
118
|
vapidKey?: string;
|
|
95
119
|
}
|
|
120
|
+
interface FirebaseDatabaseClientConfig {
|
|
121
|
+
apiKey: string;
|
|
122
|
+
projectId: string;
|
|
123
|
+
databaseURL: string;
|
|
124
|
+
appId?: string;
|
|
125
|
+
authDomain?: string;
|
|
126
|
+
/** Database path that holds channel events (default `arkstack/channels`). */
|
|
127
|
+
root?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Your API's base URL; channels are authorized at `{apiBase}/realtime/auth`.
|
|
130
|
+
* Ignored when {@link authEndpoint} is set.
|
|
131
|
+
*/
|
|
132
|
+
apiBase?: string;
|
|
133
|
+
/** Endpoint that grants channel access and returns a Firebase custom token. */
|
|
134
|
+
authEndpoint?: string;
|
|
135
|
+
auth?: {
|
|
136
|
+
headers?: Record<string, string>;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
interface PollingClientConfig {
|
|
140
|
+
/** How often, in milliseconds, {@link RealtimeClient.onPoll} handlers run (default 5000). */
|
|
141
|
+
interval?: number;
|
|
142
|
+
}
|
|
96
143
|
interface RealtimeConfig {
|
|
97
144
|
transport?: RealtimeTransportName;
|
|
98
145
|
/** Event name broadcasts are published under (default `notification`). */
|
|
@@ -101,6 +148,8 @@ interface RealtimeConfig {
|
|
|
101
148
|
channelPrefix?: string;
|
|
102
149
|
pusher?: PusherClientConfig;
|
|
103
150
|
firebase?: FirebaseClientConfig;
|
|
151
|
+
'firebase-database'?: FirebaseDatabaseClientConfig;
|
|
152
|
+
polling?: PollingClientConfig;
|
|
104
153
|
/** Inject a transport directly — bypasses the built-ins (tests, custom backends). */
|
|
105
154
|
transportFactory?: () => RealtimeTransport | Promise<RealtimeTransport>;
|
|
106
155
|
}
|
|
@@ -126,6 +175,11 @@ declare class RealtimeClient {
|
|
|
126
175
|
channelFor(userId: string | number): string;
|
|
127
176
|
private transport;
|
|
128
177
|
private resolveTransport;
|
|
178
|
+
private required;
|
|
179
|
+
/**
|
|
180
|
+
* The configured transport name.
|
|
181
|
+
*/
|
|
182
|
+
get transportName(): RealtimeTransportName;
|
|
129
183
|
/**
|
|
130
184
|
* Subscribe to a channel. Returns a function that unsubscribes.
|
|
131
185
|
*
|
|
@@ -175,10 +229,27 @@ declare class RealtimeClient {
|
|
|
175
229
|
* @param handler Called with each incoming notification.
|
|
176
230
|
*/
|
|
177
231
|
forUser(userId: string | number, handler: NotificationHandler): Promise<() => void>;
|
|
232
|
+
/**
|
|
233
|
+
* Run `handler` on an interval while the transport is `polling`, so a view
|
|
234
|
+
* can refetch what it would otherwise receive live. Does nothing on push
|
|
235
|
+
* transports. Returns a function that stops it.
|
|
236
|
+
*
|
|
237
|
+
* @param handler
|
|
238
|
+
* @param interval Overrides the transport's advertised interval, in milliseconds.
|
|
239
|
+
* @returns
|
|
240
|
+
*/
|
|
241
|
+
onPoll(handler: () => void, interval?: number): Promise<() => void>;
|
|
242
|
+
/**
|
|
243
|
+
* This device's push registration token, for registering with your API.
|
|
244
|
+
* Null when the transport cannot issue one.
|
|
245
|
+
*
|
|
246
|
+
* @returns
|
|
247
|
+
*/
|
|
248
|
+
deviceToken(): Promise<string | null>;
|
|
178
249
|
/** Tear down the underlying transport connection. */
|
|
179
250
|
disconnect(): Promise<void>;
|
|
180
251
|
}
|
|
181
252
|
/** Create a new {@link RealtimeClient}. */
|
|
182
253
|
declare const createRealtime: (config?: RealtimeConfig) => RealtimeClient;
|
|
183
254
|
//#endregion
|
|
184
|
-
export {
|
|
255
|
+
export { NotificationHandler as a, RealtimeConfig as c, RealtimeSubscription as d, RealtimeTransport as f, FirebaseDatabaseClientConfig as i, RealtimeEventHandler as l, createRealtime as n, PollingClientConfig as o, RealtimeTransportName as p, FirebaseClientConfig as r, PusherClientConfig as s, RealtimeClient as t, RealtimeNotification as u };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as NotificationHandler, c as RealtimeConfig, d as RealtimeSubscription, f as RealtimeTransport, i as FirebaseDatabaseClientConfig, l as RealtimeEventHandler, n as createRealtime, o as PollingClientConfig, p as RealtimeTransportName, r as FirebaseClientConfig, s as PusherClientConfig, t as RealtimeClient, u as RealtimeNotification } from "./RealtimeClient-MI224djP.js";
|
|
2
2
|
//#region src/transports/pusher.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Realtime transport backed by [pusher-js](https://github.com/pusher/pusher-js).
|
|
@@ -20,6 +20,47 @@ declare const createPusherTransport: (config: PusherClientConfig) => Promise<Rea
|
|
|
20
20
|
*/
|
|
21
21
|
declare const createFirebaseTransport: (config: FirebaseClientConfig) => Promise<RealtimeTransport>;
|
|
22
22
|
//#endregion
|
|
23
|
+
//#region src/transports/firebase-database.d.ts
|
|
24
|
+
/**
|
|
25
|
+
* Realtime transport backed by [Firebase Realtime Database](https://firebase.google.com/docs/database/web/start).
|
|
26
|
+
* `firebase` is an optional peer dependency imported lazily.
|
|
27
|
+
*
|
|
28
|
+
* Each subscription first asks the API to authorize the channel, which grants
|
|
29
|
+
* this user read access and returns a custom token to sign in with. Only events
|
|
30
|
+
* written after the transport connected are delivered.
|
|
31
|
+
*
|
|
32
|
+
* @param config
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
35
|
+
declare const createFirebaseDatabaseTransport: (config: FirebaseDatabaseClientConfig) => Promise<RealtimeTransport>;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/transports/polling.d.ts
|
|
38
|
+
/**
|
|
39
|
+
* A transport that receives nothing. Subscriptions are accepted and stay
|
|
40
|
+
* silent; use {@link RealtimeClient.onPoll} to refetch on the advertised interval.
|
|
41
|
+
*
|
|
42
|
+
* @param config
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
declare const createPollingTransport: (config?: PollingClientConfig) => RealtimeTransport;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/transports/memory.d.ts
|
|
48
|
+
/**
|
|
49
|
+
* Deliver an event to every in-process subscriber of `channel`, as a server
|
|
50
|
+
* broadcast would.
|
|
51
|
+
*
|
|
52
|
+
* @param channel
|
|
53
|
+
* @param event
|
|
54
|
+
* @param payload
|
|
55
|
+
*/
|
|
56
|
+
declare const emitMemoryEvent: (channel: string, event: string, payload: unknown) => void;
|
|
57
|
+
/**
|
|
58
|
+
* An in-process transport for tests. Feed it with {@link emitMemoryEvent}.
|
|
59
|
+
*
|
|
60
|
+
* @returns
|
|
61
|
+
*/
|
|
62
|
+
declare const createMemoryTransport: () => RealtimeTransport;
|
|
63
|
+
//#endregion
|
|
23
64
|
//#region src/supersede.d.ts
|
|
24
65
|
/**
|
|
25
66
|
* Fold an incoming notification into a list, honouring tag supersession.
|
|
@@ -46,4 +87,4 @@ declare const createFirebaseTransport: (config: FirebaseClientConfig) => Promise
|
|
|
46
87
|
*/
|
|
47
88
|
declare const supersede: (list: RealtimeNotification[], incoming: RealtimeNotification, limit?: number) => RealtimeNotification[];
|
|
48
89
|
//#endregion
|
|
49
|
-
export { type FirebaseClientConfig, type NotificationHandler, type PusherClientConfig, RealtimeClient, type RealtimeConfig, type RealtimeEventHandler, type RealtimeNotification, type RealtimeSubscription, type RealtimeTransport, type RealtimeTransportName, createFirebaseTransport, createPusherTransport, createRealtime, supersede };
|
|
90
|
+
export { type FirebaseClientConfig, type FirebaseDatabaseClientConfig, type NotificationHandler, type PollingClientConfig, type PusherClientConfig, RealtimeClient, type RealtimeConfig, type RealtimeEventHandler, type RealtimeNotification, type RealtimeSubscription, type RealtimeTransport, type RealtimeTransportName, createFirebaseDatabaseTransport, createFirebaseTransport, createMemoryTransport, createPollingTransport, createPusherTransport, createRealtime, emitMemoryEvent, supersede };
|
package/dist/index.js
CHANGED
|
@@ -42,14 +42,39 @@ var RealtimeClient = class {
|
|
|
42
42
|
}
|
|
43
43
|
async resolveTransport() {
|
|
44
44
|
if (this.config.transportFactory) return await this.config.transportFactory();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
switch (this.transportName) {
|
|
46
|
+
case "firebase": {
|
|
47
|
+
const { createFirebaseTransport } = await Promise.resolve().then(() => firebase_exports);
|
|
48
|
+
return await createFirebaseTransport(this.required("firebase"));
|
|
49
|
+
}
|
|
50
|
+
case "firebase-database": {
|
|
51
|
+
const { createFirebaseDatabaseTransport } = await Promise.resolve().then(() => firebase_database_exports);
|
|
52
|
+
return await createFirebaseDatabaseTransport(this.required("firebase-database"));
|
|
53
|
+
}
|
|
54
|
+
case "polling": {
|
|
55
|
+
const { createPollingTransport } = await Promise.resolve().then(() => polling_exports);
|
|
56
|
+
return createPollingTransport(this.config.polling);
|
|
57
|
+
}
|
|
58
|
+
case "memory": {
|
|
59
|
+
const { createMemoryTransport } = await Promise.resolve().then(() => memory_exports);
|
|
60
|
+
return createMemoryTransport();
|
|
61
|
+
}
|
|
62
|
+
default: {
|
|
63
|
+
const { createPusherTransport } = await Promise.resolve().then(() => pusher_exports);
|
|
64
|
+
return await createPusherTransport(this.required("pusher"));
|
|
65
|
+
}
|
|
49
66
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
}
|
|
68
|
+
required(name) {
|
|
69
|
+
const config = this.config[name];
|
|
70
|
+
if (!config) throw new Error(`Realtime: \`${name}\` config is required for the ${name} transport`);
|
|
71
|
+
return config;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The configured transport name.
|
|
75
|
+
*/
|
|
76
|
+
get transportName() {
|
|
77
|
+
return this.config.transport ?? "pusher";
|
|
53
78
|
}
|
|
54
79
|
/**
|
|
55
80
|
* Subscribe to a channel. Returns a function that unsubscribes.
|
|
@@ -117,6 +142,30 @@ var RealtimeClient = class {
|
|
|
117
142
|
async forUser(userId, handler) {
|
|
118
143
|
return await this.subscribe(this.channelFor(userId), handler);
|
|
119
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Run `handler` on an interval while the transport is `polling`, so a view
|
|
147
|
+
* can refetch what it would otherwise receive live. Does nothing on push
|
|
148
|
+
* transports. Returns a function that stops it.
|
|
149
|
+
*
|
|
150
|
+
* @param handler
|
|
151
|
+
* @param interval Overrides the transport's advertised interval, in milliseconds.
|
|
152
|
+
* @returns
|
|
153
|
+
*/
|
|
154
|
+
async onPoll(handler, interval) {
|
|
155
|
+
const transport = await this.transport();
|
|
156
|
+
if (!transport.polling) return () => {};
|
|
157
|
+
const timer = setInterval(handler, interval ?? transport.polling.interval);
|
|
158
|
+
return () => clearInterval(timer);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* This device's push registration token, for registering with your API.
|
|
162
|
+
* Null when the transport cannot issue one.
|
|
163
|
+
*
|
|
164
|
+
* @returns
|
|
165
|
+
*/
|
|
166
|
+
async deviceToken() {
|
|
167
|
+
return await (await this.transport()).deviceToken?.() ?? null;
|
|
168
|
+
}
|
|
120
169
|
/** Tear down the underlying transport connection. */
|
|
121
170
|
async disconnect() {
|
|
122
171
|
if (!this.transportPromise) return;
|
|
@@ -145,7 +194,13 @@ const createPusherTransport = async (config) => {
|
|
|
145
194
|
cluster: config.cluster ?? "mt1",
|
|
146
195
|
forceTLS: config.forceTLS ?? true,
|
|
147
196
|
authEndpoint: config.authEndpoint ?? `${config.apiBase}/realtime/auth`,
|
|
148
|
-
auth: config.auth
|
|
197
|
+
auth: config.auth,
|
|
198
|
+
...config.wsHost ? {
|
|
199
|
+
wsHost: config.wsHost,
|
|
200
|
+
wsPort: config.wsPort,
|
|
201
|
+
wssPort: config.wssPort ?? config.wsPort,
|
|
202
|
+
enabledTransports: config.enabledTransports ?? ["ws", "wss"]
|
|
203
|
+
} : {}
|
|
149
204
|
});
|
|
150
205
|
const channels = /* @__PURE__ */ new Map();
|
|
151
206
|
return {
|
|
@@ -252,6 +307,10 @@ const createFirebaseTransport = async (config) => {
|
|
|
252
307
|
});
|
|
253
308
|
await databaseApi.remove(eventReference);
|
|
254
309
|
},
|
|
310
|
+
async deviceToken() {
|
|
311
|
+
const getToken = messagingMod.getToken;
|
|
312
|
+
return await getToken(messaging, config.vapidKey ? { vapidKey: config.vapidKey } : void 0) || null;
|
|
313
|
+
},
|
|
255
314
|
disconnect() {
|
|
256
315
|
messageUnsubscribers.forEach((off) => off());
|
|
257
316
|
messageUnsubscribers.clear();
|
|
@@ -261,4 +320,192 @@ const createFirebaseTransport = async (config) => {
|
|
|
261
320
|
};
|
|
262
321
|
};
|
|
263
322
|
//#endregion
|
|
264
|
-
|
|
323
|
+
//#region src/transports/firebase-database.ts
|
|
324
|
+
var firebase_database_exports = /* @__PURE__ */ __exportAll({ createFirebaseDatabaseTransport: () => createFirebaseDatabaseTransport });
|
|
325
|
+
const APP_NAME = "arkstack-realtime-database";
|
|
326
|
+
/**
|
|
327
|
+
* Encode a channel name into a single Realtime Database key, matching the server.
|
|
328
|
+
*
|
|
329
|
+
* @param channel
|
|
330
|
+
* @returns
|
|
331
|
+
*/
|
|
332
|
+
const channelKey = (channel) => encodeURIComponent(channel).replace(/\./g, "%2E");
|
|
333
|
+
/**
|
|
334
|
+
* The `uid` claim of a Firebase custom token.
|
|
335
|
+
*
|
|
336
|
+
* @param token
|
|
337
|
+
* @returns
|
|
338
|
+
*/
|
|
339
|
+
const tokenUid = (token) => {
|
|
340
|
+
try {
|
|
341
|
+
const segment = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
342
|
+
return JSON.parse(atob(segment)).uid ?? null;
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Realtime transport backed by [Firebase Realtime Database](https://firebase.google.com/docs/database/web/start).
|
|
349
|
+
* `firebase` is an optional peer dependency imported lazily.
|
|
350
|
+
*
|
|
351
|
+
* Each subscription first asks the API to authorize the channel, which grants
|
|
352
|
+
* this user read access and returns a custom token to sign in with. Only events
|
|
353
|
+
* written after the transport connected are delivered.
|
|
354
|
+
*
|
|
355
|
+
* @param config
|
|
356
|
+
* @returns
|
|
357
|
+
*/
|
|
358
|
+
const createFirebaseDatabaseTransport = async (config) => {
|
|
359
|
+
const [appMod, authMod, databaseMod] = await Promise.all([
|
|
360
|
+
import("firebase/app"),
|
|
361
|
+
import("firebase/auth"),
|
|
362
|
+
import("firebase/database")
|
|
363
|
+
]).catch(() => {
|
|
364
|
+
throw new Error("The \"firebase\" package is required for the Firebase database transport. Install it with `npm i firebase`.");
|
|
365
|
+
});
|
|
366
|
+
const app = appMod.getApps().find((existing) => existing.name === APP_NAME) ?? appMod.initializeApp({
|
|
367
|
+
apiKey: config.apiKey,
|
|
368
|
+
authDomain: config.authDomain,
|
|
369
|
+
projectId: config.projectId,
|
|
370
|
+
appId: config.appId,
|
|
371
|
+
databaseURL: config.databaseURL
|
|
372
|
+
}, APP_NAME);
|
|
373
|
+
const auth = authMod.getAuth(app);
|
|
374
|
+
const database = databaseMod.getDatabase(app, config.databaseURL);
|
|
375
|
+
const root = config.root ?? "arkstack/channels";
|
|
376
|
+
const endpoint = config.authEndpoint ?? `${config.apiBase}/realtime/auth`;
|
|
377
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
378
|
+
const offset = await new Promise((resolve) => {
|
|
379
|
+
databaseMod.onValue(databaseMod.ref(database, ".info/serverTimeOffset"), (snapshot) => resolve(Number(snapshot.val()) || 0), { onlyOnce: true });
|
|
380
|
+
});
|
|
381
|
+
const since = Date.now() + offset;
|
|
382
|
+
let signing = Promise.resolve();
|
|
383
|
+
const authorize = async (channel) => {
|
|
384
|
+
const response = await fetch(endpoint, {
|
|
385
|
+
method: "POST",
|
|
386
|
+
headers: {
|
|
387
|
+
"content-type": "application/json",
|
|
388
|
+
accept: "application/json",
|
|
389
|
+
...config.auth?.headers
|
|
390
|
+
},
|
|
391
|
+
body: JSON.stringify({ channel_name: channel })
|
|
392
|
+
});
|
|
393
|
+
if (!response.ok) throw new Error(`Realtime: channel "${channel}" was not authorized (${response.status})`);
|
|
394
|
+
const { token } = await response.json();
|
|
395
|
+
signing = signing.then(async () => {
|
|
396
|
+
if (auth.currentUser?.uid !== tokenUid(token)) await authMod.signInWithCustomToken(auth, token);
|
|
397
|
+
});
|
|
398
|
+
await signing;
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
async subscribe(channel, event, handler) {
|
|
402
|
+
await authorize(channel);
|
|
403
|
+
const off = databaseMod.onChildAdded(databaseMod.query(databaseMod.ref(database, `${root}/${channelKey(channel)}`), databaseMod.orderByChild("at"), databaseMod.startAt(since)), (snapshot) => {
|
|
404
|
+
const value = snapshot.val();
|
|
405
|
+
if (value?.event === event) handler(value.payload);
|
|
406
|
+
});
|
|
407
|
+
listeners.add(off);
|
|
408
|
+
return {
|
|
409
|
+
channel,
|
|
410
|
+
unsubscribe() {
|
|
411
|
+
off();
|
|
412
|
+
listeners.delete(off);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
},
|
|
416
|
+
async disconnect() {
|
|
417
|
+
listeners.forEach((off) => off());
|
|
418
|
+
listeners.clear();
|
|
419
|
+
await authMod.signOut(auth);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/transports/polling.ts
|
|
425
|
+
var polling_exports = /* @__PURE__ */ __exportAll({ createPollingTransport: () => createPollingTransport });
|
|
426
|
+
/**
|
|
427
|
+
* A transport that receives nothing. Subscriptions are accepted and stay
|
|
428
|
+
* silent; use {@link RealtimeClient.onPoll} to refetch on the advertised interval.
|
|
429
|
+
*
|
|
430
|
+
* @param config
|
|
431
|
+
* @returns
|
|
432
|
+
*/
|
|
433
|
+
const createPollingTransport = (config = {}) => ({
|
|
434
|
+
polling: { interval: config.interval ?? 5e3 },
|
|
435
|
+
subscribe(channel) {
|
|
436
|
+
return {
|
|
437
|
+
channel,
|
|
438
|
+
unsubscribe() {}
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
disconnect() {}
|
|
442
|
+
});
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region src/transports/memory.ts
|
|
445
|
+
var memory_exports = /* @__PURE__ */ __exportAll({
|
|
446
|
+
createMemoryTransport: () => createMemoryTransport,
|
|
447
|
+
emitMemoryEvent: () => emitMemoryEvent
|
|
448
|
+
});
|
|
449
|
+
const BUS_KEY = Symbol.for("arkstack.realtime.client-memory");
|
|
450
|
+
/**
|
|
451
|
+
* The in-process bus, kept on `globalThis` so every module copy shares it.
|
|
452
|
+
*
|
|
453
|
+
* @returns
|
|
454
|
+
*/
|
|
455
|
+
const bus = () => {
|
|
456
|
+
const store = globalThis;
|
|
457
|
+
return store[BUS_KEY] ??= /* @__PURE__ */ new Map();
|
|
458
|
+
};
|
|
459
|
+
/**
|
|
460
|
+
* Deliver an event to every in-process subscriber of `channel`, as a server
|
|
461
|
+
* broadcast would.
|
|
462
|
+
*
|
|
463
|
+
* @param channel
|
|
464
|
+
* @param event
|
|
465
|
+
* @param payload
|
|
466
|
+
*/
|
|
467
|
+
const emitMemoryEvent = (channel, event, payload) => {
|
|
468
|
+
bus().get(channel)?.forEach((listener) => {
|
|
469
|
+
if (listener.event === event) listener.handler(payload);
|
|
470
|
+
});
|
|
471
|
+
};
|
|
472
|
+
/**
|
|
473
|
+
* An in-process transport for tests. Feed it with {@link emitMemoryEvent}.
|
|
474
|
+
*
|
|
475
|
+
* @returns
|
|
476
|
+
*/
|
|
477
|
+
const createMemoryTransport = () => {
|
|
478
|
+
const own = /* @__PURE__ */ new Set();
|
|
479
|
+
return {
|
|
480
|
+
subscribe(channel, event, handler) {
|
|
481
|
+
const listener = {
|
|
482
|
+
event,
|
|
483
|
+
handler
|
|
484
|
+
};
|
|
485
|
+
const listeners = bus().get(channel) ?? /* @__PURE__ */ new Set();
|
|
486
|
+
const entry = {
|
|
487
|
+
channel,
|
|
488
|
+
listener
|
|
489
|
+
};
|
|
490
|
+
listeners.add(listener);
|
|
491
|
+
bus().set(channel, listeners);
|
|
492
|
+
own.add(entry);
|
|
493
|
+
return {
|
|
494
|
+
channel,
|
|
495
|
+
unsubscribe() {
|
|
496
|
+
listeners.delete(listener);
|
|
497
|
+
own.delete(entry);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
},
|
|
501
|
+
trigger(channel, event, payload) {
|
|
502
|
+
emitMemoryEvent(channel, event, payload);
|
|
503
|
+
},
|
|
504
|
+
disconnect() {
|
|
505
|
+
own.forEach(({ channel, listener }) => bus().get(channel)?.delete(listener));
|
|
506
|
+
own.clear();
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
//#endregion
|
|
511
|
+
export { RealtimeClient, createFirebaseDatabaseTransport, createFirebaseTransport, createMemoryTransport, createPollingTransport, createPusherTransport, createRealtime, emitMemoryEvent, supersede };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as RealtimeClient, u as RealtimeNotification } from "../RealtimeClient-MI224djP.js";
|
|
2
2
|
//#region src/react/index.d.ts
|
|
3
3
|
interface UseNotificationsOptions {
|
|
4
4
|
/** Cap the number of retained notifications (newest kept). Default: unbounded. */
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as RealtimeClient, u as RealtimeNotification } from "../RealtimeClient-MI224djP.js";
|
|
2
2
|
import { ComputedRef, Ref } from "vue";
|
|
3
3
|
//#region src/vue/index.d.ts
|
|
4
4
|
interface UseNotificationsOptions {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkstack/realtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Client for consuming Arkstack realtime notifications (Pusher/Firebase), with framework-agnostic core plus React and Vue bindings.",
|
|
6
6
|
"homepage": "https://arkstack.toneflix.net/guide/notifications",
|