/* Service Worker do Notas — caches versionados. * BUMP de CACHE em todo deploy que mude shell/assets (o activate limpa os antigos). */ const CACHE = 'notas-shell-v16'; const SHELL = [ '/', '/css/app.css', '/js/app.js', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/icon-512.png', '/icons/maskable-192.png', '/icons/maskable-512.png', '/icons/monochrome-512.png', ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE) .then((cache) => cache.addAll(SHELL)) .then(() => self.skipWaiting()), ); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys() .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) .then(() => self.clients.claim()) .then(() => self.clients.matchAll({ type: 'window' })) .then((clients) => { for (const client of clients) client.postMessage('notas:updated'); }), ); }); self.addEventListener('message', (event) => { if (event.data === 'notas:skip-waiting') self.skipWaiting(); }); self.addEventListener('fetch', (event) => { const req = event.request; const url = new URL(req.url); // 1. API e SSE: NUNCA interceptar/cache — dados devem ser sempre frescos // (interceptar SSE quebraria o stream de tempo real). if (url.origin !== self.location.origin || url.pathname.startsWith('/api/')) return; // 2. Navegação (abrir o app): network-first com fallback ao shell em cache (offline). if (req.mode === 'navigate' || req.destination === 'document') { event.respondWith( fetch(req) .then((resp) => { const copy = resp.clone(); caches.open(CACHE).then((c) => c.put('/', copy)); return resp; }) .catch(() => caches.match('/')), ); return; } // 3. Estáticos (css/js/ícones/fontes): stale-while-revalidate — // mostra o cache na hora e atualiza em background. event.respondWith( caches.match(req).then((hit) => { const refresh = fetch(req) .then((resp) => { if (resp && resp.ok) { const copy = resp.clone(); caches.open(CACHE).then((c) => c.put(req, copy)); } return resp; }) .catch(() => hit); return hit || refresh; }), ); });