aboutsummaryrefslogtreecommitdiffstats
path: root/src/sw-nwc.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/sw-nwc.js')
-rw-r--r--src/sw-nwc.js46
1 files changed, 45 insertions, 1 deletions
diff --git a/src/sw-nwc.js b/src/sw-nwc.js
index 3dac246..f9d383e 100644
--- a/src/sw-nwc.js
+++ b/src/sw-nwc.js
@@ -7,15 +7,59 @@
import { respondFromBg, tendRenewals } from './nwc-respond.js';
import { withRequestLock } from './nwc-lock.js';
import { bgAutoWithdraw } from './bg-autowithdraw.js';
-import { allInboxes, classifyDm, shouldNotifyDm } from './dm-inbox.js';
+import { allInboxes, classifyDm, shouldNotifyDm, classifyCall } from './dm-inbox.js';
const NOTIFIER = 'https://nwcpush.coinos.io';
+const CALL_CACHE = 'coinos-call'; // a call offer a push brought, for the app the tap opens
+// where a call's offer wrap is fetched from: the notifier, or (the app served
+// on this machine, as the tests do) this origin
+const WRAPS = /^(localhost|127\.0\.0\.1)$/.test(self.location.hostname) ? self.location.origin + '/nwcpush' : NOTIFIER;
self.addEventListener('push', (e) => {
let data = {};
try { data = e.data ? e.data.json() : {}; } catch (_) {}
// User-facing notifications (payments, DMs, community chat). An open
// window already shows everything live — stay quiet then.
+ // A call ringing in: the push names the offer's wrap, kept at the notifier
+ // (too big to ride in a push). Opened here with our key; a fresh offer
+ // from someone we follow or talk to rings, as a notification that stays
+ // until it's answered or missed. The offer is kept for the app to pick up
+ // when the tap opens it (CALL_CACHE). An open window rings by itself.
+ if (data.type === 'notify' && data.reason === 'call') {
+ e.waitUntil((async () => {
+ const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
+ if (clients.some((c) => c.visibilityState === 'visible')) return;
+ let ring = null;
+ try {
+ const r = await fetch(WRAPS + '/wrap/' + encodeURIComponent(data.wrapId || ''));
+ if (r.ok) ring = await classifyCall(await r.json(), await allInboxes());
+ } catch (err) { console.warn('[sw-call] could not open:', err && err.message); }
+ if (!ring) return;
+ if (ring.quiet) {
+ // no ring for them; a note for the app, which lists it as a missed call
+ try {
+ const cache = await caches.open(CALL_CACHE);
+ const had = await cache.match('/__quiet-calls');
+ const list = had ? await had.json() : [];
+ list.push(ring.offer);
+ await cache.put('/__quiet-calls', new Response(JSON.stringify(list.slice(-20)), { headers: { 'content-type': 'application/json' } }));
+ } catch (_) {}
+ return;
+ }
+ try {
+ const cache = await caches.open(CALL_CACHE);
+ await cache.put('/__pending-call', new Response(JSON.stringify({ offer: ring.offer, at: Date.now() }), { headers: { 'content-type': 'application/json' } }));
+ } catch (_) {}
+ for (const c of clients) { try { c.postMessage({ type: 'call-offer', offer: ring.offer }); } catch (_) {} }
+ await self.registration.showNotification((ring.name || 'Someone') + ' is calling', {
+ body: ring.type === 'video' ? 'Video call. Tap to answer.' : 'Voice call. Tap to answer.',
+ icon: 'icon-192.png', badge: 'badge-96.png',
+ tag: 'call', renotify: true, requireInteraction: true, vibrate: [400, 200, 400, 200, 400],
+ data: { url: './?open=call', view: 'call' },
+ });
+ })());
+ return;
+ }
if (data.type === 'notify') {
e.waitUntil((async () => {
const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });