@phoenix-iq/notifications 1.0.0 → 1.2.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.
package/dist/index.js CHANGED
@@ -1,13 +1,23 @@
1
+ import { mountBanner } from './banner.js';
2
+ import { mountBell } from './bell.js';
1
3
  import { keys, store } from './store.js';
2
4
  /**
3
5
  * Phoenix Notifications web SDK: subscribes the browser to web push for an app on notifications.phnx-iq.com, binds it to
4
- * the signed-in user with an identity token from the app's own backend, and hands notification clicks to the app.
6
+ * the signed-in user with an identity token from the app's own backend, and hands notification clicks to the app. It also
7
+ * draws the prompt HQ chose for the app (a bell, the app's logo or a pop-up), so a site needs no button of its own.
5
8
  *
6
9
  * Nothing here throws for an unsupported browser or a denied permission: check `isSupported()` / `permission()`.
7
10
  * The service worker file (phoenix-notifications-sw.js) must be served from the site, at `serviceWorkerPath`.
8
11
  */
9
12
  export const DEFAULT_SERVER_URL = 'https://notifications.phnx-iq.com';
10
13
  export const DEFAULT_SERVICE_WORKER_PATH = '/phoenix-notifications-sw.js';
14
+ function promptOf(web) {
15
+ if (!web)
16
+ return 'none';
17
+ if (web.prompt)
18
+ return web.prompt;
19
+ return web.bell?.enabled ? 'bell' : web.banner?.enabled ? 'banner' : 'none';
20
+ }
11
21
  export class NotificationsError extends Error {
12
22
  status;
13
23
  constructor(status, message) {
@@ -17,7 +27,8 @@ export class NotificationsError extends Error {
17
27
  }
18
28
  }
19
29
  const CLICK_MESSAGE = 'phoenix-notifications:click';
20
- const RESYNC_AFTER_MS = 24 * 60 * 60 * 1000;
30
+ const DAY_MS = 24 * 60 * 60 * 1000;
31
+ const RESYNC_AFTER_MS = DAY_MS;
21
32
  let options = {
22
33
  appId: '',
23
34
  serverUrl: DEFAULT_SERVER_URL,
@@ -31,8 +42,15 @@ let syncing = null;
31
42
  /** The last successful sync of this page, so init and login on the same load ask the server once. */
32
43
  let synced = null;
33
44
  const SYNC_REUSE_MS = 5 * 60 * 1000;
45
+ /** The app's web config, read once per page: the subscription and the bell both need it. */
46
+ let config = null;
34
47
  const clickHandlers = new Set();
48
+ const changeHandlers = new Set();
49
+ let lastState = null;
35
50
  let listening = false;
51
+ let watchingPermission = false;
52
+ let bell = null;
53
+ let banner = null;
36
54
  function supported() {
37
55
  return (typeof window !== 'undefined' &&
38
56
  window.isSecureContext &&
@@ -64,6 +82,19 @@ async function call(method, path, body, deviceSecret) {
64
82
  throw new NotificationsError(response.status, payload?.errors?.join(' ') || `Request failed with ${response.status}`);
65
83
  return payload?.result;
66
84
  }
85
+ function webConfig(force = false) {
86
+ if (force || !config || config.appId !== options.appId) {
87
+ const appId = options.appId;
88
+ const value = call('GET', `/v1/apps/${appId}/web`);
89
+ // a failed read is not kept: the next call asks again
90
+ value.catch(() => {
91
+ if (config?.value === value)
92
+ config = null;
93
+ });
94
+ config = { appId, value };
95
+ }
96
+ return config.value;
97
+ }
67
98
  function toBytes(base64Url) {
68
99
  const padded = base64Url.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (base64Url.length % 4)) % 4);
69
100
  const raw = atob(padded);
@@ -91,9 +122,12 @@ function subscriptionBody(subscription, vapidKey) {
91
122
  applicationServerKey: vapidKey,
92
123
  };
93
124
  }
125
+ function language() {
126
+ return options.language || document.documentElement.lang || navigator.language;
127
+ }
94
128
  function deviceInfo() {
95
129
  return {
96
- language: options.language || navigator.language,
130
+ language: language(),
97
131
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
98
132
  appVersion: options.appVersion,
99
133
  os: navigator.platform || undefined,
@@ -120,14 +154,41 @@ function listenForClicks() {
120
154
  // messages from the worker wait in a queue until the page says it is listening
121
155
  navigator.serviceWorker.startMessages();
122
156
  }
123
- /** Subscribes (or renews the subscription) and registers or updates the device. Needs the permission granted. */
157
+ async function optedOut() {
158
+ return !!options.appId && !!(await store.get(keys.optOut(options.appId)));
159
+ }
160
+ async function currentState() {
161
+ if (!supported())
162
+ return { permission: 'unsupported', subscribed: false };
163
+ const permission = Notification.permission;
164
+ if (permission !== 'granted' || !options.appId)
165
+ return { permission, subscribed: false };
166
+ const [device, out] = await Promise.all([store.get(keys.device(options.appId)), optedOut()]);
167
+ return { permission, subscribed: !!device && !out };
168
+ }
169
+ /** Tells the listeners when the state changed; never throws. */
170
+ async function announce() {
171
+ try {
172
+ const state = await currentState();
173
+ const key = `${options.appId}:${state.permission}:${state.subscribed}`;
174
+ if (key === lastState)
175
+ return;
176
+ lastState = key;
177
+ for (const handler of changeHandlers)
178
+ handler(state);
179
+ }
180
+ catch {
181
+ // a listener's failure or a blocked IndexedDB must not break the caller
182
+ }
183
+ }
184
+ /** Subscribes (or renews the subscription) and registers or updates the device. Needs the permission granted, and no opt-out. */
124
185
  async function sync() {
125
- if (!registration || Notification.permission !== 'granted')
186
+ if (!registration || Notification.permission !== 'granted' || (await optedOut()))
126
187
  return null;
127
- const config = await call('GET', `/v1/apps/${options.appId}/web`);
128
- if (!config)
188
+ const web = await webConfig();
189
+ if (!web)
129
190
  return null;
130
- const vapidKey = config.vapidPublicKey;
191
+ const vapidKey = web.vapidPublicKey;
131
192
  await store.set(keys.config, { appId: options.appId, serverUrl: options.serverUrl, vapidKey });
132
193
  let subscription = await registration.pushManager.getSubscription();
133
194
  // a subscription made with another key (an earlier key of this app, or another push provider) cannot receive our pushes
@@ -179,23 +240,130 @@ function syncOnce(force = false) {
179
240
  })
180
241
  .finally(() => {
181
242
  syncing = null;
243
+ void announce();
182
244
  });
183
245
  return syncing;
184
246
  }
185
- /** A login made before the person allowed notifications is applied once there is a device. */
247
+ /**
248
+ * A login made before the person allowed notifications (or kept through an unsubscribe) is applied once there is a device.
249
+ * Its failure does not undo the subscription: an expired token is dropped, and the site logs in again on its next load.
250
+ */
186
251
  async function applyPendingLogin(device) {
187
252
  if (!device)
188
253
  return;
189
254
  const pending = await store.get(keys.login(options.appId));
190
- if (pending && pending.at === 0)
255
+ if (!pending || pending.at !== 0)
256
+ return;
257
+ try {
191
258
  await Notifications.login(pending.token);
259
+ }
260
+ catch (error) {
261
+ if (error instanceof NotificationsError && error.status === 401)
262
+ await store.remove(keys.login(options.appId));
263
+ console.warn('[notifications] the pending login failed', error);
264
+ }
265
+ }
266
+ /** Follows permission changes made outside the page (the site settings next to the address bar). */
267
+ function watchPermission() {
268
+ if (watchingPermission)
269
+ return;
270
+ watchingPermission = true;
271
+ let known = Notification.permission;
272
+ const changed = () => {
273
+ if (Notification.permission === known)
274
+ return;
275
+ known = Notification.permission;
276
+ if (known === 'granted')
277
+ void syncOnce(true).catch(() => undefined);
278
+ void announce();
279
+ };
280
+ window.addEventListener('focus', changed);
281
+ void navigator.permissions
282
+ ?.query({ name: 'notifications' })
283
+ .then((status) => status.addEventListener('change', changed))
284
+ .catch(() => undefined);
285
+ }
286
+ /** Draws the floating button (a bell, or the app's logo) when it is the app's prompt. A failure only means no button. */
287
+ async function showBell(appId) {
288
+ try {
289
+ const web = await webConfig();
290
+ const prompt = promptOf(web);
291
+ if (bell?.appId !== appId || !web?.bell || (prompt !== 'bell' && prompt !== 'logo'))
292
+ return;
293
+ if (document.readyState === 'loading')
294
+ await new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve, { once: true }));
295
+ if (bell?.appId !== appId || bell.handle)
296
+ return;
297
+ bell.handle = mountBell({
298
+ state: currentState,
299
+ subscribe: async () => {
300
+ await Notifications.requestPermission();
301
+ return currentState();
302
+ },
303
+ unsubscribe: () => Notifications.unsubscribe(),
304
+ onChange: (handler) => Notifications.onSubscriptionChange(handler),
305
+ }, { ...web.bell, face: prompt === 'logo' ? 'logo' : 'bell', logoUrl: web.iconUrl }, language());
306
+ }
307
+ catch (error) {
308
+ console.warn('[notifications] the subscribe button is not available', error);
309
+ }
310
+ }
311
+ function whenDocumentReady() {
312
+ if (document.readyState !== 'loading')
313
+ return Promise.resolve();
314
+ return new Promise((resolve) => document.addEventListener('DOMContentLoaded', () => resolve(), { once: true }));
315
+ }
316
+ /**
317
+ * Slides the pop-up in when it is the app's prompt and its time has come: only for a browser never asked (the permission still
318
+ * to be given, and no unsubscribe here), from the page view HQ set, not within the days after a "Later", and after the delay.
319
+ * Each call is one page view. A failure only means no pop-up.
320
+ */
321
+ async function showBanner(appId) {
322
+ try {
323
+ const web = await webConfig();
324
+ const settings = web?.banner;
325
+ if (banner?.appId !== appId || !settings || promptOf(web) !== 'banner')
326
+ return;
327
+ if (Notification.permission !== 'default' || (await optedOut()))
328
+ return;
329
+ const key = keys.banner(appId);
330
+ const stored = (await store.get(key)) ?? { appId, views: 0 };
331
+ const views = Math.min(stored.views + 1, 1000);
332
+ await store.set(key, { ...stored, views });
333
+ if (views < settings.pageViews)
334
+ return;
335
+ if (stored.dismissedAt && Date.now() - stored.dismissedAt < settings.repromptDays * DAY_MS)
336
+ return;
337
+ await new Promise((resolve) => setTimeout(resolve, Math.max(0, settings.delaySeconds) * 1000));
338
+ await whenDocumentReady();
339
+ // the page may have moved on meanwhile: another app, the person subscribed with the site's own button, or answered the browser
340
+ if (banner?.appId !== appId || banner.handle || Notification.permission !== 'default' || (await optedOut()))
341
+ return;
342
+ banner.handle = mountBanner({
343
+ subscribe: async () => {
344
+ await Notifications.requestPermission();
345
+ return currentState();
346
+ },
347
+ dismiss: async () => {
348
+ const latest = (await store.get(key)) ?? { appId, views };
349
+ await store.set(key, { ...latest, dismissedAt: Date.now() });
350
+ },
351
+ onChange: (handler) => Notifications.onSubscriptionChange(handler),
352
+ }, settings, language(), web?.iconUrl);
353
+ }
354
+ catch (error) {
355
+ console.warn('[notifications] the subscribe pop-up is not available', error);
356
+ }
192
357
  }
193
358
  export const Notifications = {
194
359
  /** True when this browser can receive web push here (a secure context with service workers and the Push API). */
195
360
  isSupported() {
196
361
  return supported();
197
362
  },
198
- /** Registers the service worker. When the person already allowed notifications, subscribes quietly. */
363
+ /**
364
+ * Registers the service worker, and draws the prompt HQ chose for the app (a bell, the logo or a pop-up). When the person
365
+ * already allowed notifications (and did not unsubscribe here), subscribes quietly.
366
+ */
199
367
  async init(init) {
200
368
  if (!supported())
201
369
  return;
@@ -209,26 +377,60 @@ export const Notifications = {
209
377
  language: init.language ?? '',
210
378
  appVersion: init.appVersion,
211
379
  };
380
+ // one bell per page, for the app last initialised
381
+ const wantsBell = init.bell !== false;
382
+ if (bell && (bell.appId !== init.appId || !wantsBell)) {
383
+ bell.handle?.destroy();
384
+ bell = null;
385
+ }
386
+ if (wantsBell && !bell) {
387
+ bell = { appId: init.appId, handle: null };
388
+ void showBell(init.appId);
389
+ }
390
+ // the banner likewise; a second init of the same app on one page (React's strict mode) is no second page view
391
+ const wantsBanner = init.banner !== false;
392
+ if (banner && (banner.appId !== init.appId || !wantsBanner)) {
393
+ banner.handle?.destroy();
394
+ banner = null;
395
+ }
396
+ if (wantsBanner && !banner) {
397
+ banner = { appId: init.appId, handle: null };
398
+ void showBanner(init.appId);
399
+ }
212
400
  ready = (async () => {
213
401
  registration = await navigator.serviceWorker.register(options.serviceWorkerPath, { scope: options.serviceWorkerScope });
214
402
  listenForClicks();
215
403
  })();
216
404
  await ready;
405
+ watchPermission();
217
406
  if (Notification.permission === 'granted')
218
407
  await applyPendingLogin(await syncOnce(true));
408
+ else
409
+ void announce();
219
410
  },
220
411
  permission() {
221
412
  return supported() ? Notification.permission : 'unsupported';
222
413
  },
223
- /** Asks the person (call it from a click) and subscribes when they allow. */
414
+ /** Whether this browser gets the app's notifications now. */
415
+ getState() {
416
+ return currentState();
417
+ },
418
+ /**
419
+ * Asks the person (call it from a click: browsers only ask on a user gesture) and subscribes when they allow. It also
420
+ * subscribes again after `unsubscribe()`, without asking, since the browser keeps the permission.
421
+ */
224
422
  async requestPermission() {
225
423
  if (!supported())
226
424
  return 'unsupported';
227
425
  const result = await Notification.requestPermission();
228
- if (result === 'granted') {
229
- await ready;
230
- await applyPendingLogin(await syncOnce(true));
426
+ if (result !== 'granted') {
427
+ void announce();
428
+ return result;
231
429
  }
430
+ if (options.appId)
431
+ await store.remove(keys.optOut(options.appId));
432
+ await ready;
433
+ await applyPendingLogin(await syncOnce(true));
232
434
  return result;
233
435
  },
234
436
  /**
@@ -260,30 +462,48 @@ export const Notifications = {
260
462
  if (device)
261
463
  await call('POST', `/v1/devices/${device.id}/logout`, undefined, device.secret);
262
464
  },
263
- /** Stops notifications in this browser altogether. */
465
+ /**
466
+ * Stops notifications in this browser altogether, and remembers it: later page loads do not subscribe again although the
467
+ * permission stays granted, until `requestPermission()` (the bell's "Subscribe").
468
+ */
264
469
  async unsubscribe() {
265
470
  if (!supported() || !options.appId)
266
471
  return;
267
- const device = await store.get(keys.device(options.appId));
268
- if (device) {
269
- await call('DELETE', `/v1/devices/${device.id}`, undefined, device.secret);
270
- await store.remove(keys.device(options.appId));
472
+ await store.set(keys.optOut(options.appId), { appId: options.appId, at: Date.now() });
473
+ synced = null;
474
+ try {
475
+ const device = await store.get(keys.device(options.appId));
476
+ if (device) {
477
+ await call('DELETE', `/v1/devices/${device.id}`, undefined, device.secret);
478
+ await store.remove(keys.device(options.appId));
479
+ }
480
+ // the user is kept for a later subscription, like a login made before the permission
481
+ const login = await store.get(keys.login(options.appId));
482
+ if (login)
483
+ await store.set(keys.login(options.appId), { ...login, at: 0 });
484
+ const subscription = await registration?.pushManager.getSubscription();
485
+ await subscription?.unsubscribe();
486
+ }
487
+ finally {
488
+ void announce();
271
489
  }
272
- await store.remove(keys.login(options.appId));
273
- const subscription = await registration?.pushManager.getSubscription();
274
- await subscription?.unsubscribe();
275
490
  },
276
491
  /** A notification was clicked while the site was open. Without a handler the SDK opens its link. Returns an unsubscribe function. */
277
492
  onClick(handler) {
278
493
  clickHandlers.add(handler);
279
494
  return () => clickHandlers.delete(handler);
280
495
  },
496
+ /** Called when this browser subscribes, unsubscribes or the permission changes. Returns an unsubscribe function. */
497
+ onSubscriptionChange(handler) {
498
+ changeHandlers.add(handler);
499
+ return () => changeHandlers.delete(handler);
500
+ },
281
501
  /** Re-runs the subscription check now (e.g. after the person changed the site's permission). */
282
502
  async refresh() {
283
503
  if (!supported())
284
504
  return;
285
505
  await ready;
506
+ void webConfig(true).catch(() => undefined);
286
507
  await applyPendingLogin(await syncOnce(true));
287
508
  },
288
509
  };
289
- //# sourceMappingURL=index.js.map
package/dist/store.d.ts CHANGED
@@ -29,9 +29,21 @@ export declare const store: {
29
29
  set<T>(key: string, value: T): Promise<void>;
30
30
  remove(key: string): Promise<void>;
31
31
  };
32
+ /** The person turned notifications off here (the bell's "Unsubscribe", or `unsubscribe()`): a granted permission no longer subscribes. */
33
+ export interface StoredOptOut {
34
+ appId: string;
35
+ at: number;
36
+ }
37
+ /** The banner in this browser: page views counted since it was switched on, and the last "Later". */
38
+ export interface StoredBanner {
39
+ appId: string;
40
+ views: number;
41
+ dismissedAt?: number;
42
+ }
32
43
  export declare const keys: {
33
44
  config: string;
34
45
  device: (appId: string) => string;
35
46
  login: (appId: string) => string;
47
+ optOut: (appId: string) => string;
48
+ banner: (appId: string) => string;
36
49
  };
37
- //# sourceMappingURL=store.d.ts.map
package/dist/store.js CHANGED
@@ -43,5 +43,6 @@ export const keys = {
43
43
  config: 'config',
44
44
  device: (appId) => `device:${appId}`,
45
45
  login: (appId) => `login:${appId}`,
46
+ optOut: (appId) => `opt-out:${appId}`,
47
+ banner: (appId) => `banner:${appId}`,
46
48
  };
47
- //# sourceMappingURL=store.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What the bell and the banner share: their default colour, the readable text colour on it, and a light or dark look picked
3
+ * from the page itself, so they suit the site without settings of their own.
4
+ */
5
+ export declare const DEFAULT_COLOR = "#2563eb";
6
+ type Rgb = [number, number, number];
7
+ /** `#rrggbb` as given, or the SDK's blue. */
8
+ export declare function colorOr(color: string | null | undefined): string;
9
+ export declare function hexToRgb(hex: string): Rgb;
10
+ /** White or near-black, whichever reads better on the colour. */
11
+ export declare function textOn(color: string): string;
12
+ /** Sets the accent (`--bell`, `--bell-text`, `--ring`) and the page-matched look (`--panel`, `--text`…) on the host. */
13
+ export declare function applyColors(host: HTMLElement, color: string): void;
14
+ /** Again when something opens: the site may have changed its look since. */
15
+ export declare function applyTheme(host: HTMLElement): void;
16
+ /** The language the bell and the banner speak: Arabic for `ar…`, else English. */
17
+ export declare function uiLanguage(language: string): 'ar' | 'en';
18
+ export declare const ICONS: {
19
+ bell: string;
20
+ bellOff: string;
21
+ check: string;
22
+ close: string;
23
+ };
24
+ export declare function svg(paths: string, size: number, stroke?: number): string;
25
+ export {};
package/dist/theme.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * What the bell and the banner share: their default colour, the readable text colour on it, and a light or dark look picked
3
+ * from the page itself, so they suit the site without settings of their own.
4
+ */
5
+ export const DEFAULT_COLOR = '#2563eb';
6
+ /** `#rrggbb` as given, or the SDK's blue. */
7
+ export function colorOr(color) {
8
+ return /^#[0-9a-f]{6}$/i.test(color ?? '') ? color.toLowerCase() : DEFAULT_COLOR;
9
+ }
10
+ export function hexToRgb(hex) {
11
+ const n = Number.parseInt(hex.slice(1), 16);
12
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
13
+ }
14
+ function luminance([r, g, b]) {
15
+ const [lr, lg, lb] = [r, g, b].map((v) => {
16
+ const c = v / 255;
17
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
18
+ });
19
+ return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
20
+ }
21
+ function contrast(a, b) {
22
+ const [high, low] = [luminance(a), luminance(b)].sort((x, y) => y - x);
23
+ return (high + 0.05) / (low + 0.05);
24
+ }
25
+ /** White or near-black, whichever reads better on the colour. */
26
+ export function textOn(color) {
27
+ const rgb = hexToRgb(color);
28
+ return contrast(rgb, [255, 255, 255]) >= contrast(rgb, [17, 17, 17]) ? '#ffffff' : '#111111';
29
+ }
30
+ /** `rgb(…)` / `rgba(…)` as getComputedStyle gives it, unless (almost) transparent. */
31
+ function parseRgb(value) {
32
+ const match = /^rgba?\(\s*([\d.]+),\s*([\d.]+),\s*([\d.]+)(?:,\s*([\d.]+))?\s*\)$/.exec(value);
33
+ if (!match || (match[4] !== undefined && Number(match[4]) < 0.5))
34
+ return null;
35
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
36
+ }
37
+ /** The page's own background decides; without one, the colour scheme it declares; else the system's. */
38
+ function pageIsDark() {
39
+ for (const element of [document.body, document.documentElement]) {
40
+ if (!element)
41
+ continue;
42
+ const rgb = parseRgb(getComputedStyle(element).backgroundColor);
43
+ if (rgb)
44
+ return luminance(rgb) < 0.2;
45
+ }
46
+ const meta = document.querySelector('meta[name="color-scheme"]')?.content ?? '';
47
+ const declared = `${getComputedStyle(document.documentElement).colorScheme} ${meta}`;
48
+ const dark = /\bdark\b/.test(declared);
49
+ const light = /\blight\b/.test(declared);
50
+ if (dark !== light)
51
+ return dark;
52
+ return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
53
+ }
54
+ const THEMES = {
55
+ light: { panel: '#ffffff', text: '#111827', muted: '#4b5563', border: 'rgba(17, 24, 39, 0.12)', hover: 'rgba(17, 24, 39, 0.06)', error: '#b91c1c', tip: '#111827', tipText: '#ffffff' },
56
+ dark: { panel: '#1c1c22', text: '#f4f4f5', muted: '#a1a1aa', border: 'rgba(255, 255, 255, 0.14)', hover: 'rgba(255, 255, 255, 0.08)', error: '#fca5a5', tip: '#f4f4f5', tipText: '#18181b' },
57
+ };
58
+ /** Sets the accent (`--bell`, `--bell-text`, `--ring`) and the page-matched look (`--panel`, `--text`…) on the host. */
59
+ export function applyColors(host, color) {
60
+ host.style.setProperty('--bell', color);
61
+ host.style.setProperty('--bell-text', textOn(color));
62
+ host.style.setProperty('--ring', `rgba(${hexToRgb(color).join(', ')}, 0.55)`);
63
+ applyTheme(host);
64
+ }
65
+ /** Again when something opens: the site may have changed its look since. */
66
+ export function applyTheme(host) {
67
+ const theme = THEMES[pageIsDark() ? 'dark' : 'light'];
68
+ for (const [name, value] of Object.entries({
69
+ '--panel': theme.panel,
70
+ '--text': theme.text,
71
+ '--muted': theme.muted,
72
+ '--border': theme.border,
73
+ '--hover': theme.hover,
74
+ '--error': theme.error,
75
+ '--tip': theme.tip,
76
+ '--tip-text': theme.tipText,
77
+ }))
78
+ host.style.setProperty(name, value);
79
+ }
80
+ /** The language the bell and the banner speak: Arabic for `ar…`, else English. */
81
+ export function uiLanguage(language) {
82
+ return language.toLowerCase().startsWith('ar') ? 'ar' : 'en';
83
+ }
84
+ // Tabler icons (MIT).
85
+ export const ICONS = {
86
+ bell: '<path d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6"/><path d="M9 17v1a3 3 0 0 0 6 0v-1"/>',
87
+ bellOff: '<path d="M9.346 5.353c.21 -.129 .428 -.246 .654 -.353a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3m-1 3h-13a4 4 0 0 0 2 -3v-3a6.996 6.996 0 0 1 1.273 -3.707"/><path d="M9 17v1a3 3 0 0 0 6 0v-1"/><path d="M3 3l18 18"/>',
88
+ check: '<path d="M5 12l5 5l10 -10"/>',
89
+ close: '<path d="M18 6l-12 12"/><path d="M6 6l12 12"/>',
90
+ };
91
+ export function svg(paths, size, stroke = 1.8) {
92
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${stroke}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
93
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phoenix-iq/notifications",
3
- "version": "1.0.0",
4
- "description": "Phoenix Notifications web SDK: browser push subscriptions, verified sign-in and click handling for apps on notifications.phnx-iq.com.",
3
+ "version": "1.2.0",
4
+ "description": "Phoenix Notifications web SDK: browser push subscriptions, the subscribe prompt (a bell, the app's logo or a pop-up), verified sign-in and click handling for apps on notifications.phnx-iq.com.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "main": "./dist/index.js",
@@ -15,7 +15,6 @@
15
15
  },
16
16
  "files": [
17
17
  "dist",
18
- "src",
19
18
  "phoenix-notifications-sw.js",
20
19
  "README.md"
21
20
  ],
@@ -29,11 +28,17 @@
29
28
  "access": "public"
30
29
  },
31
30
  "scripts": {
32
- "build": "tsc -p tsconfig.build.json",
31
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
32
+ "build": "npm run clean && tsc -p tsconfig.build.json",
33
33
  "typecheck": "tsc --noEmit -p tsconfig.json",
34
+ "lint": "eslint .",
34
35
  "prepublishOnly": "npm run build"
35
36
  },
36
37
  "devDependencies": {
37
- "typescript": "^5.9.3"
38
+ "@eslint/js": "^10.0.1",
39
+ "eslint": "^10.12.0",
40
+ "globals": "^17.13.0",
41
+ "typescript": "^5.9.3",
42
+ "typescript-eslint": "^8.71.1"
38
43
  }
39
44
  }
@@ -72,7 +72,7 @@ self.addEventListener('install', () => self.skipWaiting());
72
72
  self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()));
