easy-ping 0.7.0 → 0.8.0

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.
Files changed (66) hide show
  1. package/README.md +4 -0
  2. package/dist/{adapter-BJ8Iejvs.d.cts → adapter-CV5Yt2c6.d.cts} +1 -1
  3. package/dist/{adapter-BJ8Iejvs.d.ts → adapter-CV5Yt2c6.d.ts} +1 -1
  4. package/dist/adapters/drizzle.d.cts +1 -1
  5. package/dist/adapters/drizzle.d.ts +1 -1
  6. package/dist/adapters/mongodb.d.cts +2 -2
  7. package/dist/adapters/mongodb.d.ts +2 -2
  8. package/dist/adapters/mysql.d.cts +2 -2
  9. package/dist/adapters/mysql.d.ts +2 -2
  10. package/dist/adapters/postgres.d.cts +2 -2
  11. package/dist/adapters/postgres.d.ts +2 -2
  12. package/dist/adapters/sqlite.d.cts +2 -2
  13. package/dist/adapters/sqlite.d.ts +2 -2
  14. package/dist/client.cjs +7 -2
  15. package/dist/client.cjs.map +1 -1
  16. package/dist/client.js +7 -2
  17. package/dist/client.js.map +1 -1
  18. package/dist/index.d.cts +4 -4
  19. package/dist/index.d.ts +4 -4
  20. package/dist/{plugin-Wwp1nvo0.d.cts → plugin-6S8tpH0n.d.cts} +1 -1
  21. package/dist/{plugin-CSLKKgXT.d.ts → plugin-C4fikLl7.d.ts} +1 -1
  22. package/dist/plugins/digests.d.cts +2 -2
  23. package/dist/plugins/digests.d.ts +2 -2
  24. package/dist/plugins/mobile-push.cjs +307 -0
  25. package/dist/plugins/mobile-push.cjs.map +1 -0
  26. package/dist/plugins/mobile-push.d.cts +117 -0
  27. package/dist/plugins/mobile-push.d.ts +117 -0
  28. package/dist/plugins/mobile-push.js +261 -0
  29. package/dist/plugins/mobile-push.js.map +1 -0
  30. package/dist/plugins/preferences-client.d.cts +2 -2
  31. package/dist/plugins/preferences-client.d.ts +2 -2
  32. package/dist/plugins/preferences-react.d.cts +1 -1
  33. package/dist/plugins/preferences-react.d.ts +1 -1
  34. package/dist/plugins/preferences.cjs +9 -1
  35. package/dist/plugins/preferences.cjs.map +1 -1
  36. package/dist/plugins/preferences.d.cts +2 -2
  37. package/dist/plugins/preferences.d.ts +2 -2
  38. package/dist/plugins/preferences.js +9 -1
  39. package/dist/plugins/preferences.js.map +1 -1
  40. package/dist/plugins/push.d.cts +2 -2
  41. package/dist/plugins/push.d.ts +2 -2
  42. package/dist/plugins/telegram.d.cts +2 -2
  43. package/dist/plugins/telegram.d.ts +2 -2
  44. package/dist/providers/expo-push.cjs +114 -0
  45. package/dist/providers/expo-push.cjs.map +1 -0
  46. package/dist/providers/expo-push.d.cts +61 -0
  47. package/dist/providers/expo-push.d.ts +61 -0
  48. package/dist/providers/expo-push.js +109 -0
  49. package/dist/providers/expo-push.js.map +1 -0
  50. package/dist/providers/web-push.d.cts +2 -2
  51. package/dist/providers/web-push.d.ts +2 -2
  52. package/dist/react-native.cjs +637 -0
  53. package/dist/react-native.cjs.map +1 -0
  54. package/dist/react-native.d.cts +145 -0
  55. package/dist/react-native.d.ts +145 -0
  56. package/dist/react-native.js +609 -0
  57. package/dist/react-native.js.map +1 -0
  58. package/dist/react.cjs +7 -2
  59. package/dist/react.cjs.map +1 -1
  60. package/dist/react.js +7 -2
  61. package/dist/react.js.map +1 -1
  62. package/dist/schema.d.cts +2 -2
  63. package/dist/schema.d.ts +2 -2
  64. package/dist/testing.d.cts +1 -1
  65. package/dist/testing.d.ts +1 -1
  66. package/package.json +26 -3
