@flighthq/notification 0.1.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.
@@ -0,0 +1,2 @@
1
+ export * from './notification';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './notification';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { NotificationBackend, NotificationCapabilities, NotificationChannel, NotificationPermission, NotificationRequest, NotificationSchedule, ScheduledNotification } from '@flighthq/types';
2
+ interface ServiceWorkerRegistrationLike {
3
+ showNotification(title: string, options?: NotificationOptions): Promise<void>;
4
+ getNotifications(filter?: {
5
+ tag?: string;
6
+ }): Promise<ReadonlyArray<{
7
+ title: string;
8
+ close(): void;
9
+ tag: string;
10
+ }>>;
11
+ }
12
+ export declare function cancelScheduledNotification(id: string): void;
13
+ export declare function closeAllNotifications(): void;
14
+ export declare function closeNotification(id: string): void;
15
+ export declare function createNotificationChannel(channel: Readonly<NotificationChannel>): void;
16
+ export declare function createServiceWorkerNotificationBackend(registration: ServiceWorkerRegistrationLike): NotificationBackend;
17
+ export declare function createWebNotificationBackend(): NotificationBackend;
18
+ export declare function deleteNotificationChannel(id: string): void;
19
+ export declare function getActiveNotifications(): Promise<ReadonlyArray<Readonly<NotificationRequest>>>;
20
+ export declare function getLaunchNotification(): Promise<Readonly<NotificationRequest> | null>;
21
+ export declare function getNotificationBackend(): NotificationBackend;
22
+ export declare function getNotificationCapabilities(): NotificationCapabilities;
23
+ export declare function getNotificationChannels(): ReadonlyArray<Readonly<NotificationChannel>>;
24
+ export declare function getNotificationPermission(): NotificationPermission;
25
+ export declare function getPendingNotifications(): Promise<ReadonlyArray<Readonly<ScheduledNotification>>>;
26
+ export declare function isNotificationSupported(): boolean;
27
+ export declare function notifyServiceWorkerBackendAction(backend: NotificationBackend, message: Readonly<{
28
+ type: string;
29
+ notificationId: string;
30
+ actionId?: string;
31
+ reply?: string;
32
+ }>): void;
33
+ export declare function onNotificationAction(listener: (id: string, actionId: string) => void): () => void;
34
+ export declare function onNotificationClick(listener: (id: string) => void): () => void;
35
+ export declare function onNotificationDismiss(listener: (id: string) => void): () => void;
36
+ export declare function onNotificationReply(listener: (id: string, actionId: string, text: string) => void): () => void;
37
+ export declare function onNotificationShow(listener: (id: string) => void): () => void;
38
+ export declare function requestNotificationPermission(): Promise<NotificationPermission>;
39
+ export declare function scheduleNotification(request: Readonly<NotificationRequest>, schedule: Readonly<NotificationSchedule>): Promise<string>;
40
+ export declare function setNotificationBackend(backend: NotificationBackend | null): void;
41
+ export declare function showNotification(request: Readonly<NotificationRequest>): Promise<string>;
42
+ export declare function updateNotification(id: string, partial: Readonly<Partial<NotificationRequest>>): Promise<boolean>;
43
+ export {};
44
+ //# sourceMappingURL=notification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.d.ts","sourceRoot":"","sources":["../src/notification.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,603 @@
1
+ // Cancels a pending scheduled notification by id. No-ops when the id is unknown.
2
+ export function cancelScheduledNotification(id) {
3
+ getNotificationBackend().cancelScheduledNotification(id);
4
+ }
5
+ // Dismisses all currently-shown notifications.
6
+ export function closeAllNotifications() {
7
+ getNotificationBackend().closeAllNotifications();
8
+ }
9
+ // Dismisses the notification with the given id. No-ops when the id is unknown or already dismissed.
10
+ export function closeNotification(id) {
11
+ getNotificationBackend().closeNotification(id);
12
+ }
13
+ // Creates or updates a notification channel. No-ops on backends without channel support.
14
+ export function createNotificationChannel(channel) {
15
+ const backend = getNotificationBackend();
16
+ backend.createNotificationChannel?.(channel);
17
+ }
18
+ // Builds a web backend backed by the Service Worker Notifications API. Unlike the basic web
19
+ // backend, this variant can deliver action-button activations because it uses
20
+ // `registration.showNotification`, which triggers the SW `notificationclick` event globally.
21
+ //
22
+ // The caller must supply an active ServiceWorkerRegistration (e.g. from
23
+ // `navigator.serviceWorker.ready`). The backend uses `registration.showNotification` and
24
+ // `registration.getNotifications` to manage notification identity and lifecycle.
25
+ //
26
+ // Action delivery requires a service worker that forwards `notificationclick` messages back to the
27
+ // page. Wire the SW side like this:
28
+ //
29
+ // self.addEventListener('notificationclick', (event) => {
30
+ // event.notification.close();
31
+ // const { notificationId, actionId } = event.notification.data ?? {};
32
+ // if (event.source) event.source.postMessage({ type: 'notificationclick', notificationId, actionId });
33
+ // });
34
+ //
35
+ // Then call `notifyServiceWorkerBackendAction(backend, event.data)` in the page's message handler.
36
+ // This two-step wiring is necessary because SW events fire in the worker thread, not the page.
37
+ export function createServiceWorkerNotificationBackend(registration) {
38
+ let _idCounter = 0;
39
+ const _clickListeners = new Set();
40
+ const _actionListeners = new Set();
41
+ const _dismissListeners = new Set();
42
+ const _replyListeners = new Set();
43
+ const _showListeners = new Set();
44
+ const _scheduled = new Map();
45
+ function _generateId() {
46
+ _idCounter += 1;
47
+ return `sw-notif-${_idCounter}`;
48
+ }
49
+ function _fire(listeners, arg) {
50
+ for (const fn of listeners)
51
+ fn(arg);
52
+ }
53
+ async function _show(request) {
54
+ if (typeof Notification === 'undefined')
55
+ return '';
56
+ try {
57
+ if (Notification.permission !== 'granted')
58
+ return '';
59
+ const id = request.id ?? _generateId();
60
+ await registration.showNotification(request.title, {
61
+ body: request.body,
62
+ badge: request.badge,
63
+ dir: request.dir,
64
+ icon: request.icon,
65
+ image: request.image,
66
+ lang: request.lang,
67
+ renotify: request.renotify,
68
+ requireInteraction: request.requireInteraction,
69
+ silent: request.silent,
70
+ tag: request.tag ?? id,
71
+ timestamp: request.timestamp,
72
+ vibrate: request.vibrate ? [...request.vibrate] : undefined,
73
+ // Store the Flight id in data so SW click handlers can forward it back.
74
+ data: { ...request.data, notificationId: id },
75
+ actions: request.actions?.map((a) => ({ action: a.id, title: a.title, icon: a.icon })),
76
+ });
77
+ _fire(_showListeners, id);
78
+ return id;
79
+ }
80
+ catch {
81
+ return '';
82
+ }
83
+ }
84
+ const backend = {
85
+ cancelScheduledNotification(id) {
86
+ const entry = _scheduled.get(id);
87
+ if (entry !== undefined) {
88
+ clearTimeout(entry.timeout);
89
+ _scheduled.delete(id);
90
+ }
91
+ },
92
+ async closeAllNotifications() {
93
+ try {
94
+ const notifications = await registration.getNotifications();
95
+ for (const n of notifications) {
96
+ n.close();
97
+ }
98
+ }
99
+ catch {
100
+ // Guarded.
101
+ }
102
+ },
103
+ async closeNotification(id) {
104
+ try {
105
+ const notifications = await registration.getNotifications({ tag: id });
106
+ for (const n of notifications) {
107
+ n.close();
108
+ }
109
+ }
110
+ catch {
111
+ // Guarded.
112
+ }
113
+ },
114
+ getCapabilities() {
115
+ return {
116
+ actions: true,
117
+ channels: false,
118
+ coldStart: false,
119
+ image: true,
120
+ listActive: true,
121
+ scheduling: true,
122
+ textReply: false,
123
+ };
124
+ },
125
+ async getLaunchNotification() {
126
+ return null;
127
+ },
128
+ async getActiveNotifications() {
129
+ try {
130
+ const notifications = await registration.getNotifications();
131
+ return notifications.map((n) => ({ title: n.title, tag: n.tag }));
132
+ }
133
+ catch {
134
+ return [];
135
+ }
136
+ },
137
+ async getPendingNotifications() {
138
+ return Array.from(_scheduled.values()).map((e) => e.entry);
139
+ },
140
+ getPermission() {
141
+ if (typeof Notification === 'undefined')
142
+ return 'denied';
143
+ return Notification.permission;
144
+ },
145
+ isSupported() {
146
+ return typeof Notification !== 'undefined' && 'serviceWorker' in navigator;
147
+ },
148
+ notify: _show,
149
+ async requestPermission() {
150
+ if (typeof Notification === 'undefined' || typeof Notification.requestPermission !== 'function')
151
+ return 'denied';
152
+ try {
153
+ return (await Notification.requestPermission());
154
+ }
155
+ catch {
156
+ return 'denied';
157
+ }
158
+ },
159
+ async scheduleNotification(request, schedule) {
160
+ const id = request.id ?? _generateId();
161
+ const delay = Math.max(0, schedule.at - Date.now());
162
+ const entry = { id, request, schedule };
163
+ const fireAndReschedule = () => {
164
+ _scheduled.delete(id);
165
+ void _show({ ...request, id });
166
+ if (schedule.repeat !== undefined) {
167
+ const ms = _repeatMs(schedule.repeat);
168
+ const timeout = setTimeout(fireAndReschedule, ms);
169
+ _scheduled.set(id, {
170
+ timeout,
171
+ entry: { id, request, schedule: { ...schedule, at: Date.now() + ms } },
172
+ });
173
+ }
174
+ };
175
+ const timeout = setTimeout(fireAndReschedule, delay);
176
+ _scheduled.set(id, { timeout, entry });
177
+ return id;
178
+ },
179
+ subscribeAction(listener) {
180
+ _actionListeners.add(listener);
181
+ return () => {
182
+ _actionListeners.delete(listener);
183
+ };
184
+ },
185
+ subscribeClick(listener) {
186
+ _clickListeners.add(listener);
187
+ return () => {
188
+ _clickListeners.delete(listener);
189
+ };
190
+ },
191
+ subscribeDismiss(listener) {
192
+ _dismissListeners.add(listener);
193
+ return () => {
194
+ _dismissListeners.delete(listener);
195
+ };
196
+ },
197
+ subscribeReply(listener) {
198
+ _replyListeners.add(listener);
199
+ return () => {
200
+ _replyListeners.delete(listener);
201
+ };
202
+ },
203
+ subscribeShow(listener) {
204
+ _showListeners.add(listener);
205
+ return () => {
206
+ _showListeners.delete(listener);
207
+ };
208
+ },
209
+ async updateNotification(id, partial) {
210
+ // Close existing and re-show with merged fields.
211
+ await backend.closeNotification(id);
212
+ // Merge the partial on top; since we don't retain the original request in the SW backend
213
+ // (the SW doesn't expose it), callers must include at least a title in partial or this
214
+ // cannot reconstruct the notification.
215
+ const merged = { ...partial, id };
216
+ if (merged.title === undefined)
217
+ return false;
218
+ await _show(merged);
219
+ return true;
220
+ },
221
+ };
222
+ // Exposes internal dispatch hooks so page-side message handlers can forward SW events.
223
+ // Use notifyServiceWorkerBackendAction() rather than calling these directly.
224
+ const internal = backend;
225
+ internal._dispatchAction = (notificationId, actionId) => {
226
+ // Action button tap: deliver to both action listeners (id + actionId) and click listeners (id).
227
+ for (const fn of _actionListeners)
228
+ fn(notificationId, actionId);
229
+ _fire(_clickListeners, notificationId);
230
+ };
231
+ internal._dispatchClick = (notificationId) => {
232
+ _fire(_clickListeners, notificationId);
233
+ };
234
+ internal._dispatchDismiss = (notificationId) => {
235
+ _fire(_dismissListeners, notificationId);
236
+ };
237
+ internal._dispatchReply = (notificationId, actionId, text) => {
238
+ // Inline-reply submission (a `text-input` action): deliver the reply text to reply listeners.
239
+ for (const fn of _replyListeners)
240
+ fn(notificationId, actionId, text);
241
+ };
242
+ return backend;
243
+ }
244
+ // Builds the default web backend over the global Notification API. Every touch is guarded for hosts
245
+ // where Notification is absent (jsdom, non-secure contexts) and wrapped in try/catch; notify and
246
+ // requestPermission resolve to '' / 'default' when unsupported or denied rather than throwing.
247
+ // Per-instance onclick is wired so clicks are actually delivered; action buttons require the
248
+ // service-worker backend variant and never fire on this basic backend.
249
+ export function createWebNotificationBackend() {
250
+ // Live map of id → Notification instance so we can close them and wire onclick.
251
+ const _live = new Map();
252
+ // Live request registry: id → the original request, needed to merge partial updates.
253
+ const _requests = new Map();
254
+ const _clickListeners = new Set();
255
+ const _actionListeners = new Set();
256
+ const _dismissListeners = new Set();
257
+ const _replyListeners = new Set();
258
+ const _showListeners = new Set();
259
+ // Scheduled-notification registry (best-effort setTimeout — cleared on page reload).
260
+ const _scheduled = new Map();
261
+ let _idCounter = 0;
262
+ function _generateId() {
263
+ _idCounter += 1;
264
+ return `web-notif-${_idCounter}`;
265
+ }
266
+ function _fire(listeners, arg) {
267
+ for (const fn of listeners)
268
+ fn(arg);
269
+ }
270
+ // Extracted as a closure function so scheduleNotification can call it without a `this` reference.
271
+ async function _notify(request) {
272
+ if (typeof Notification === 'undefined')
273
+ return '';
274
+ try {
275
+ if (Notification.permission !== 'granted')
276
+ return '';
277
+ const id = request.id ?? _generateId();
278
+ const n = new Notification(request.title, {
279
+ body: request.body,
280
+ badge: request.badge,
281
+ data: request.data,
282
+ dir: request.dir,
283
+ icon: request.icon,
284
+ image: request.image,
285
+ lang: request.lang,
286
+ renotify: request.renotify,
287
+ requireInteraction: request.requireInteraction,
288
+ silent: request.silent,
289
+ tag: request.tag ?? id,
290
+ timestamp: request.timestamp,
291
+ vibrate: request.vibrate ? [...request.vibrate] : undefined,
292
+ });
293
+ _live.set(id, n);
294
+ _requests.set(id, request);
295
+ n.onshow = () => {
296
+ _fire(_showListeners, id);
297
+ };
298
+ n.onclick = () => {
299
+ _fire(_clickListeners, id);
300
+ };
301
+ n.onclose = () => {
302
+ _live.delete(id);
303
+ _requests.delete(id);
304
+ _fire(_dismissListeners, id);
305
+ };
306
+ n.onerror = () => {
307
+ _live.delete(id);
308
+ _requests.delete(id);
309
+ };
310
+ return id;
311
+ }
312
+ catch {
313
+ return '';
314
+ }
315
+ }
316
+ return {
317
+ cancelScheduledNotification(id) {
318
+ const entry = _scheduled.get(id);
319
+ if (entry !== undefined) {
320
+ clearTimeout(entry.timeout);
321
+ _scheduled.delete(id);
322
+ }
323
+ },
324
+ closeAllNotifications() {
325
+ for (const [id, n] of _live) {
326
+ try {
327
+ n.close();
328
+ }
329
+ catch {
330
+ // Guarded — some browsers disallow closing programmatically.
331
+ }
332
+ _live.delete(id);
333
+ _requests.delete(id);
334
+ }
335
+ },
336
+ closeNotification(id) {
337
+ const n = _live.get(id);
338
+ if (n !== undefined) {
339
+ try {
340
+ n.close();
341
+ }
342
+ catch {
343
+ // Guarded.
344
+ }
345
+ _live.delete(id);
346
+ _requests.delete(id);
347
+ }
348
+ },
349
+ getCapabilities() {
350
+ return {
351
+ actions: false,
352
+ channels: false,
353
+ coldStart: false,
354
+ image: false,
355
+ listActive: false,
356
+ scheduling: true,
357
+ textReply: false,
358
+ };
359
+ },
360
+ async getLaunchNotification() {
361
+ // Web pages are not launched from a notification; always null.
362
+ return null;
363
+ },
364
+ async getActiveNotifications() {
365
+ return [];
366
+ },
367
+ async getPendingNotifications() {
368
+ return Array.from(_scheduled.values()).map((e) => e.entry);
369
+ },
370
+ getPermission() {
371
+ if (typeof Notification === 'undefined')
372
+ return 'denied';
373
+ const p = Notification.permission;
374
+ // The web API uses 'default' / 'granted' / 'denied' — same union values.
375
+ return p;
376
+ },
377
+ isSupported() {
378
+ return typeof Notification !== 'undefined';
379
+ },
380
+ notify: _notify,
381
+ async requestPermission() {
382
+ if (typeof Notification === 'undefined' || typeof Notification.requestPermission !== 'function')
383
+ return 'denied';
384
+ try {
385
+ const result = (await Notification.requestPermission());
386
+ return result;
387
+ }
388
+ catch {
389
+ return 'denied';
390
+ }
391
+ },
392
+ async scheduleNotification(request, schedule) {
393
+ const id = request.id ?? _generateId();
394
+ const delay = Math.max(0, schedule.at - Date.now());
395
+ const entry = { id, request, schedule };
396
+ const fireAndReschedule = () => {
397
+ _scheduled.delete(id);
398
+ void _notify({ ...request, id });
399
+ if (schedule.repeat !== undefined) {
400
+ const ms = _repeatMs(schedule.repeat);
401
+ const timeout = setTimeout(fireAndReschedule, ms);
402
+ _scheduled.set(id, {
403
+ timeout,
404
+ entry: { id, request, schedule: { ...schedule, at: Date.now() + ms } },
405
+ });
406
+ }
407
+ };
408
+ const timeout = setTimeout(fireAndReschedule, delay);
409
+ _scheduled.set(id, { timeout, entry });
410
+ return id;
411
+ },
412
+ subscribeAction(listener) {
413
+ // Web notifications have no global action feed — action buttons require the Service Worker
414
+ // Notifications API. A native host (Electron/Tauri) or the SW backend is required.
415
+ _actionListeners.add(listener);
416
+ return () => {
417
+ _actionListeners.delete(listener);
418
+ };
419
+ },
420
+ subscribeClick(listener) {
421
+ _clickListeners.add(listener);
422
+ return () => {
423
+ _clickListeners.delete(listener);
424
+ };
425
+ },
426
+ subscribeDismiss(listener) {
427
+ _dismissListeners.add(listener);
428
+ return () => {
429
+ _dismissListeners.delete(listener);
430
+ };
431
+ },
432
+ subscribeReply(listener) {
433
+ // Inline reply requires the SW backend; this basic backend never fires it.
434
+ _replyListeners.add(listener);
435
+ return () => {
436
+ _replyListeners.delete(listener);
437
+ };
438
+ },
439
+ subscribeShow(listener) {
440
+ _showListeners.add(listener);
441
+ return () => {
442
+ _showListeners.delete(listener);
443
+ };
444
+ },
445
+ async updateNotification(id, partial) {
446
+ // The basic Notification API has no update mechanism. Simulate by closing and re-opening.
447
+ const existing = _live.get(id);
448
+ const originalRequest = _requests.get(id);
449
+ if (existing === undefined || originalRequest === undefined)
450
+ return false;
451
+ try {
452
+ existing.close();
453
+ _live.delete(id);
454
+ _requests.delete(id);
455
+ }
456
+ catch {
457
+ // Guarded — some browsers disallow closing programmatically.
458
+ }
459
+ // Merge the partial fields into the original request, preserving the stable id.
460
+ const merged = { ...originalRequest, ...partial, id };
461
+ await _notify(merged);
462
+ return true;
463
+ },
464
+ };
465
+ }
466
+ // Deletes the notification channel with the given id. No-ops on backends without channel support.
467
+ export function deleteNotificationChannel(id) {
468
+ const backend = getNotificationBackend();
469
+ backend.deleteNotificationChannel?.(id);
470
+ }
471
+ // Returns all currently-displayed notifications. Returns an empty array on backends that do not
472
+ // support active-notification introspection.
473
+ export function getActiveNotifications() {
474
+ return getNotificationBackend().getActiveNotifications();
475
+ }
476
+ // Returns the notification the app was launched from, or null when the app was not launched via
477
+ // a notification tap. On web this always returns null.
478
+ export function getLaunchNotification() {
479
+ return getNotificationBackend().getLaunchNotification();
480
+ }
481
+ // The active notification backend, or a lazily-created web default. There is always a backend.
482
+ export function getNotificationBackend() {
483
+ if (_backend === null)
484
+ _backend = createWebNotificationBackend();
485
+ return _backend;
486
+ }
487
+ // Returns a plain-data record of what the active backend supports.
488
+ export function getNotificationCapabilities() {
489
+ return getNotificationBackend().getCapabilities();
490
+ }
491
+ // Returns the list of currently-registered notification channels. Returns an empty array on
492
+ // backends that do not support channels (e.g. web).
493
+ export function getNotificationChannels() {
494
+ const backend = getNotificationBackend();
495
+ return backend.getNotificationChannels?.() ?? [];
496
+ }
497
+ // Returns the current notification permission state: 'default' (not yet asked), 'granted', or 'denied'.
498
+ export function getNotificationPermission() {
499
+ return getNotificationBackend().getPermission();
500
+ }
501
+ // Returns all locally-scheduled (not yet delivered) notifications.
502
+ export function getPendingNotifications() {
503
+ return getNotificationBackend().getPendingNotifications();
504
+ }
505
+ // True when the host can show notifications. Cheap; reads the active backend.
506
+ export function isNotificationSupported() {
507
+ return getNotificationBackend().isSupported();
508
+ }
509
+ // Forwards a service-worker click/action event received via postMessage to the given SW backend's
510
+ // listeners. Call this from your page's `navigator.serviceWorker.addEventListener('message', ...)`
511
+ // handler when the SW posts a `{ type: 'notificationclick', notificationId, actionId?, reply? }`
512
+ // message. When `reply` is present alongside `actionId` (a `text-input` action), the reply text is
513
+ // routed to onNotificationReply listeners; otherwise an `actionId` routes to action listeners and a
514
+ // bare message routes to click listeners.
515
+ export function notifyServiceWorkerBackendAction(backend, message) {
516
+ if (message.type !== 'notificationclick')
517
+ return;
518
+ const b = backend;
519
+ if (message.actionId !== undefined && message.reply !== undefined && b._dispatchReply !== undefined) {
520
+ b._dispatchReply(message.notificationId, message.actionId, message.reply);
521
+ }
522
+ else if (message.actionId !== undefined && b._dispatchAction !== undefined) {
523
+ b._dispatchAction(message.notificationId, message.actionId);
524
+ }
525
+ else if (b._dispatchClick !== undefined) {
526
+ b._dispatchClick(message.notificationId);
527
+ }
528
+ }
529
+ // Subscribes to notification action-button activations, delivering the notification id and action id.
530
+ // Returns an unsubscribe function. On the basic web backend this never fires; a native host or the
531
+ // service-worker web backend is required for action delivery.
532
+ export function onNotificationAction(listener) {
533
+ return getNotificationBackend().subscribeAction(listener);
534
+ }
535
+ // Subscribes to notification body clicks, delivering the notification id.
536
+ // Returns an unsubscribe function. On the basic web backend clicks are delivered per-instance via
537
+ // the onclick event (wired by createWebNotificationBackend); a native host delivers a global feed.
538
+ export function onNotificationClick(listener) {
539
+ return getNotificationBackend().subscribeClick(listener);
540
+ }
541
+ // Subscribes to notification dismiss/close events, delivering the notification id.
542
+ // Returns an unsubscribe function.
543
+ export function onNotificationDismiss(listener) {
544
+ return getNotificationBackend().subscribeDismiss(listener);
545
+ }
546
+ // Subscribes to inline-reply text actions, delivering the notification id, action id, and reply text.
547
+ // Returns an unsubscribe function. Requires native host or service-worker backend; never fires on
548
+ // the basic web backend.
549
+ export function onNotificationReply(listener) {
550
+ return getNotificationBackend().subscribeReply(listener);
551
+ }
552
+ // Subscribes to notification show events, delivering the notification id. Returns an unsubscribe function.
553
+ export function onNotificationShow(listener) {
554
+ return getNotificationBackend().subscribeShow(listener);
555
+ }
556
+ // Requests notification permission. Returns the tri-state result: 'granted', 'denied', or 'default'
557
+ // (user dismissed the prompt without deciding). 'denied' is also returned when the host lacks the
558
+ // notification surface. Use getNotificationPermission() to read the current state without prompting.
559
+ export function requestNotificationPermission() {
560
+ return getNotificationBackend().requestPermission();
561
+ }
562
+ // Channel management — no-ops on web (no channel model); load-bearing on Android-class native hosts.
563
+ // Schedules a local notification for delivery at the time specified in the schedule. Returns the
564
+ // notification id (echoes request.id when provided, else a generated id). Returns '' when
565
+ // scheduling is not supported. On the basic web backend this uses setTimeout (best-effort,
566
+ // cleared on page reload); native backends use the OS scheduler.
567
+ export function scheduleNotification(request, schedule) {
568
+ return getNotificationBackend().scheduleNotification(request, schedule);
569
+ }
570
+ // Installs a native host notification backend; pass null to fall back to the web default.
571
+ export function setNotificationBackend(backend) {
572
+ _backend = backend;
573
+ }
574
+ // Shows a notification. Returns the notification id (echoes request.id when provided, else a
575
+ // generated id). Returns '' when permission is not granted or the host lacks the surface.
576
+ export function showNotification(request) {
577
+ return getNotificationBackend().notify(request);
578
+ }
579
+ // Updates a live notification. Partial fields are merged into the existing notification — useful for
580
+ // progress bars and live-content updates (e.g. download percentage). Returns true when the update
581
+ // was applied; false when the notification is no longer visible or the backend does not support
582
+ // updates. The basic web backend simulates updates by closing and re-opening the notification.
583
+ export function updateNotification(id, partial) {
584
+ return getNotificationBackend().updateNotification(id, partial);
585
+ }
586
+ function _repeatMs(repeat) {
587
+ switch (repeat) {
588
+ case 'minute':
589
+ return 60_000;
590
+ case 'hour':
591
+ return 3_600_000;
592
+ case 'day':
593
+ return 86_400_000;
594
+ case 'week':
595
+ return 604_800_000;
596
+ case 'month':
597
+ return 2_592_000_000;
598
+ case 'year':
599
+ return 31_536_000_000;
600
+ }
601
+ }
602
+ let _backend = null;
603
+ //# sourceMappingURL=notification.js.map