@arkstack/realtime 0.19.1 → 0.19.3

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/README.md CHANGED
@@ -24,7 +24,7 @@ pnpm add firebase # Firebase transport
24
24
  Create a client and subscribe to a user's channel. `subscribe`/`forUser` resolve to an unsubscribe function.
25
25
 
26
26
  ```ts
27
- import { createRealtime } from '@arkstack/realtime';
27
+ import { createRealtime } from '@arkstack/realtime'
28
28
 
29
29
  const realtime = createRealtime({
30
30
  transport: 'pusher',
@@ -33,15 +33,15 @@ const realtime = createRealtime({
33
33
  cluster: 'mt1',
34
34
  authEndpoint: '/broadcasting/auth',
35
35
  },
36
- });
36
+ })
37
37
 
38
38
  const unsubscribe = await realtime.forUser(user.id, (notification) => {
39
- console.log(notification.title, notification.description);
40
- });
39
+ console.log(notification.title, notification.description)
40
+ })
41
41
 
42
42
  // later
43
- unsubscribe();
44
- await realtime.disconnect();
43
+ unsubscribe()
44
+ await realtime.disconnect()
45
45
  ```
46
46
 
47
47
  ### Client events
@@ -50,14 +50,12 @@ Pusher private and presence channels can send ephemeral client events (often
50
50
  called whispers). Subscribe to the channel before sending an event:
51
51
 
52
52
  ```ts
53
- const stopTyping = await realtime.listenForWhisper(
54
- 'private-room.7',
55
- 'typing',
56
- ({ userId }) => console.log(`${userId} is typing`),
57
- );
53
+ const stopTyping = await realtime.listenForWhisper('private-room.7', 'typing', ({ userId }) =>
54
+ console.log(`${userId} is typing`),
55
+ )
58
56
 
59
- await realtime.whisper('private-room.7', 'typing', { userId: user.id });
60
- stopTyping();
57
+ await realtime.whisper('private-room.7', 'typing', { userId: user.id })
58
+ stopTyping()
61
59
  ```
62
60
 
63
61
  The `client-` prefix is added automatically. Pusher sends client events over its
@@ -70,35 +68,31 @@ Each `notification` matches the payload broadcast by the server:
70
68
 
71
69
  ```ts
72
70
  interface RealtimeNotification {
73
- id: string;
74
- type: string | null;
75
- title: string;
76
- description: string;
77
- actionText?: string | null;
78
- actionLink?: string | null;
79
- meta?: Record<string, unknown> | null;
80
- read_at: string | null;
81
- created_at: string;
71
+ id: string
72
+ type: string | null
73
+ title: string
74
+ description: string
75
+ actionText?: string | null
76
+ actionLink?: string | null
77
+ meta?: Record<string, unknown> | null
78
+ read_at: string | null
79
+ created_at: string
82
80
  }
83
81
  ```
84
82
 
85
83
  ## React
86
84
 
87
85
  ```tsx
88
- import { useNotifications } from '@arkstack/realtime/react';
86
+ import { useNotifications } from '@arkstack/realtime/react'
89
87
 
90
88
  function Bell({ realtime, userId }) {
91
- const { notifications, latest, clear } = useNotifications(
92
- realtime,
93
- realtime.channelFor(userId),
94
- { limit: 20 },
95
- );
89
+ const { notifications, latest, clear } = useNotifications(realtime, realtime.channelFor(userId), { limit: 20 })
96
90
 
97
91
  return (
98
92
  <span onClick={clear}>
99
93
  {notifications.length} · {latest?.title}
100
94
  </span>
101
- );
95
+ )
102
96
  }
103
97
  ```
104
98
 
@@ -108,9 +102,9 @@ The hook accumulates notifications (newest first), caps them at `limit`, and uns
108
102
 
