// Service Worker — Sargasse-Sentry // Force l'activation immédiate sans attendre la fermeture des onglets. // Critique : tant que l'ancien SW (sans fetch handler) reste actif, // Chrome ne considère pas l'app comme installable. self.addEventListener('install', () => { self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); }); // Fetch handler vide — requis par Chrome pour l'installabilité PWA. // Pas de mise en cache : les données sont temps-réel. // Pas de respondWith pour éviter les erreurs sur les tiles cross-origin. self.addEventListener('fetch', () => {}); self.addEventListener('push', (event) => { if (!event.data) return; let payload; try { payload = event.data.json(); } catch { payload = { title: 'Alerte sargasses', body: event.data.text() }; } const title = payload.title ?? 'Alerte sargasses'; const options = { body: payload.body ?? '', icon: '/icon-192.png', badge: '/icon-192.png', tag: payload.spotId ? `sargasse-${payload.spotId}` : 'sargasse-alert', renotify: true, data: { url: payload.url ?? '/' }, }; event.waitUntil(self.registration.showNotification(title, options)); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); const rawUrl = event.notification.data?.url ?? '/'; // N'autoriser que les URLs relatives ou du même domaine (protection open redirect) const targetUrl = (rawUrl.startsWith('/') || rawUrl.startsWith(self.location.origin)) ? rawUrl : '/'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => { for (const client of windowClients) { if (client.url === targetUrl && 'focus' in client) { return client.focus(); } } if (clients.openWindow) { return clients.openWindow(targetUrl); } }) ); });