@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/README.md +56 -13
- package/dist/banner.d.ts +35 -0
- package/dist/banner.js +178 -0
- package/dist/bell.d.ts +38 -0
- package/dist/bell.js +369 -0
- package/dist/index.d.ts +37 -7
- package/dist/index.js +244 -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,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
|
|
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:
|
|
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
|
-
|
|
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
|
|
128
|
-
if (!
|
|
188
|
+
const web = await webConfig();
|
|
189
|
+
if (!web)
|
|
129
190
|
return null;
|
|
130
|
-
const vapidKey =
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
await store.
|
|
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
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.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
|
-
"
|
|
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"}
|