109
103
  ```vue
110
104
  <script setup>
111
- import { useNotifications } from '@arkstack/realtime/vue';
105
+ import { useNotifications } from "@arkstack/realtime/vue";
112
106
 
113
- const props = defineProps(['realtime', 'userId']);
107
+ const props = defineProps(["realtime", "userId"]);
114
108
  const { notifications, latest, clear } = useNotifications(
115
109
  props.realtime,
116
110
  props.realtime.channelFor(props.userId),
@@ -139,7 +133,7 @@ const realtime = createRealtime({
139
133
  messagingSenderId: import.meta.env.VITE_FIREBASE_SENDER_ID,
140
134
  databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL,
141
135
  },
142
- });
136
+ })
143
137
  ```
144
138
 
145
139
  Firebase client events are written below `arkstack/client-events` in Realtime
@@ -152,22 +146,22 @@ the root with `firebase.clientEventsPath` when needed.
152
146
  Provide `transportFactory` to bridge any backend (a raw WebSocket, SSE, a test double, …):
153
147
 
154
148
  ```ts
155
- import { createRealtime, type RealtimeTransport } from '@arkstack/realtime';
149
+ import { createRealtime, type RealtimeTransport } from '@arkstack/realtime'
156
150
 
157
151
  const transport: RealtimeTransport = {
158
152
  subscribe(channel, event, handler) {
159
- const socket = new WebSocket(`wss://example.test/${channel}`);
153
+ const socket = new WebSocket(`wss://example.test/${channel}`)
160
154
  socket.addEventListener('message', (e) => {
161
- const { event: name, payload } = JSON.parse(e.data);
162
- if (name === event) handler(payload);
163
- });
155
+ const { event: name, payload } = JSON.parse(e.data)
156
+ if (name === event) handler(payload)
157
+ })
164
158
 
165
- return { channel, unsubscribe: () => socket.close() };
159
+ return { channel, unsubscribe: () => socket.close() }
166
160
  },
167
161
  disconnect() {},
168
- };
162
+ }
169
163
 
