@phoenix-iq/notifications 1.0.0 → 1.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.
- package/README.md +52 -10
- package/dist/banner.d.ts +41 -0
- package/dist/banner.js +183 -0
- package/dist/bell.d.ts +31 -0
- package/dist/bell.js +331 -0
- package/dist/index.d.ts +35 -7
- package/dist/index.js +236 -24
- package/dist/store.d.ts +13 -1
- package/dist/store.js +2 -1
- package/dist/theme.d.ts +25 -0
- package/dist/theme.js +93 -0
- package/package.json +10 -5
- package/phoenix-notifications-sw.js +3 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js.map +0 -1
- package/src/index.ts +0 -331
- package/src/store.ts +0 -74
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
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. When the
|
|
7
|
+
* app has its subscription bell or banner switched on in HQ, it also draws them, 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`.
|
|
@@ -17,7 +20,8 @@ export class NotificationsError extends Error {
|
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
22
|
const CLICK_MESSAGE = 'phoenix-notifications:click';
|
|
20
|
-
const
|
|
23
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
24
|
+
const RESYNC_AFTER_MS = DAY_MS;
|
|
21
25
|
let options = {
|
|
22
26
|
appId: '',
|
|
23
27
|
serverUrl: DEFAULT_SERVER_URL,
|
|
@@ -31,8 +35,15 @@ let syncing = null;
|
|
|
31
35
|
/** The last successful sync of this page, so init and login on the same load ask the server once. */
|
|
32
36
|
let synced = null;
|
|
33
37
|
const SYNC_REUSE_MS = 5 * 60 * 1000;
|
|
38
|
+
/** The app's web config, read once per page: the subscription and the bell both need it. */
|
|
39
|
+
let config = null;
|
|
34
40
|
const clickHandlers = new Set();
|
|
41
|
+
const changeHandlers = new Set();
|
|
42
|
+
let lastState = null;
|
|
35
43
|
let listening = false;
|
|
44
|
+
let watchingPermission = false;
|
|
45
|
+
let bell = null;
|
|
46
|
+
let banner = null;
|
|
36
47
|
function supported() {
|
|
37
48
|
return (typeof window !== 'undefined' &&
|
|
38
49
|
window.isSecureContext &&
|
|
@@ -64,6 +75,19 @@ async function call(method, path, body, deviceSecret) {
|
|
|
64
75
|
throw new NotificationsError(response.status, payload?.errors?.join(' ') || `Request failed with ${response.status}`);
|
|
65
76
|
return payload?.result;
|
|
66
77
|
}
|
|
78
|
+
function webConfig(force = false) {
|
|
79
|
+
if (force || !config || config.appId !== options.appId) {
|
|
80
|
+
const appId = options.appId;
|
|
81
|
+
const value = call('GET', `/v1/apps/${appId}/web`);
|
|
82
|
+
// a failed read is not kept: the next call asks again
|
|
83
|
+
value.catch(() => {
|
|
84
|
+
if (config?.value === value)
|
|
85
|
+
config = null;
|
|
86
|
+
});
|
|
87
|
+
config = { appId, value };
|
|
88
|
+
}
|
|
89
|
+
return config.value;
|
|
90
|
+
}
|
|
67
91
|
function toBytes(base64Url) {
|
|
68
92
|
const padded = base64Url.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (base64Url.length % 4)) % 4);
|
|
69
93
|
const raw = atob(padded);
|
|
@@ -91,9 +115,12 @@ function subscriptionBody(subscription, vapidKey) {
|
|
|
91
115
|
applicationServerKey: vapidKey,
|
|
92
116
|
};
|
|
93
117
|
}
|
|
118
|
+
function language() {
|
|
119
|
+
return options.language || document.documentElement.lang || navigator.language;
|
|
120
|
+
}
|
|
94
121
|
function deviceInfo() {
|
|
95
122
|
return {
|
|
96
|
-
language:
|
|
123
|
+
language: language(),
|
|
97
124
|
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
98
125
|
appVersion: options.appVersion,
|
|
99
126
|
os: navigator.platform || undefined,
|
|
@@ -120,14 +147,41 @@ function listenForClicks() {
|
|
|
120
147
|
// messages from the worker wait in a queue until the page says it is listening
|
|
121
148
|
navigator.serviceWorker.startMessages();
|
|
122
149
|
}
|
|
123
|
-
|
|
150
|
+
async function optedOut() {
|
|
151
|
+
return !!options.appId && !!(await store.get(keys.optOut(options.appId)));
|
|
152
|
+
}
|
|
153
|
+
async function currentState() {
|
|
154
|
+
if (!supported())
|
|
155
|
+
return { permission: 'unsupported', subscribed: false };
|
|
156
|
+
const permission = Notification.permission;
|
|
157
|
+
if (permission !== 'granted' || !options.appId)
|
|
158
|
+
return { permission, subscribed: false };
|
|
159
|
+
const [device, out] = await Promise.all([store.get(keys.device(options.appId)), optedOut()]);
|
|
160
|
+
return { permission, subscribed: !!device && !out };
|
|
161
|
+
}
|
|
162
|
+
/** Tells the listeners when the state changed; never throws. */
|
|
163
|
+
async function announce() {
|
|
164
|
+
try {
|
|
165
|
+
const state = await currentState();
|
|
166
|
+
const key = `${options.appId}:${state.permission}:${state.subscribed}`;
|
|
167
|
+
if (key === lastState)
|
|
168
|
+
return;
|
|
169
|
+
lastState = key;
|
|
170
|
+
for (const handler of changeHandlers)
|
|
171
|
+
handler(state);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// a listener's failure or a blocked IndexedDB must not break the caller
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Subscribes (or renews the subscription) and registers or updates the device. Needs the permission granted, and no opt-out. */
|
|
124
178
|
async function sync() {
|
|
125
|
-
if (!registration || Notification.permission !== 'granted')
|
|
179
|
+
if (!registration || Notification.permission !== 'granted' || (await optedOut()))
|
|
126
180
|
return null;
|
|
127
|
-
const
|
|
128
|
-
if (!
|
|
181
|
+
const web = await webConfig();
|
|
182
|
+
if (!web)
|
|
129
183
|
return null;
|
|
130
|
-
const vapidKey =
|
|
184
|
+
const vapidKey = web.vapidPublicKey;
|
|
131
185
|
await store.set(keys.config, { appId: options.appId, serverUrl: options.serverUrl, vapidKey });
|
|
132
186
|
let subscription = await registration.pushManager.getSubscription();
|
|
133
187
|
// a subscription made with another key (an earlier key of this app, or another push provider) cannot receive our pushes
|
|
@@ -179,23 +233,129 @@ function syncOnce(force = false) {
|
|
|
179
233
|
})
|
|
180
234
|
.finally(() => {
|
|
181
235
|
syncing = null;
|
|
236
|
+
void announce();
|
|
182
237
|
});
|
|
183
238
|
return syncing;
|
|
184
239
|
}
|
|
185
|
-
/**
|
|
240
|
+
/**
|
|
241
|
+
* A login made before the person allowed notifications (or kept through an unsubscribe) is applied once there is a device.
|
|
242
|
+
* Its failure does not undo the subscription: an expired token is dropped, and the site logs in again on its next load.
|
|
243
|
+
*/
|
|
186
244
|
async function applyPendingLogin(device) {
|
|
187
245
|
if (!device)
|
|
188
246
|
return;
|
|
189
247
|
const pending = await store.get(keys.login(options.appId));
|
|
190
|
-
if (pending
|
|
248
|
+
if (!pending || pending.at !== 0)
|
|
249
|
+
return;
|
|
250
|
+
try {
|
|
191
251
|
await Notifications.login(pending.token);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (error instanceof NotificationsError && error.status === 401)
|
|
255
|
+
await store.remove(keys.login(options.appId));
|
|
256
|
+
console.warn('[notifications] the pending login failed', error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** Follows permission changes made outside the page (the site settings next to the address bar). */
|
|
260
|
+
function watchPermission() {
|
|
261
|
+
if (watchingPermission)
|
|
262
|
+
return;
|
|
263
|
+
watchingPermission = true;
|
|
264
|
+
let known = Notification.permission;
|
|
265
|
+
const changed = () => {
|
|
266
|
+
if (Notification.permission === known)
|
|
267
|
+
return;
|
|
268
|
+
known = Notification.permission;
|
|
269
|
+
if (known === 'granted')
|
|
270
|
+
void syncOnce(true).catch(() => undefined);
|
|
271
|
+
void announce();
|
|
272
|
+
};
|
|
273
|
+
window.addEventListener('focus', changed);
|
|
274
|
+
void navigator.permissions
|
|
275
|
+
?.query({ name: 'notifications' })
|
|
276
|
+
.then((status) => status.addEventListener('change', changed))
|
|
277
|
+
.catch(() => undefined);
|
|
278
|
+
}
|
|
279
|
+
/** Draws the bell once the app's config says so. A failure only means no bell. */
|
|
280
|
+
async function showBell(appId) {
|
|
281
|
+
try {
|
|
282
|
+
const web = await webConfig();
|
|
283
|
+
if (bell?.appId !== appId || !web?.bell?.enabled)
|
|
284
|
+
return;
|
|
285
|
+
if (document.readyState === 'loading')
|
|
286
|
+
await new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve, { once: true }));
|
|
287
|
+
if (bell?.appId !== appId || bell.handle)
|
|
288
|
+
return;
|
|
289
|
+
bell.handle = mountBell({
|
|
290
|
+
state: currentState,
|
|
291
|
+
subscribe: async () => {
|
|
292
|
+
await Notifications.requestPermission();
|
|
293
|
+
return currentState();
|
|
294
|
+
},
|
|
295
|
+
unsubscribe: () => Notifications.unsubscribe(),
|
|
296
|
+
onChange: (handler) => Notifications.onSubscriptionChange(handler),
|
|
297
|
+
}, web.bell, language());
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.warn('[notifications] the subscription bell is not available', error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function whenDocumentReady() {
|
|
304
|
+
if (document.readyState !== 'loading')
|
|
305
|
+
return Promise.resolve();
|
|
306
|
+
return new Promise((resolve) => document.addEventListener('DOMContentLoaded', () => resolve(), { once: true }));
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Slides the banner in when its time has come: only for a browser never asked (the permission still to be given, and no
|
|
310
|
+
* unsubscribe here), from the page view HQ set, not within the days after a "Later", and after the delay. Each call is one page
|
|
311
|
+
* view. A failure only means no banner.
|
|
312
|
+
*/
|
|
313
|
+
async function showBanner(appId) {
|
|
314
|
+
try {
|
|
315
|
+
const web = await webConfig();
|
|
316
|
+
const settings = web?.banner;
|
|
317
|
+
if (banner?.appId !== appId || !settings?.enabled)
|
|
318
|
+
return;
|
|
319
|
+
if (Notification.permission !== 'default' || (await optedOut()))
|
|
320
|
+
return;
|
|
321
|
+
const key = keys.banner(appId);
|
|
322
|
+
const stored = (await store.get(key)) ?? { appId, views: 0 };
|
|
323
|
+
const views = Math.min(stored.views + 1, 1000);
|
|
324
|
+
await store.set(key, { ...stored, views });
|
|
325
|
+
if (views < settings.pageViews)
|
|
326
|
+
return;
|
|
327
|
+
if (stored.dismissedAt && Date.now() - stored.dismissedAt < settings.repromptDays * DAY_MS)
|
|
328
|
+
return;
|
|
329
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(0, settings.delaySeconds) * 1000));
|
|
330
|
+
await whenDocumentReady();
|
|
331
|
+
// the page may have moved on meanwhile: another app, the person subscribed with the bell, or answered the browser
|
|
332
|
+
if (banner?.appId !== appId || banner.handle || Notification.permission !== 'default' || (await optedOut()))
|
|
333
|
+
return;
|
|
334
|
+
banner.handle = mountBanner({
|
|
335
|
+
subscribe: async () => {
|
|
336
|
+
await Notifications.requestPermission();
|
|
337
|
+
return currentState();
|
|
338
|
+
},
|
|
339
|
+
dismiss: async () => {
|
|
340
|
+
const latest = (await store.get(key)) ?? { appId, views };
|
|
341
|
+
await store.set(key, { ...latest, dismissedAt: Date.now() });
|
|
342
|
+
},
|
|
343
|
+
onChange: (handler) => Notifications.onSubscriptionChange(handler),
|
|
344
|
+
}, settings, language(), { iconUrl: web?.iconUrl, aboveBell: !!bell?.handle });
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
console.warn('[notifications] the subscription banner is not available', error);
|
|
348
|
+
}
|
|
192
349
|
}
|
|
193
350
|
export const Notifications = {
|
|
194
351
|
/** True when this browser can receive web push here (a secure context with service workers and the Push API). */
|
|
195
352
|
isSupported() {
|
|
196
353
|
return supported();
|
|
197
354
|
},
|
|
198
|
-
/**
|
|
355
|
+
/**
|
|
356
|
+
* Registers the service worker, and draws the subscription bell and the banner when the app has them on. When the person
|
|
357
|
+
* already allowed notifications (and did not unsubscribe here), subscribes quietly.
|
|
358
|
+
*/
|
|
199
359
|
async init(init) {
|
|
200
360
|
if (!supported())
|
|
201
361
|
return;
|
|
@@ -209,26 +369,60 @@ export const Notifications = {
|
|
|
209
369
|
language: init.language ?? '',
|
|
210
370
|
appVersion: init.appVersion,
|
|
211
371
|
};
|
|
372
|
+
// one bell per page, for the app last initialised
|
|
373
|
+
const wantsBell = init.bell !== false;
|
|
374
|
+
if (bell && (bell.appId !== init.appId || !wantsBell)) {
|
|
375
|
+
bell.handle?.destroy();
|
|
376
|
+
bell = null;
|
|
377
|
+
}
|
|
378
|
+
if (wantsBell && !bell) {
|
|
379
|
+
bell = { appId: init.appId, handle: null };
|
|
380
|
+
void showBell(init.appId);
|
|
381
|
+
}
|
|
382
|
+
// the banner likewise; a second init of the same app on one page (React's strict mode) is no second page view
|
|
383
|
+
const wantsBanner = init.banner !== false;
|
|
384
|
+
if (banner && (banner.appId !== init.appId || !wantsBanner)) {
|
|
385
|
+
banner.handle?.destroy();
|
|
386
|
+
banner = null;
|
|
387
|
+
}
|
|
388
|
+
if (wantsBanner && !banner) {
|
|
389
|
+
banner = { appId: init.appId, handle: null };
|
|
390
|
+
void showBanner(init.appId);
|
|
391
|
+
}
|
|
212
392
|
ready = (async () => {
|
|
213
393
|
registration = await navigator.serviceWorker.register(options.serviceWorkerPath, { scope: options.serviceWorkerScope });
|
|
214
394
|
listenForClicks();
|
|
215
395
|
})();
|
|
216
396
|
await ready;
|
|
397
|
+
watchPermission();
|
|
217
398
|
if (Notification.permission === 'granted')
|
|
218
399
|
await applyPendingLogin(await syncOnce(true));
|
|
400
|
+
else
|
|
401
|
+
void announce();
|
|
219
402
|
},
|
|
220
403
|
permission() {
|
|
221
404
|
return supported() ? Notification.permission : 'unsupported';
|
|
222
405
|
},
|
|
223
|
-
/**
|
|
406
|
+
/** Whether this browser gets the app's notifications now. */
|
|
407
|
+
getState() {
|
|
408
|
+
return currentState();
|
|
409
|
+
},
|
|
410
|
+
/**
|
|
411
|
+
* Asks the person (call it from a click: browsers only ask on a user gesture) and subscribes when they allow. It also
|
|
412
|
+
* subscribes again after `unsubscribe()`, without asking, since the browser keeps the permission.
|
|
413
|
+
*/
|
|
224
414
|
async requestPermission() {
|
|
225
415
|
if (!supported())
|
|
226
416
|
return 'unsupported';
|
|
227
417
|
const result = await Notification.requestPermission();
|
|
228
|
-
if (result
|
|
229
|
-
|
|
230
|
-
|
|
418
|
+
if (result !== 'granted') {
|
|
419
|
+
void announce();
|
|
420
|
+
return result;
|
|
231
421
|
}
|
|
422
|
+
if (options.appId)
|
|
423
|
+
await store.remove(keys.optOut(options.appId));
|
|
424
|
+
await ready;
|
|
425
|
+
await applyPendingLogin(await syncOnce(true));
|
|
232
426
|
return result;
|
|
233
427
|
},
|
|
234
428
|
/**
|
|
@@ -260,30 +454,48 @@ export const Notifications = {
|
|
|
260
454
|
if (device)
|
|
261
455
|
await call('POST', `/v1/devices/${device.id}/logout`, undefined, device.secret);
|
|
262
456
|
},
|
|
263
|
-
/**
|
|
457
|
+
/**
|
|
458
|
+
* Stops notifications in this browser altogether, and remembers it: later page loads do not subscribe again although the
|
|
459
|
+
* permission stays granted, until `requestPermission()` (the bell's "Subscribe").
|
|
460
|
+
*/
|
|
264
461
|
async unsubscribe() {
|
|
265
462
|
if (!supported() || !options.appId)
|
|
266
463
|
return;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
await store.
|
|
464
|
+
await store.set(keys.optOut(options.appId), { appId: options.appId, at: Date.now() });
|
|
465
|
+
synced = null;
|
|
466
|
+
try {
|
|
467
|
+
const device = await store.get(keys.device(options.appId));
|
|
468
|
+
if (device) {
|
|
469
|
+
await call('DELETE', `/v1/devices/${device.id}`, undefined, device.secret);
|
|
470
|
+
await store.remove(keys.device(options.appId));
|
|
471
|
+
}
|
|
472
|
+
// the user is kept for a later subscription, like a login made before the permission
|
|
473
|
+
const login = await store.get(keys.login(options.appId));
|
|
474
|
+
if (login)
|
|
475
|
+
await store.set(keys.login(options.appId), { ...login, at: 0 });
|
|
476
|
+
const subscription = await registration?.pushManager.getSubscription();
|
|
477
|
+
await subscription?.unsubscribe();
|
|
478
|
+
}
|
|
479
|
+
finally {
|
|
480
|
+
void announce();
|
|
271
481
|
}
|
|
272
|
-
await store.remove(keys.login(options.appId));
|
|
273
|
-
const subscription = await registration?.pushManager.getSubscription();
|
|
274
|
-
await subscription?.unsubscribe();
|
|
275
482
|
},
|
|
276
483
|
/** A notification was clicked while the site was open. Without a handler the SDK opens its link. Returns an unsubscribe function. */
|
|
277
484
|
onClick(handler) {
|
|
278
485
|
clickHandlers.add(handler);
|
|
279
486
|
return () => clickHandlers.delete(handler);
|
|
280
487
|
},
|
|
488
|
+
/** Called when this browser subscribes, unsubscribes or the permission changes. Returns an unsubscribe function. */
|
|
489
|
+
onSubscriptionChange(handler) {
|
|
490
|
+
changeHandlers.add(handler);
|
|
491
|
+
return () => changeHandlers.delete(handler);
|
|
492
|
+
},
|
|
281
493
|
/** Re-runs the subscription check now (e.g. after the person changed the site's permission). */
|
|
282
494
|
async refresh() {
|
|
283
495
|
if (!supported())
|
|
284
496
|
return;
|
|
285
497
|
await ready;
|
|
498
|
+
void webConfig(true).catch(() => undefined);
|
|
286
499
|
await applyPendingLogin(await syncOnce(true));
|
|
287
500
|
},
|
|
288
501
|
};
|
|
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
package/dist/theme.d.ts
ADDED
|
@@ -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.
|
|
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.1.0",
|
|
4
|
+
"description": "Phoenix Notifications web SDK: browser push subscriptions, the subscription bell, 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
|
-
"
|
|
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
|
-
"
|
|
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',
|
package/dist/index.d.ts.map
DELETED
|
@@ -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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"}
|
package/dist/store.d.ts.map
DELETED
|
@@ -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"}
|