@@ -0,0 +1,609 @@
1
+ // src/react-native/index.ts
2
+ import { useMemo as useMemo2, useRef as useRef2 } from "react";
3
+ import { AppState } from "react-native";
4
+
5
+ // src/client/index.ts
6
+ var INITIAL = {
7
+ notifications: [],
8
+ unseenCount: 0,
9
+ unreadCount: 0,
10
+ nextCursor: null,
11
+ isLoading: true,
12
+ error: null
13
+ };
14
+ var countUnread = (rows) => rows.reduce((total, row) => row.readAt === null ? total + 1 : total, 0);
15
+ var SHORT_STREAM_MS = 15e3;
16
+ var SHORT_STREAMS_BEFORE_GIVING_UP = 3;
17
+ function createNotifyClient(options = {}) {
18
+ const baseUrl = (options.baseUrl ?? "/api/notifications").replace(/\/$/, "");
19
+ const doFetch = options.fetch ?? ((...args) => globalThis.fetch(...args));
20
+ const limit = options.limit ?? 20;
21
+ const pollIntervalMs = options.pollIntervalMs ?? 15e3;
22
+ const maxPollIntervalMs = options.maxPollIntervalMs ?? 10 * 6e4;
23
+ const safetyNetMs = options.safetyNetMs ?? 5 * 6e4;
24
+ const activeWindowMs = options.activeWindowMs ?? 6e4;
25
+ const now = options.now ?? (() => Date.now());
26
+ const groupKey = options.scope ? `${baseUrl}#${options.scope}` : baseUrl;
27
+ const hasDom = typeof window !== "undefined" && typeof document !== "undefined";
28
+ const transport = options.transport ?? "auto";
29
+ const wantsStream = transport === "sse" || transport === "auto" && hasDom;
30
+ const isHidden = options.isDocumentHidden ?? (() => typeof document !== "undefined" && document.visibilityState === "hidden");
31
+ const locks = options.locks === void 0 ? typeof navigator !== "undefined" ? navigator.locks ?? null : null : options.locks;
32
+ const makeChannel = options.channel === void 0 ? typeof BroadcastChannel !== "undefined" ? () => new BroadcastChannel(`easy-ping:${groupKey}`) : null : options.channel;
33
+ const serviceWorker = options.serviceWorker === void 0 ? typeof navigator !== "undefined" ? navigator.serviceWorker ?? null : null : options.serviceWorker;
34
+ const activityTarget = options.activityTarget === void 0 ? hasDom ? window : null : options.activityTarget;
35
+ let state = INITIAL;
36
+ const listeners = /* @__PURE__ */ new Set();
37
+ let role = "solo";
38
+ let running = false;
39
+ let channel;
40
+ let releaseLock;
41
+ let lockAbort;
42
+ let timer;
43
+ let consecutiveFailures = 0;
44
+ let idleTicks = 0;
45
+ let activeUntil = 0;
46
+ let lastRefreshAt = 0;
47
+ let refreshing;
48
+ let refreshAgain = false;
49
+ let streamAbort;
50
+ let streamConnected = false;
51
+ let streamDisabled = !wantsStream;
52
+ let shortStreams = 0;
53
+ let streamFailures = 0;
54
+ let generation = 0;
55
+ const localReads = /* @__PURE__ */ new Set();
56
+ let localSeen = false;
57
+ const stamp = () => (/* @__PURE__ */ new Date()).toISOString();
58
+ function emit() {
59
+ for (const listener of listeners) listener(state);
60
+ }
61
+ function set(patch) {
62
+ state = { ...state, ...patch };
63
+ state = { ...state, unreadCount: countUnread(state.notifications) };
64
+ emit();
65
+ if (role === "leader") post2({ type: "state", state });
66
+ }
67
+ function post2(message) {
68
+ try {
69
+ channel?.postMessage(message);
70
+ } catch {
71
+ }
72
+ }
73
+ async function request(path, init) {
74
+ const response = await doFetch(`${baseUrl}${path}`, {
75
+ ...init,
76
+ headers: { "content-type": "application/json", ...init?.headers }
77
+ });
78
+ if (!response.ok)
79
+ throw new Error(`${init?.method ?? "GET"} ${path} failed: ${response.status}`);
80
+ return await response.json();
81
+ }
82
+ async function fetchPage() {
83
+ const startedAt = generation;
84
+ const page = await request(`/?limit=${limit}`);
85
+ const unseenCount = page.unseenCount ?? (await request("/count")).unseen;
86
+ if (generation !== startedAt) return;
87
+ set({
88
+ notifications: page.notifications,
89
+ nextCursor: page.nextCursor,
90
+ unseenCount,
91
+ isLoading: false,
92
+ error: null
93
+ });
94
+ }
95
+ function refresh() {
96
+ if (refreshing) {
97
+ refreshAgain = true;
98
+ return refreshing;
99
+ }
100
+ refreshing = (async () => {
101
+ try {
102
+ do {
103
+ refreshAgain = false;
104
+ await fetchPage();
105
+ lastRefreshAt = now();
106
+ } while (refreshAgain);
107
+ } finally {
108
+ refreshing = void 0;
109
+ }
110
+ })();
111
+ return refreshing;
112
+ }
113
+ let loadingMore;
114
+ async function loadMore() {
115
+ if (!state.nextCursor) return;
116
+ if (loadingMore) return loadingMore;
117
+ const cursor = state.nextCursor;
118
+ loadingMore = (async () => {
119
+ try {
120
+ const page = await request(`/?limit=${limit}&cursor=${encodeURIComponent(cursor)}`);
121
+ const known = new Set(state.notifications.map((row) => row.id));
122
+ set({
123
+ notifications: [
124
+ ...state.notifications,
125
+ ...page.notifications.filter((row) => !known.has(row.id))
126
+ ],
127
+ nextCursor: page.nextCursor
128
+ });
129
+ } finally {
130
+ loadingMore = void 0;
131
+ }
132
+ })();
133
+ return loadingMore;
134
+ }
135
+ async function optimistic(apply, patch, send) {
136
+ const previous = state;
137
+ generation += 1;
138
+ set({ notifications: apply(state.notifications), ...patch });
139
+ try {
140
+ await send();
141
+ if (role === "follower") post2({ type: "mutated" });
142
+ } catch (error) {
143
+ generation += 1;
144
+ state = previous;
145
+ emit();
146
+ throw error;
147
+ }
148
+ }
149
+ async function markAsRead(id) {
150
+ if (role === "follower") localReads.add(id);
151
+ await optimistic(
152
+ (rows) => rows.map((row) => row.id === id ? { ...row, readAt: row.readAt ?? stamp() } : row),
153
+ {},
154
+ () => request("/read", { method: "POST", body: JSON.stringify({ ids: [id] }) })
155
+ ).catch((error) => {
156
+ localReads.delete(id);
157
+ throw error;
158
+ });
159
+ }
160
+ async function markAllRead() {
161
+ if (role === "follower") for (const row of state.notifications) localReads.add(row.id);
162
+ await optimistic(
163
+ (rows) => rows.map((row) => ({ ...row, readAt: row.readAt ?? stamp() })),
164
+ {},
165
+ () => request("/read-all", { method: "POST" })
166
+ ).catch((error) => {
167
+ localReads.clear();
168
+ throw error;
169
+ });
170
+ }
171
+ async function markSeen() {
172
+ const newest = state.notifications[0]?.createdAt;
173
+ if (role === "follower") localSeen = true;
174
+ await optimistic(
175
+ (rows) => rows.map((row) => ({ ...row, seenAt: row.seenAt ?? stamp() })),
176
+ { unseenCount: 0 },
177
+ () => request("/seen", {
178
+ method: "POST",
179
+ body: JSON.stringify(newest ? { before: newest } : {})
180
+ })
181
+ ).catch((error) => {
182
+ localSeen = false;
183
+ throw error;
184
+ });
185
+ }
186
+ function applyRemoteState(remote) {
187
+ const notifications = remote.notifications.map((row) => {
188
+ if (!localReads.has(row.id)) return row;
189
+ if (row.readAt) {
190
+ localReads.delete(row.id);
191
+ return row;
192
+ }
193
+ return { ...row, readAt: stamp() };
194
+ });
195
+ if (localSeen && remote.unseenCount === 0) localSeen = false;
196
+ state = {
197
+ ...remote,
198
+ notifications,
199
+ unseenCount: localSeen ? 0 : remote.unseenCount,
200
+ unreadCount: countUnread(notifications)
201
+ };
202
+ emit();
203
+ }
204
+ function onTabMessage(event) {
205
+ const message = event.data;
206
+ if (!message || typeof message !== "object") return;
207
+ if (role === "leader") {
208
+ if (message.type === "hello") post2({ type: "state", state });
209
+ else if (message.type === "mutated") void refresh().catch(() => {
210
+ });
211
+ return;
212
+ }
213
+ if (message.type === "state") applyRemoteState(message.state);
214
+ }
215
+ function schedule(delay) {
216
+ clearTimeout(timer);
217
+ timer = setTimeout(tick, delay);
218
+ }
219
+ function nextDelay() {
220
+ if (consecutiveFailures > 0) {
221
+ return Math.min(pollIntervalMs * 2 ** consecutiveFailures, maxPollIntervalMs);
222
+ }
223
+ if (streamConnected) return safetyNetMs;
224
+ if (now() < activeUntil) return pollIntervalMs;
225
+ idleTicks += 1;
226
+ return Math.min(pollIntervalMs * 2 ** idleTicks, maxPollIntervalMs);
227
+ }
228
+ async function tick() {
229
+ if (!running) return;
230
+ if (isHidden()) {
231
+ schedule(pollIntervalMs);
232
+ return;
233
+ }
234
+ try {
235
+ await refresh();
236
+ consecutiveFailures = 0;
237
+ } catch (error) {
238
+ consecutiveFailures += 1;
239
+ set({ isLoading: false, error: error instanceof Error ? error : new Error(String(error)) });
240
+ }
241
+ if (running) schedule(nextDelay());
242
+ }
243
+ function onActivity() {
244
+ if (!running || role === "follower") return;
245
+ activeUntil = now() + activeWindowMs;
246
+ idleTicks = 0;
247
+ if (!isHidden() && now() - lastRefreshAt > pollIntervalMs) {
248
+ void refresh().catch(() => {
249
+ });
250
+ schedule(nextDelay());
251
+ }
252
+ }
253
+ const onVisibility = () => {
254
+ if (!isHidden()) onActivity();
255
+ };
256
+ const ACTIVITY_EVENTS = ["focus", "pointerdown", "keydown", "online"];
257
+ function changed() {
258
+ if (!running) return;
259
+ void refresh().catch(() => {
260
+ });
261
+ }
262
+ async function readStream(signal) {
263
+ const openedAt = now();
264
+ const response = await doFetch(`${baseUrl}/events`, {
265
+ headers: { accept: "text/event-stream" },
266
+ signal
267
+ });
268
+ if (!response.ok) throw new Error(`GET /events failed: ${response.status}`);
269
+ if (!response.body) {
270
+ streamDisabled = true;
271
+ schedule(nextDelay());
272
+ return;
273
+ }
274
+ const reader = response.body.getReader();
275
+ const decoder = new TextDecoder();
276
+ let buffer = "";
277
+ let eventName = "";
278
+ try {
279
+ for (; ; ) {
280
+ const { done, value } = await reader.read();
281
+ if (done) break;
282
+ buffer += decoder.decode(value, { stream: true });
283
+ let newline = buffer.indexOf("\n");
284
+ while (newline >= 0) {
285
+ const line = buffer.slice(0, newline).replace(/\r$/, "");
286
+ buffer = buffer.slice(newline + 1);
287
+ if (line === "") {
288
+ if (eventName === "ready") {
289
+ streamConnected = true;
290
+ streamFailures = 0;
291
+ schedule(nextDelay());
292
+ } else if (eventName === "changed") changed();
293
+ eventName = "";
294
+ } else if (line.startsWith("event:")) eventName = line.slice(6).trim();
295
+ newline = buffer.indexOf("\n");
296
+ }
297
+ }
298
+ } finally {
299
+ streamConnected = false;
300
+ if (now() - openedAt < SHORT_STREAM_MS) shortStreams += 1;
301
+ else shortStreams = 0;
302
+ }
303
+ }
304
+ const isFollower = () => role === "follower";
305
+ async function streamLoop() {
306
+ while (running && !streamDisabled && !isFollower()) {
307
+ streamAbort = new AbortController();
308
+ try {
309
+ await readStream(streamAbort.signal);
310
+ } catch {
311
+ if (!running) return;
312
+ streamFailures += 1;
313
+ }
314
+ if (!running || isFollower() || streamDisabled) return;
315
+ if (shortStreams >= SHORT_STREAMS_BEFORE_GIVING_UP) {
316
+ streamDisabled = true;
317
+ schedule(nextDelay());
318
+ return;
319
+ }
320
+ await new Promise(
321
+ (resolve) => setTimeout(resolve, Math.min(3e3 * 2 ** streamFailures, 6e4))
322
+ );
323
+ }
324
+ }
325
+ function becomeLeader() {
326
+ role = "leader";
327
+ post2({ type: "leader" });
328
+ post2({ type: "state", state });
329
+ void tick();
330
+ if (!streamDisabled) void streamLoop();
331
+ }
332
+ function becomeFollower() {
333
+ role = "follower";
334
+ post2({ type: "hello" });
335
+ }
336
+ function acquireLeadership() {
337
+ if (!locks || !channel) {
338
+ role = "solo";
339
+ void tick();
340
+ if (!streamDisabled) void streamLoop();
341
+ return;
342
+ }
343
+ becomeFollower();
344
+ lockAbort = new AbortController();
345
+ locks.request(
346
+ `easy-ping:leader:${groupKey}`,
347
+ { mode: "exclusive", signal: lockAbort.signal },
348
+ () => new Promise((resolve) => {
349
+ releaseLock = resolve;
350
+ if (running) becomeLeader();
351
+ else resolve();
352
+ })
353
+ ).catch(() => {
354
+ });
355
+ }
356
+ function start() {
357
+ running = true;
358
+ channel = makeChannel ? makeChannel() : void 0;
359
+ channel?.addEventListener("message", onTabMessage);
360
+ serviceWorker?.addEventListener("message", onRelay);
361
+ if (activityTarget) {
362
+ for (const name of ACTIVITY_EVENTS) activityTarget.addEventListener(name, onActivity);
363
+ }
364
+ if (typeof document !== "undefined") {
365
+ document.addEventListener("visibilitychange", onVisibility);
366
+ }
367
+ activeUntil = now() + activeWindowMs;
368
+ acquireLeadership();
369
+ }
370
+ function stop() {
371
+ running = false;
372
+ clearTimeout(timer);
373
+ timer = void 0;
374
+ streamAbort?.abort();
375
+ streamAbort = void 0;
376
+ lockAbort?.abort();
377
+ lockAbort = void 0;
378
+ releaseLock?.();
379
+ releaseLock = void 0;
380
+ channel?.removeEventListener("message", onTabMessage);
381
+ channel?.close();
382
+ channel = void 0;
383
+ serviceWorker?.removeEventListener("message", onRelay);
384
+ if (activityTarget) {
385
+ for (const name of ACTIVITY_EVENTS) activityTarget.removeEventListener(name, onActivity);
386
+ }
387
+ if (typeof document !== "undefined") {
388
+ document.removeEventListener("visibilitychange", onVisibility);
389
+ }
390
+ role = "solo";
391
+ }
392
+ function onRelay(event) {
393
+ const data = event.data;
394
+ if (data?.source === "easy-ping" && data.type === "changed") changed();
395
+ }
396
+ let lastInboxVersion = null;
397
+ const sameOrigin = (response) => {
398
+ if (!response.url || typeof location === "undefined") return true;
399
+ try {
400
+ return new URL(response.url).origin === location.origin;
401
+ } catch {
402
+ return false;
403
+ }
404
+ };
405
+ function instrument(inner) {
406
+ return (async (...args) => {
407
+ const response = await inner(...args);
408
+ const version = sameOrigin(response) ? response.headers.get("x-easy-ping-inbox") : null;
409
+ if (version !== null && version !== lastInboxVersion) {
410
+ const first = lastInboxVersion === null;
411
+ lastInboxVersion = version;
412
+ if (!first && role !== "follower") changed();
413
+ }
414
+ return response;
415
+ });
416
+ }
417
+ function subscribe(listener) {
418
+ listeners.add(listener);
419
+ listener(state);
420
+ if (listeners.size === 1) start();
421
+ return () => {
422
+ listeners.delete(listener);
423
+ if (listeners.size === 0) stop();
424
+ };
425
+ }
426
+ return {
427
+ subscribe,
428
+ refresh,
429
+ loadMore,
430
+ markAsRead,
431
+ markAllRead,
432
+ markSeen,
433
+ instrument,
434
+ getState: () => state,
435
+ getTransport: () => ({
436
+ role,
437
+ transport: streamDisabled ? "poll" : "sse",
438
+ connected: streamConnected
439
+ })
440
+ };
441
+ }
442
+
443
+ // src/react/index.ts
444
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
445
+ function useNotifications(options = {}) {
446
+ const {
447
+ client: provided,
448
+ baseUrl,
449
+ limit,
450
+ pollIntervalMs,
451
+ maxPollIntervalMs,
452
+ safetyNetMs,
453
+ activeWindowMs,
454
+ transport
455
+ } = options;
456
+ const callbacksRef = useRef(options);
457
+ callbacksRef.current = options;
458
+ const client = useMemo(
459
+ () => provided ?? createNotifyClient({
460
+ baseUrl,
461
+ limit,
462
+ pollIntervalMs,
463
+ maxPollIntervalMs,
464
+ safetyNetMs,
465
+ activeWindowMs,
466
+ transport,
467
+ fetch: (...args) => (callbacksRef.current.fetch ?? globalThis.fetch)(...args),
468
+ isDocumentHidden: () => callbacksRef.current.isDocumentHidden?.() ?? (typeof document !== "undefined" && document.visibilityState === "hidden")
469
+ }),
470
+ [
471
+ provided,
472
+ baseUrl,
473
+ limit,
474
+ pollIntervalMs,
475
+ maxPollIntervalMs,
476
+ safetyNetMs,
477
+ activeWindowMs,
478
+ transport
479
+ ]
480
+ );
481
+ const [state, setState] = useState(() => client.getState());
482
+ useEffect(() => client.subscribe(setState), [client]);
483
+ return {
484
+ ...state,
485
+ markAsRead: useCallback((id) => client.markAsRead(id), [client]),
486
+ markAllRead: useCallback(() => client.markAllRead(), [client]),
487
+ markSeen: useCallback(() => client.markSeen(), [client]),
488
+ loadMore: useCallback(() => client.loadMore(), [client]),
489
+ refresh: useCallback(() => client.refresh(), [client])
490
+ };
491
+ }
492
+
493
+ // src/react-native/index.ts
494
+ function appStateTarget(appState) {
495
+ const listeners = /* @__PURE__ */ new Map();
496
+ let subscription;
497
+ let last = appState.currentState;
498
+ const ensure = () => {
499
+ subscription ??= appState.addEventListener("change", (state) => {
500
+ if (state === "active" && last !== "active") {
501
+ for (const listener of listeners.get("focus") ?? []) listener({});
502
+ }
503
+ last = state;
504
+ });
505
+ };
506
+ return {
507
+ addEventListener(type, handler) {
508
+ listeners.set(type, (listeners.get(type) ?? /* @__PURE__ */ new Set()).add(handler));
509
+ ensure();
510
+ },
511
+ removeEventListener(type, handler) {
512
+ listeners.get(type)?.delete(handler);
513
+ if ([...listeners.values()].every((set) => set.size === 0)) {
514
+ subscription?.remove();
515
+ subscription = void 0;
516
+ }
517
+ }
518
+ };
519
+ }
520
+ function createNativeNotifyClient(options = {}) {
521
+ const { appState = AppState, ...rest } = options;
522
+ return createNotifyClient({
523
+ transport: "sse",
524
+ ...rest,
525
+ locks: null,
526
+ channel: null,
527
+ serviceWorker: null,
528
+ isDocumentHidden: () => appState.currentState !== "active",
529
+ activityTarget: appStateTarget(appState)
530
+ });
531
+ }
532
+ function useNotifications2(options = {}) {
533
+ const {
534
+ client: provided,
535
+ baseUrl,
536
+ limit,
537
+ pollIntervalMs,
538
+ maxPollIntervalMs,
539
+ safetyNetMs,
540
+ activeWindowMs,
541
+ transport,
542
+ scope,
543
+ appState
544
+ } = options;
545
+ const fetchRef = useRef2(options.fetch);
546
+ fetchRef.current = options.fetch;
547
+ const client = useMemo2(
548
+ () => provided ?? createNativeNotifyClient({
549
+ baseUrl,
550
+ limit,
551
+ pollIntervalMs,
552
+ maxPollIntervalMs,
553
+ safetyNetMs,
554
+ activeWindowMs,
555
+ transport,
556
+ scope,
557
+ appState,
558
+ fetch: (...args) => (fetchRef.current ?? globalThis.fetch)(...args)
559
+ }),
560
+ [
561
+ provided,
562
+ baseUrl,
563
+ limit,
564
+ pollIntervalMs,
565
+ maxPollIntervalMs,
566
+ safetyNetMs,
567
+ activeWindowMs,
568
+ transport,
569
+ scope,
570
+ appState
571
+ ]
572
+ );
573
+ return useNotifications({ client });
574
+ }
575
+ var post = async (doFetch, url, body) => doFetch(url, {
576
+ method: "POST",
577
+ headers: { "content-type": "application/json" },
578
+ body: JSON.stringify(body)
579
+ });
580
+ async function registerMobilePushDevice(options) {
581
+ const base = (options.baseUrl ?? "/api/notifications").replace(/\/$/, "");
582
+ const doFetch = options.fetch ?? globalThis.fetch;
583
+ const response = await post(doFetch, `${base}/mobile-push/devices`, {
584
+ token: options.token,
585
+ platform: options.platform,
586
+ deviceName: options.deviceName
587
+ });
588
+ if (!response.ok) {
589
+ const detail = await response.json().catch(() => ({}));
590
+ throw new Error(detail.error ?? `registering the device failed: ${response.status}`);
591
+ }
592
+ }
593
+ async function unregisterMobilePushDevice(options) {
594
+ const base = (options.baseUrl ?? "/api/notifications").replace(/\/$/, "");
595
+ const doFetch = options.fetch ?? globalThis.fetch;
596
+ const response = await post(doFetch, `${base}/mobile-push/devices/remove`, {
597
+ token: options.token
598
+ });
599
+ if (response.status === 404) return false;
600
+ if (!response.ok) throw new Error(`unregistering the device failed: ${response.status}`);
601
+ return true;
602
+ }
603
+ export {
604
+ createNativeNotifyClient,
605
+ registerMobilePushDevice,
606
+ unregisterMobilePushDevice,
607
+ useNotifications2 as useNotifications
608
+ };
609
+ //# sourceMappingURL=react-native.js.map