170
- const realtime = createRealtime({ transportFactory: () => transport });
164
+ const realtime = createRealtime({ transportFactory: () => transport })
171
165
  ```
172
166
 
173
167
  ## API
@@ -70,7 +70,7 @@ interface RealtimeTransport {
70
70
  /**
71
71
  * This device's push registration token, when the transport can issue one.
72
72
  */
73
- deviceToken?(): Promise<string | null>;
73
+ deviceToken?(options?: DeviceTokenOptions): Promise<string | null>;
74
74
  /**
75
75
  * Set by transports that push nothing; clients should poll this often instead.
76
76
  */
@@ -117,6 +117,15 @@ interface FirebaseClientConfig {
117
117
  /** Web push VAPID key used when requesting a messaging token. */
118
118
  vapidKey?: string;
119
119
  }
120
+ interface DeviceTokenOptions {
121
+ /**
122
+ * The service worker that receives background messages: a registration, or
123
+ * the URL of a script to register. A registered URL receives the Firebase
124
+ * web config as its `firebaseConfig` query parameter. Defaults to Firebase's
125
+ * own `/firebase-messaging-sw.js` lookup.
126
+ */
127
+ serviceWorker?: ServiceWorkerRegistration | string;
128
+ }
120
129
  interface FirebaseDatabaseClientConfig {
121
130
  apiKey: string;
122
131
  projectId: string;
@@ -140,6 +149,12 @@ interface PollingClientConfig {
140
149
  /** How often, in milliseconds, {@link RealtimeClient.onPoll} handlers run (default 5000). */
141
150
  interval?: number;
142
151
  }
152
+ /**
153
+ * Loads an SDK module (`pusher-js`, `firebase/app`, …) by package specifier.
154
+ * Supply one where bare imports cannot resolve, e.g. a plain `<script>` page
155
+ * that loads the SDKs from a CDN.
156
+ */
157
+ type ModuleImporter = (specifier: string) => Promise<unknown>;
143
158
  interface RealtimeConfig {
144
159
  transport?: RealtimeTransportName;
145
160
  /** Event name broadcasts are published under (default `notification`). */
@@ -150,6 +165,8 @@ interface RealtimeConfig {
150
165
  firebase?: FirebaseClientConfig;
151
166
  'firebase-database'?: FirebaseDatabaseClientConfig;
152
167
  polling?: PollingClientConfig;
168
+ /** Loads the transport SDKs. Defaults to a dynamic `import()`. */
169
+ importModule?: ModuleImporter;
153
170
  /** Inject a transport directly — bypasses the built-ins (tests, custom backends). */
154
171
  transportFactory?: () => RealtimeTransport | Promise<RealtimeTransport>;
155
172
  }
@@ -241,15 +258,18 @@ declare class RealtimeClient {
241
258
  onPoll(handler: () => void, interval?: number): Promise<() => void>;
242
259
  /**
243
260
  * This device's push registration token, for registering with your API.
244
- * Null when the transport cannot issue one.
261
+ * Uses the transport when it can issue one, otherwise the `firebase` config
262
+ * (push over FCM while streaming over another transport). Null when neither
263
+ * can, or the browser cannot receive web push.
245
264
  *
265
+ * @param options
246
266
  * @returns
247
267
  */
248
- deviceToken(): Promise<string | null>;
268
+ deviceToken(options?: DeviceTokenOptions): Promise<string | null>;
249
269
  /** Tear down the underlying transport connection. */
250
270
  disconnect(): Promise<void>;
251
271
  }
252
272
  /** Create a new {@link RealtimeClient}. */
253
273
  declare const createRealtime: (config?: RealtimeConfig) => RealtimeClient;
254
274
  //#endregion
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 };
275
+ export { FirebaseDatabaseClientConfig as a, PollingClientConfig as c, RealtimeEventHandler as d, RealtimeNotification as f, RealtimeTransportName as h, FirebaseClientConfig as i, PusherClientConfig as l, RealtimeTransport as m, createRealtime as n, ModuleImporter as o, RealtimeSubscription as p, DeviceTokenOptions as r, NotificationHandler as s, RealtimeClient as t, RealtimeConfig as u };
@@ -0,0 +1,14 @@
1
+ //#region src/importer.ts
2
+ /**
3
+ * Load an optional SDK by its package specifier. Non-literal so bundlers leave
4
+ * SDKs the app does not use out of the bundle.
5
+ *
6
+ * @param specifier
7
+ * @returns
8
+ */
9
+ const defaultImporter = (specifier) => import(
10
+ /* @vite-ignore */
11
+ specifier
12
+ );
13
+ //#endregion
14
+ export { defaultImporter as t };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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";
1
+ import { a as FirebaseDatabaseClientConfig, c as PollingClientConfig, d as RealtimeEventHandler, f as RealtimeNotification, h as RealtimeTransportName, i as FirebaseClientConfig, l as PusherClientConfig, m as RealtimeTransport, n as createRealtime, o as ModuleImporter, p as RealtimeSubscription, r as DeviceTokenOptions, s as NotificationHandler, t as RealtimeClient, u as RealtimeConfig } from "./RealtimeClient-C3_ppuiI.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).
@@ -6,9 +6,10 @@ import { a as NotificationHandler, c as RealtimeConfig, d as RealtimeSubscriptio
6
6
  * it in when they use the Pusher transport.
7
7
  *
8
8
  * @param config
9
+ * @param importModule Loads `pusher-js`.
9
10
  * @returns
10
11
  */
11
- declare const createPusherTransport: (config: PusherClientConfig) => Promise<RealtimeTransport>;
12
+ declare const createPusherTransport: (config: PusherClientConfig, importModule?: ModuleImporter) => Promise<RealtimeTransport>;
12
13
  //#endregion
13
14
  //#region src/transports/firebase.d.ts
14
15
  /**
@@ -18,7 +19,7 @@ declare const createPusherTransport: (config: PusherClientConfig) => Promise<Rea
18
19
  * FCM delivers to the device (not per-channel), so the channel is informational;
19
20
  * messages are matched by `event` and the JSON-encoded payload is parsed back.
20
21
  */
21
- declare const createFirebaseTransport: (config: FirebaseClientConfig) => Promise<RealtimeTransport>;
22
+ declare const createFirebaseTransport: (config: FirebaseClientConfig, importModule?: ModuleImporter) => Promise<RealtimeTransport>;
22
23
  //#endregion
23
24
  //#region src/transports/firebase-database.d.ts
24
25
  /**
@@ -30,9 +31,10 @@ declare const createFirebaseTransport: (config: FirebaseClientConfig) => Promise
30
31
  * written after the transport connected are delivered.
31
32
  *
32
33
  * @param config
34
+ * @param importModule Loads the `firebase` SDK modules.
33
35
  * @returns
34
36
  */
35
- declare const createFirebaseDatabaseTransport: (config: FirebaseDatabaseClientConfig) => Promise<RealtimeTransport>;
37
+ declare const createFirebaseDatabaseTransport: (config: FirebaseDatabaseClientConfig, importModule?: ModuleImporter) => Promise<RealtimeTransport>;
36
38
  //#endregion
37
39
  //#region src/transports/polling.d.ts
38
40
  /**
@@ -87,4 +89,16 @@ declare const createMemoryTransport: () => RealtimeTransport;
87
89
  */
88
90
  declare const supersede: (list: RealtimeNotification[], incoming: RealtimeNotification, limit?: number) => RealtimeNotification[];
89
91
  //#endregion
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 };
92
+ //#region src/push.d.ts
93
+ /**
94
+ * Issue a device token without a Firebase transport, e.g. when notifications
95
+ * stream over Pusher but are pushed to devices over FCM.
96
+ *
97
+ * @param config
98
+ * @param options
99
+ * @param importModule Loads the `firebase` SDK modules.
100
+ * @returns
101
+ */
102
+ declare const requestDeviceToken: (config: FirebaseClientConfig, options?: DeviceTokenOptions, importModule?: ModuleImporter) => Promise<string | null>;
103
+ //#endregion
104
+ export { type DeviceTokenOptions, type FirebaseClientConfig, type FirebaseDatabaseClientConfig, type ModuleImporter, 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, requestDeviceToken, supersede };
package/dist/index.js CHANGED
@@ -1,16 +1,6 @@
1
+ import { i as __exportAll, r as requestDeviceToken, t as issueDeviceToken } from "./push-ucsxjX92.js";
2
+ import { t as defaultImporter } from "./importer-0BugD5WV.js";
1
3
  import { t as supersede } from "./supersede-T1ch4wJF.js";
2
- //#region \0rolldown/runtime.js
3
- var __defProp = Object.defineProperty;
4
- var __exportAll = (all, no_symbols) => {
5
- let target = {};
6
- for (var name in all) __defProp(target, name, {
7
- get: all[name],
8
- enumerable: true
9
- });
10
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
- return target;
12
- };
13
- //#endregion
14
4
  //#region src/RealtimeClient.ts
15
5
  /**
16
6
  * Consumes Arkstack realtime notifications. Resolves a transport (Pusher,
@@ -29,9 +19,9 @@ var RealtimeClient = class {
29
19
  }
30
20
  /**
31
21
  * The channel name a given user's notifications are broadcast on.
32
- *
33
- * @param userId
34
- * @returns
22
+ *
23
+ * @param userId
24
+ * @returns
35
25
  */
36
26
  channelFor(userId) {
37
27
  return `${this.channelPrefix}${userId}`;
@@ -45,11 +35,11 @@ var RealtimeClient = class {
45
35
  switch (this.transportName) {
46
36
  case "firebase": {
47
37
  const { createFirebaseTransport } = await Promise.resolve().then(() => firebase_exports);
48
- return await createFirebaseTransport(this.required("firebase"));
38
+ return await createFirebaseTransport(this.required("firebase"), this.config.importModule);
49
39
  }
50
40
  case "firebase-database": {
51
41
  const { createFirebaseDatabaseTransport } = await Promise.resolve().then(() => firebase_database_exports);
52
- return await createFirebaseDatabaseTransport(this.required("firebase-database"));
42
+ return await createFirebaseDatabaseTransport(this.required("firebase-database"), this.config.importModule);
53
43
  }
54
44
  case "polling": {
55
45
  const { createPollingTransport } = await Promise.resolve().then(() => polling_exports);
@@ -61,7 +51,7 @@ var RealtimeClient = class {
61
51
  }
62
52
  default: {
63
53
  const { createPusherTransport } = await Promise.resolve().then(() => pusher_exports);
64
- return await createPusherTransport(this.required("pusher"));
54
+ return await createPusherTransport(this.required("pusher"), this.config.importModule);
65
55
  }
66
56
  }
67
57
  }
@@ -159,12 +149,19 @@ var RealtimeClient = class {
159
149
  }
160
150
  /**
161
151
  * This device's push registration token, for registering with your API.
162
- * Null when the transport cannot issue one.
152
+ * Uses the transport when it can issue one, otherwise the `firebase` config
153
+ * (push over FCM while streaming over another transport). Null when neither
154
+ * can, or the browser cannot receive web push.
163
155
  *
156
+ * @param options
164
157
  * @returns
165
158
  */
166
- async deviceToken() {
167
- return await (await this.transport()).deviceToken?.() ?? null;
159
+ async deviceToken(options) {
160
+ const transport = await this.transport();
161
+ if (transport.deviceToken) return await transport.deviceToken(options);
162
+ if (!this.config.firebase) return null;
163
+ const { requestDeviceToken } = await import("./push-ucsxjX92.js").then((n) => n.n);
164
+ return await requestDeviceToken(this.config.firebase, options, this.config.importModule);
168
165
  }
169
166
  /** Tear down the underlying transport connection. */
170
167
  async disconnect() {
@@ -181,13 +178,14 @@ var pusher_exports = /* @__PURE__ */ __exportAll({ createPusherTransport: () =>
181
178
  /**
182
179
  * Realtime transport backed by [pusher-js](https://github.com/pusher/pusher-js).
183
180
  * The SDK is an optional peer dependency imported lazily, so consumers only pull
184
- * it in when they use the Pusher transport.
185
- *
186
- * @param config
187
- * @returns
181
+ * it in when they use the Pusher transport.
182
+ *
183
+ * @param config
184
+ * @param importModule Loads `pusher-js`.
185
+ * @returns
188
186
  */
189
- const createPusherTransport = async (config) => {
190
- const mod = await import("pusher-js").catch(() => {
187
+ const createPusherTransport = async (config, importModule = defaultImporter) => {
188
+ const mod = await importModule("pusher-js").catch(() => {
191
189
  throw new Error("The \"pusher-js\" package is required for the Pusher transport. Install it with `npm i pusher-js`.");
192
190
  });
193
191
  const client = new (mod.default ?? mod)(config.key, {
@@ -250,11 +248,11 @@ const isFirebaseClientEvent = (value) => {
250
248
  * FCM delivers to the device (not per-channel), so the channel is informational;
251
249
  * messages are matched by `event` and the JSON-encoded payload is parsed back.
252
250
  */
253
- const createFirebaseTransport = async (config) => {
251
+ const createFirebaseTransport = async (config, importModule = defaultImporter) => {
254
252
  const [appMod, messagingMod, databaseMod] = await Promise.all([
255
- import("firebase/app"),
256
- import("firebase/messaging"),
257
- import("firebase/database")
253
+ importModule("firebase/app"),
254
+ importModule("firebase/messaging"),
255
+ importModule("firebase/database")
258
256
  ]).catch(() => {
259
257
  throw new Error("The \"firebase\" package is required for the Firebase transport. Install it with `npm i firebase`.");
260
258
  });
@@ -307,9 +305,8 @@ const createFirebaseTransport = async (config) => {
307
305
  });
308
306
  await databaseApi.remove(eventReference);
309
307
  },
310
- async deviceToken() {
311
- const getToken = messagingMod.getToken;
312
- return await getToken(messaging, config.vapidKey ? { vapidKey: config.vapidKey } : void 0) || null;
308
+ async deviceToken(options) {
309
+ return await issueDeviceToken(messagingMod, messaging, config, options);
313
310
  },
314
311
  disconnect() {
315
312
  messageUnsubscribers.forEach((off) => off());
@@ -353,13 +350,14 @@ const tokenUid = (token) => {
353
350
  * written after the transport connected are delivered.
354
351
  *
355
352
  * @param config
353
+ * @param importModule Loads the `firebase` SDK modules.
356
354
  * @returns
357
355
  */
358
- const createFirebaseDatabaseTransport = async (config) => {
356
+ const createFirebaseDatabaseTransport = async (config, importModule = defaultImporter) => {
359
357
  const [appMod, authMod, databaseMod] = await Promise.all([
360
- import("firebase/app"),
361
- import("firebase/auth"),
362
- import("firebase/database")
358
+ importModule("firebase/app"),
359
+ importModule("firebase/auth"),
360
+ importModule("firebase/database")
363
361
  ]).catch(() => {
364
362
  throw new Error("The \"firebase\" package is required for the Firebase database transport. Install it with `npm i firebase`.");
365
363
  });
@@ -508,4 +506,4 @@ const createMemoryTransport = () => {
508
506
  };
509
507
  };
510
508
  //#endregion
511
- export { RealtimeClient, createFirebaseDatabaseTransport, createFirebaseTransport, createMemoryTransport, createPollingTransport, createPusherTransport, createRealtime, emitMemoryEvent, supersede };
509
+ export { RealtimeClient, createFirebaseDatabaseTransport, createFirebaseTransport, createMemoryTransport, createPollingTransport, createPusherTransport, createRealtime, emitMemoryEvent, requestDeviceToken, supersede };
@@ -0,0 +1,80 @@
1
+ import { t as defaultImporter } from "./importer-0BugD5WV.js";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __exportAll = (all, no_symbols) => {
5
+ let target = {};
6
+ for (var name in all) __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
+ return target;
12
+ };
13
+ //#endregion
14
+ //#region src/push.ts
15
+ var push_exports = /* @__PURE__ */ __exportAll({
16
+ issueDeviceToken: () => issueDeviceToken,
17
+ requestDeviceToken: () => requestDeviceToken
18
+ });
19
+ const APP_NAME = "arkstack-push";
20
+ /**
21
+ * The public settings a service worker needs to initialize Firebase.
22
+ *
23
+ * @param config
24
+ * @returns
25
+ */
26
+ const webConfig = (config) => ({
27
+ apiKey: config.apiKey,
28
+ projectId: config.projectId,
29
+ appId: config.appId,
30
+ messagingSenderId: config.messagingSenderId
31
+ });
32
+ /**
33
+ * Register `url` as the messaging service worker, handing it the Firebase web
34
+ * config as the `firebaseConfig` query parameter.
35
+ *
36
+ * @param url
37
+ * @param config
38
+ * @returns
39
+ */
40
+ const registerServiceWorker = async (url, config) => {
41
+ const separator = url.includes("?") ? "&" : "?";
42
+ const query = `firebaseConfig=${encodeURIComponent(JSON.stringify(webConfig(config)))}`;
43
+ return await navigator.serviceWorker.register(`${url}${separator}${query}`);
44
+ };
45
+ /**
46
+ * Issue an FCM registration token for this browser, prompting for notification
47
+ * permission when needed. Null when the browser cannot receive web push.
48
+ *
49
+ * @param messagingMod
50
+ * @param messaging
51
+ * @param config
52
+ * @param options
53
+ * @returns
54
+ */
55
+ const issueDeviceToken = async (messagingMod, messaging, config, options = {}) => {
56
+ if (!await messagingMod.isSupported().catch(() => false)) return null;
57
+ const serviceWorkerRegistration = typeof options.serviceWorker === "string" ? await registerServiceWorker(options.serviceWorker, config) : options.serviceWorker;
58
+ return await messagingMod.getToken(messaging, {
59
+ ...config.vapidKey ? { vapidKey: config.vapidKey } : {},
60
+ ...serviceWorkerRegistration ? { serviceWorkerRegistration } : {}
61
+ }) || null;
62
+ };
63
+ /**
64
+ * Issue a device token without a Firebase transport, e.g. when notifications
65
+ * stream over Pusher but are pushed to devices over FCM.
66
+ *
67
+ * @param config
68
+ * @param options
69
+ * @param importModule Loads the `firebase` SDK modules.
70
+ * @returns
71
+ */
72
+ const requestDeviceToken = async (config, options = {}, importModule = defaultImporter) => {
73
+ const [appMod, messagingMod] = await Promise.all([importModule("firebase/app"), importModule("firebase/messaging")]).catch(() => {
74
+ throw new Error("The \"firebase\" package is required for device tokens. Install it with `npm i firebase`.");
75
+ });
76
+ const app = appMod.getApps().find((existing) => existing.name === APP_NAME) ?? appMod.initializeApp(webConfig(config), APP_NAME);
77
+ return await issueDeviceToken(messagingMod, messagingMod.getMessaging(app), config, options);
78
+ };
79
+ //#endregion
80
+ export { __exportAll as i, push_exports as n, requestDeviceToken as r, issueDeviceToken as t };
@@ -1,4 +1,4 @@
1
- import { t as RealtimeClient, u as RealtimeNotification } from "../RealtimeClient-MI224djP.js";
1
+ import { f as RealtimeNotification, t as RealtimeClient } from "../RealtimeClient-C3_ppuiI.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. */
@@ -24,7 +24,9 @@ function useNotifications(client, channel, options = {}) {
24
24
  client.subscribe(channel, push).then((off) => {
25
25
  if (cancelled) off();
26
26
  else unsubscribe = off;
27
- }).catch(() => {/** surfaced by the caller's own error handling */});
27
+ }).catch(() => {
28
+ /** surfaced by the caller's own error handling */
29
+ });
28
30
  return () => {
29
31
  cancelled = true;
30
32
  unsubscribe?.();
@@ -1,4 +1,4 @@
1
- import { t as RealtimeClient, u as RealtimeNotification } from "../RealtimeClient-MI224djP.js";
1
+ import { f as RealtimeNotification, t as RealtimeClient } from "../RealtimeClient-C3_ppuiI.js";
2
2
  import { ComputedRef, Ref } from "vue";
3
3
  //#region src/vue/index.d.ts
4
4
  interface UseNotificationsOptions {
package/dist/vue/index.js CHANGED
@@ -25,7 +25,9 @@ function useNotifications(client, channel, options = {}) {
25
25
  client.subscribe(channel, push).then((off) => {
26
26
  if (cancelled) off();
27
27
  else unsubscribe = off;
28
- }).catch(() => {/** surfaced by the caller's own error handling */});
28
+ }).catch(() => {
29
+ /** surfaced by the caller's own error handling */
30
+ });
29
31
  const stop = () => {
30
32
  cancelled = true;
31
33
  unsubscribe?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/realtime",
3
- "version": "0.19.1",
3
+ "version": "0.19.3",
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",