forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpush.ts
More file actions
71 lines (66 loc) · 3.03 KB
/
Copy pathpush.ts
File metadata and controls
71 lines (66 loc) · 3.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// The single place OurHike is allowed to send a push notification.
//
// The policy (features/HIKER_SAFETY.md §5, restated in FEATURES.md and
// WIREFRAMES.md): the wrong-way / off-trail alert is the ONLY notification
// this app ever sends. Serious warnings do not push. Weather does not push -
// HIKER_SAFETY.md flags whether it ever should as a genuinely open question
// and deliberately does not resolve it.
//
// The reason for a chokepoint rather than a convention: this rule erodes one
// well-meaning exception at a time. Each new "but this one is genuinely
// urgent" is defensible on its own, and the aggregate is an app that
// interrupts people on a mountain. Routing every push through one function
// means adding a second kind is a visible edit to this file, not a line
// somewhere else that nobody reviews as a policy change.
//
// lib/push.test.ts scans the source tree and fails if any other module
// touches a notification API directly.
export interface WrongWayAlert {
title: string
body: string
}
/**
* The one permitted push. Named for its only caller on purpose - a generic
* `sendPush(...)` would be an invitation.
*
* Delivered through the service worker registration rather than the
* `Notification` constructor, because on the platform this actually ships to
* the constructor does not work. Android Chrome throws outright - "Failed to
* construct 'Notification': Illegal constructor. Use
* ServiceWorkerRegistration.showNotification() instead" - and an installed
* iOS PWA, the only place iOS delivers web push at all, has no usable
* constructor either. Both want the registration.
*
* The constructor stays as the fallback because it is the path that works on
* a desktop browser during development, which is where this gets looked at.
*/
export async function publishWrongWayAlert(alert: WrongWayAlert): Promise<boolean> {
if (typeof Notification === 'undefined') return false
if (Notification.permission !== 'granted') return false
const options: NotificationOptions = { body: alert.body }
if ('serviceWorker' in navigator) {
try {
// getRegistration(), not ready: `ready` never settles when nothing is
// registered, and a promise that hangs forever is a worse failure than
// a missed notification - it would leave the alert silently pending
// for the rest of the hike with nothing to time it out.
const registration = await navigator.serviceWorker.getRegistration()
if (registration !== undefined) {
await registration.showNotification(alert.title, options)
return true
}
} catch {
// Fall through: a registration that cannot show is not a reason to
// give up on telling someone they are walking the wrong way.
}
}
try {
new Notification(alert.title, options)
return true
} catch {
// Every path exhausted. Reported honestly rather than thrown: the caller
// (lib/wrongWayAlert.ts) treats a false as "the cue is all we have", and
// an exception here would take the in-app cue down with the push.
return false
}
}