/** * 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(); }