73
73
 
74
74
  self.addEventListener('push', (event) => {
75
- let payload = {};
75
+ let payload;
76
76
  try {
77
77
  payload = event.data ? event.data.json() : {};
78
78
  } catch {
@@ -124,11 +124,12 @@ self.addEventListener('pushsubscriptionchange', (event) => {
124
124
  (async () => {
125
125
  const config = await read('config');
126
126
  if (!config || !config.vapidKey) return;
127
+ // no device: the browser was never registered, or the person unsubscribed (the bell, unsubscribe()); leave it so
127
128
  const device = await read(`device:${config.appId}`);
129
+ if (!device || (await read(`opt-out:${config.appId}`))) return;
128
130
  const subscription =
129
131
  event.newSubscription ||
130
132
  (await self.registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: toBytes(config.vapidKey) }));
131
- if (!device) return;
132
133
 
133
134
  const response = await fetch(apiUrl(config, `/v1/devices/${device.id}`), {
134
135
  method: 'PUT',
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,YAAY,EAA2B,MAAM,YAAY,CAAC;AAEjG;;;;;;GAMG;AAEH,eAAO,MAAM,kBAAkB,sCAAsC,CAAC;AACtE,eAAO,MAAM,2BAA2B,iCAAiC,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AAE9E,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqBD,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK5C;AA+KD,eAAO,MAAM,aAAa;IACxB,iHAAiH;mBAClG,OAAO;IAItB,uGAAuG;eACtF,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;kBAmB9B,cAAc;IAI5B,6EAA6E;yBAClD,OAAO,CAAC,cAAc,CAAC;IAUlD;;;OAGG;yBACwB,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAejD,4GAA4G;cAC5F,OAAO,CAAC,IAAI,CAAC;IAO7B,sDAAsD;mBACjC,OAAO,CAAC,IAAI,CAAC;IAYlC,qIAAqI;qBACpH,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKhE,gGAAgG;eAC/E,OAAO,CAAC,IAAI,CAAC;CAK/B,CAAC;AAEF,YAAY,EAAE,YAAY,EAAE,CAAC"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4GAA4G;AAC5G,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,uGAAuG;AACvG,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AA0BD,eAAO,MAAM,KAAK;QACZ,CAAC,OAAO,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;QAGvC,CAAC,OAAO,MAAM,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;gBAGhC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGnC,CAAC;AAEF,eAAO,MAAM,IAAI;;oBAEC,MAAM;mBACP,MAAM;CACtB,CAAC"}
package/dist/store.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,GAAG,uBAAuB,CAAC;AACxC,MAAM,KAAK,GAAG,OAAO,CAAC;AA2BtB,SAAS,IAAI;IACX,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,eAAe,GAAG,GAAG,EAAE;YAC7B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAChG,CAAC,CAAC;QACF,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,GAAG,CAAI,IAAwB,EAAE,MAA6C;IAC3F,MAAM,EAAE,GAAG,MAAM,IAAI,EAAE,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;YACvE,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAW,CAAC,CAAC;YACvD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,GAAG,CAAI,GAAW;QAChB,OAAO,GAAG,CAAgB,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,GAAG,CAAI,GAAW,EAAE,KAAQ;QAC1B,OAAO,GAAG,CAAO,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,CAAC,GAAW;QAChB,OAAO,GAAG,CAAO,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,UAAU,KAAK,EAAE;IAC5C,KAAK,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,SAAS,KAAK,EAAE;CAC3C,CAAC"}