fix(pwa): N105 — notification clicks work after the tab is closed
OS notifications were shown via page-level `new Notification()` whose onclick only works while the originating tab is alive — clicking a notification after closing the tab did nothing. - New `showOsNotification()` (utils/dom) prefers `registration.showNotification()` so the notification is service-worker-owned and persists; falls back to `new Notification()` (with the previous onclick) when no SW is available, so worst case is unchanged behaviour. - sw.ts gains a `notificationclick` handler: focuses an existing app window and forwards the target path, or opens the app if none is open. - ClientNonUIFeatures forwards the SW `notificationClick` message to react-router `navigate()` (works for both hash and browser router configs), and uses a per-room `tag` to coalesce notifications (replacing the old notifRef.close() dedup a SW notification can't hold). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,39 @@ self.addEventListener('message', (event: ExtendableMessageEvent) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* N105: handle clicks on service-worker-owned notifications. This fires even
|
||||
* when the originating tab was closed (a page-level `Notification.onclick`
|
||||
* would not). Focus an existing app window and forward the target path so it
|
||||
* can route there; if no window is open, open the app.
|
||||
*/
|
||||
self.addEventListener('notificationclick', (event: NotificationEvent) => {
|
||||
event.notification.close();
|
||||
const data = event.notification.data ?? {};
|
||||
const path = typeof data.path === 'string' ? data.path : undefined;
|
||||
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const windowClients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true,
|
||||
});
|
||||
const client = windowClients.find((c): c is WindowClient => 'focus' in c);
|
||||
if (client) {
|
||||
await client.focus();
|
||||
if (path) client.postMessage({ type: 'notificationClick', path });
|
||||
return;
|
||||
}
|
||||
// No app window open — open one at the app root (the client routes from
|
||||
// there). Router config (hash vs browser) is page-owned, so we don't try
|
||||
// to deep-link the URL here.
|
||||
if (self.clients.openWindow) {
|
||||
await self.clients.openWindow(self.registration.scope);
|
||||
}
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
const MEDIA_PATHS = ['/_matrix/client/v1/media/download', '/_matrix/client/v1/media/thumbnail'];
|
||||
|
||||
function mediaPath(url: string): boolean {
|
||||
|
||||
Reference in New Issue
Block a user