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
|
/**
* Notifications on this phone: the page's half.
*
* Nothing to install: the shell fetches what is new on its own every quarter
* of an hour, and when the phone already has a UnifiedPush distributor it is
* used too, so they arrive at once. The page gives the hub whatever the shell
* ends up with, because the page holds the session. Whether a notification is
* wanted — every one turned off, or a group muted — is decided on the hub,
* which then creates nothing (docs/MESHBAY_DESIGN.md §11.3).
*/
import * as platform from './platform.js';
import { hubFetch } from './hub-client.js';
// How long turning it on waits for a distributor before registering without
// one; when the endpoint comes later, the next sync hands it over.
const WAIT_MS = 10_000;
const POLL_MS = 500;
/**
* Make the hub's row match what this phone has: at every start and sign-in,
* and after turning it on. A distributor may hand out a new endpoint, or go
* away, at any time, page running or not; this is where the hub hears it.
*/
export async function syncPush(user) {
if (!platform.push.available || !user) return null;
const s = await platform.push.status();
if (!s.enabled) return s;
const mine = s.subscription && s.account === user.userId;
if (mine && s.registered === s.endpoint) return s;
const body = s.endpoint
? { endpoint: s.endpoint, p256dh: s.p256dh, auth: s.auth }
: {};
if (mine) body.id = s.subscription;
const r = await hubFetch('/v1/push/subscriptions', {
method: 'POST', token: user.token, body,
});
return platform.push.remember(r.id, user.userId, r.poll_secret, r.now);
}
/** Turn it on: a moment for a distributor to answer, then register either way. */
export async function enablePush(user, onProgress) {
let s = await platform.push.enable();
const until = Date.now() + WAIT_MS;
while (s.state === 'pending' && Date.now() < until) {
if (onProgress) onProgress(s);
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
s = await platform.push.status();
}
return syncPush(user).then((synced) => synced || s);
}
/**
* Turn it off: the hub forgets this phone first, then the phone stops. The hub
* half is the one that can fail; the phone half happens regardless, so a
* refused request never leaves it on here.
*/
export async function disablePush(user) {
if (!platform.push.available) return null;
const s = await platform.push.status();
if (s.subscription && user && s.account === user.userId) {
try {
await hubFetch(`/v1/push/subscriptions/${s.subscription}`, {
method: 'DELETE', token: user.token,
});
} catch (e) { /* already gone, or unreachable: the phone half still happens */ }
}
return platform.push.disable();
}
|