@arkstack/realtime 0.19.2 → 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
@@ -149,6 +149,12 @@ interface PollingClientConfig {
149
149
  /** How often, in milliseconds, {@link RealtimeClient.onPoll} handlers run (default 5000). */
150
150
  interval?: number;
151
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>;
152
158
  interface RealtimeConfig {
153
159
  transport?: RealtimeTransportName;
154
160
  /** Event name broadcasts are published under (default `notification`). */
@@ -159,6 +165,8 @@ interface RealtimeConfig {
159
165
  firebase?: FirebaseClientConfig;
160
166
  'firebase-database'?: FirebaseDatabaseClientConfig;
161
167
  polling?: PollingClientConfig;
168
+ /** Loads the transport SDKs. Defaults to a dynamic `import()`. */
169
+ importModule?: ModuleImporter;
162
170
  /** Inject a transport directly — bypasses the built-ins (tests, custom backends). */
163
171
  transportFactory?: () => RealtimeTransport | Promise<RealtimeTransport>;
164
172
  }
@@ -264,4 +272,4 @@ declare class RealtimeClient {
264
272
  /** Create a new {@link RealtimeClient}. */
265
273
  declare const createRealtime: (config?: RealtimeConfig) => RealtimeClient;
266
274
  //#endregion
267
- export { FirebaseDatabaseClientConfig as a, PusherClientConfig as c, RealtimeNotification as d, RealtimeSubscription as f, FirebaseClientConfig as i, RealtimeConfig as l, RealtimeTransportName as m, createRealtime as n, NotificationHandler as o, RealtimeTransport as p, DeviceTokenOptions as r, PollingClientConfig as s, RealtimeClient as t, RealtimeEventHandler 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 FirebaseDatabaseClientConfig, c as PusherClientConfig, d as RealtimeNotification, f as RealtimeSubscription, i as FirebaseClientConfig, l as RealtimeConfig, m as RealtimeTransportName, n as createRealtime, o as NotificationHandler, p as RealtimeTransport, r as DeviceTokenOptions, s as PollingClientConfig, t as RealtimeClient, u as RealtimeEventHandler } from "./RealtimeClient-CAoMUUkc.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 FirebaseDatabaseClientConfig, c as PusherClientConfig, d as Realti
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
  /**
@@ -94,8 +96,9 @@ declare const supersede: (list: RealtimeNotification[], incoming: RealtimeNotifi
94
96
  *
95
97
  * @param config
96
98
  * @param options
99
+ * @param importModule Loads the `firebase` SDK modules.
97
100
  * @returns
98
101
  */
99
- declare const requestDeviceToken: (config: FirebaseClientConfig, options?: DeviceTokenOptions) => Promise<string | null>;
102
+ declare const requestDeviceToken: (config: FirebaseClientConfig, options?: DeviceTokenOptions, importModule?: ModuleImporter) => Promise<string | null>;
100
103
  //#endregion
101
- export { type DeviceTokenOptions, 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, requestDeviceToken, supersede };
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,4 +1,5 @@
1
- import { i as __exportAll, r as requestDeviceToken, t as issueDeviceToken } from "./push-TmPMqRUR.js";
1
+ import { i as __exportAll, r as requestDeviceToken, t as issueDeviceToken } from "./push-ucsxjX92.js";
2
+ import { t as defaultImporter } from "./importer-0BugD5WV.js";
2
3
  import { t as supersede } from "./supersede-T1ch4wJF.js";
3
4
  //#region src/RealtimeClient.ts
4
5
  /**
@@ -18,9 +19,9 @@ var RealtimeClient = class {
18
19
  }
19
20
  /**
20
21
  * The channel name a given user's notifications are broadcast on.
21
- *
22
- * @param userId
23
- * @returns
22
+ *
23
+ * @param userId
24
+ * @returns
24
25
  */
25
26
  channelFor(userId) {
26
27
  return `${this.channelPrefix}${userId}`;
@@ -34,11 +35,11 @@ var RealtimeClient = class {
34
35
  switch (this.transportName) {
35
36
  case "firebase": {
36
37
  const { createFirebaseTransport } = await Promise.resolve().then(() => firebase_exports);
37
- return await createFirebaseTransport(this.required("firebase"));
38
+ return await createFirebaseTransport(this.required("firebase"), this.config.importModule);
38
39
  }
39
40
  case "firebase-database": {
40
41
  const { createFirebaseDatabaseTransport } = await Promise.resolve().then(() => firebase_database_exports);
41
- return await createFirebaseDatabaseTransport(this.required("firebase-database"));
42
+ return await createFirebaseDatabaseTransport(this.required("firebase-database"), this.config.importModule);
42
43
  }
43
44
  case "polling": {
44
45
  const { createPollingTransport } = await Promise.resolve().then(() => polling_exports);
@@ -50,7 +51,7 @@ var RealtimeClient = class {
50
51
  }
51
52
  default: {
52
53
  const { createPusherTransport } = await Promise.resolve().then(() => pusher_exports);
53
- return await createPusherTransport(this.required("pusher"));
54
+ return await createPusherTransport(this.required("pusher"), this.config.importModule);
54
55
  }
55
56
  }
56
57
  }
@@ -159,8 +160,8 @@ var RealtimeClient = class {
159
160
  const transport = await this.transport();
160
161
  if (transport.deviceToken) return await transport.deviceToken(options);
161
162
  if (!this.config.firebase) return null;
162
- const { requestDeviceToken } = await import("./push-TmPMqRUR.js").then((n) => n.n);
163
- return await requestDeviceToken(this.config.firebase, options);
163
+ const { requestDeviceToken } = await import("./push-ucsxjX92.js").then((n) => n.n);
164
+ return await requestDeviceToken(this.config.firebase, options, this.config.importModule);
164
165
  }
165
166
  /** Tear down the underlying transport connection. */
166
167
  async disconnect() {
@@ -177,13 +178,14 @@ var pusher_exports = /* @__PURE__ */ __exportAll({ createPusherTransport: () =>
177
178
  /**
178
179
  * Realtime transport backed by [pusher-js](https://github.com/pusher/pusher-js).
179
180
  * The SDK is an optional peer dependency imported lazily, so consumers only pull
180
- * it in when they use the Pusher transport.
181
- *
182
- * @param config
183
- * @returns
181
+ * it in when they use the Pusher transport.
182
+ *
183
+ * @param config
184
+ * @param importModule Loads `pusher-js`.
185
+ * @returns
184
186
  */
185
- const createPusherTransport = async (config) => {
186
- const mod = await import("pusher-js").catch(() => {
187
+ const createPusherTransport = async (config, importModule = defaultImporter) => {
188
+ const mod = await importModule("pusher-js").catch(() => {
187
189
  throw new Error("The \"pusher-js\" package is required for the Pusher transport. Install it with `npm i pusher-js`.");
188
190
  });
189
191
  const client = new (mod.default ?? mod)(config.key, {
@@ -246,11 +248,11 @@ const isFirebaseClientEvent = (value) => {
246
248
  * FCM delivers to the device (not per-channel), so the channel is informational;
247
249
  * messages are matched by `event` and the JSON-encoded payload is parsed back.
248
250
  */
249
- const createFirebaseTransport = async (config) => {
251
+ const createFirebaseTransport = async (config, importModule = defaultImporter) => {
250
252
  const [appMod, messagingMod, databaseMod] = await Promise.all([
251
- import("firebase/app"),
252
- import("firebase/messaging"),
253
- import("firebase/database")
253
+ importModule("firebase/app"),
254
+ importModule("firebase/messaging"),
255
+ importModule("firebase/database")
254
256
  ]).catch(() => {
255
257
  throw new Error("The \"firebase\" package is required for the Firebase transport. Install it with `npm i firebase`.");
256
258
  });
@@ -348,13 +350,14 @@ const tokenUid = (token) => {
348
350
  * written after the transport connected are delivered.
349
351
  *
350
352
  * @param config
353
+ * @param importModule Loads the `firebase` SDK modules.
351
354
  * @returns
352
355
  */
353
- const createFirebaseDatabaseTransport = async (config) => {
356
+ const createFirebaseDatabaseTransport = async (config, importModule = defaultImporter) => {
354
357
  const [appMod, authMod, databaseMod] = await Promise.all([
355
- import("firebase/app"),
356
- import("firebase/auth"),
357
- import("firebase/database")
358
+ importModule("firebase/app"),
359
+ importModule("firebase/auth"),
360
+ importModule("firebase/database")
358
361
  ]).catch(() => {
359
362
  throw new Error("The \"firebase\" package is required for the Firebase database transport. Install it with `npm i firebase`.");
360
363
  });
@@ -1,3 +1,4 @@
1
+ import { t as defaultImporter } from "./importer-0BugD5WV.js";
1
2
  //#region \0rolldown/runtime.js
2
3
  var __defProp = Object.defineProperty;
3
4
  var __exportAll = (all, no_symbols) => {
@@ -65,10 +66,11 @@ const issueDeviceToken = async (messagingMod, messaging, config, options = {}) =
65
66
  *
66
67
  * @param config
67
68
  * @param options
69
+ * @param importModule Loads the `firebase` SDK modules.
68
70
  * @returns
69
71
  */
70
- const requestDeviceToken = async (config, options = {}) => {
71
- const [appMod, messagingMod] = await Promise.all([import("firebase/app"), import("firebase/messaging")]).catch(() => {
72
+ const requestDeviceToken = async (config, options = {}, importModule = defaultImporter) => {
73
+ const [appMod, messagingMod] = await Promise.all([importModule("firebase/app"), importModule("firebase/messaging")]).catch(() => {
72
74
  throw new Error("The \"firebase\" package is required for device tokens. Install it with `npm i firebase`.");
73
75
  });
74
76
  const app = appMod.getApps().find((existing) => existing.name === APP_NAME) ?? appMod.initializeApp(webConfig(config), APP_NAME);
@@ -1,4 +1,4 @@
1
- import { d as RealtimeNotification, t as RealtimeClient } from "../RealtimeClient-CAoMUUkc.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 { d as RealtimeNotification, t as RealtimeClient } from "../RealtimeClient-CAoMUUkc.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.2",
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",