web: answer incoming calls from the browser notification
ci / check (push) Successful in 1m34s
ci / images (push) Successful in 27s
ci / check (pull_request) Successful in 1m18s
ci / images (pull_request) Successful in 5s

Owner follow-up to the softphone (spec 05 §5).

- the incoming call notification comes from a service worker
  (public/sw.js) so it carries Answer and Decline buttons (Chromium);
  Answer and a plain click focus the softphone tab, Decline doesn't
- shown whenever the tab isn't focused, not only when it's hidden; kept
  up while it rings and closed once it stops
- the softphone panel says how to allow notifications when they're blocked
This commit is contained in:
iamdoubz committed 2026-10-27 12:35:43 -05:00
1 parent 431ada51aa
commit 33d5f1a5f5
4 files changed
+100 -26

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// The softphone's incoming call notification (05 §5). Only a service worker's notifications can
// carry buttons. The softphone tab asks for one here; Answer/Decline go back to that tab only.
/* global self */
self.addEventListener('message', (e) => {
const call = e.data?.notifyCall;
if (!call) return;
e.waitUntil(
self.registration.showNotification('Incoming call', {
body: call.body,
tag: call.id,
requireInteraction: true,
actions: [
{ action: 'answer', title: 'Answer' },
{ action: 'decline', title: 'Decline' },
],
data: { id: call.id, tab: e.source.id },
}),
);
});
self.addEventListener('notificationclick', (e) => {
e.notification.close();
const { id, tab } = e.notification.data;
e.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(async (all) => {
const client = all.find((c) => c.id === tab);
if (!client) return;
if (e.action) client.postMessage({ softphone: e.action, id });
// Declining needs nothing from the tab; otherwise bring it back.
if (e.action !== 'decline') await client.focus();
}),
);
});