/* * NIS2 Agile — Service Worker (PWA) * ----------------------------------------------------------------------------- * Strategia (SaaS autenticato multi-tenant — sicurezza prima di tutto): * - /api/* -> NETWORK-ONLY, MAI in cache. Nessun dato autenticato * viene cacheato => zero leak cross-utente su un * dispositivo condiviso. La sessione resta gestita da * JWT / auth-gate.js, fuori dal service worker. * - navigazioni HTML -> NETWORK-FIRST, fallback alla cache e infine /offline.html. * - asset statici -> STALE-WHILE-REVALIDATE (risposta immediata dalla cache, * aggiornamento in background). * * Il nome cache include la release: va BUMPATO ad ogni rilascio (vedi version.json), * cosi' activate elimina automaticamente la shell vecchia. */ const CACHE = 'nis2-shell-v1.25.4'; // Shell V3 (top-nav): niente più style.css/common-bi.js/bootstrap-italia (UI V2). const PRECACHE = [ '/offline.html', '/manifest.webmanifest', '/css/v3.css', '/js/common.js', '/js/api.js', '/js/i18n.js', '/js/help.js', '/js/topnav-v3.js', '/js/pwa.js', '/vendor/inter/inter.css', '/vendor/lucide/lucide.min.js', '/assets/icons/icon-192.png', '/assets/icons/icon-512.png' ]; self.addEventListener('install', (event) => { event.waitUntil((async () => { const cache = await caches.open(CACHE); // add uno-a-uno: un singolo asset mancante non deve far fallire l'install await Promise.all(PRECACHE.map((url) => cache.add(new Request(url, { cache: 'reload' })).catch((err) => console.warn('[sw] precache skip', url, err && err.message)) )); await self.skipWaiting(); })()); }); self.addEventListener('activate', (event) => { event.waitUntil((async () => { const keys = await caches.keys(); await Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))); await self.clients.claim(); })()); }); self.addEventListener('fetch', (event) => { const req = event.request; if (req.method !== 'GET') return; // non-GET: rete normale const url = new URL(req.url); if (url.origin !== self.location.origin) return; // cross-origin: rete normale // /api/* -> network-only, mai in cache if (url.pathname.startsWith('/api/')) { event.respondWith( fetch(req).catch(() => new Response( JSON.stringify({ error: 'offline', message: 'Connessione di rete assente' }), { status: 503, headers: { 'Content-Type': 'application/json' } } )) ); return; } // navigazioni HTML -> network-first, fallback cache -> offline if (req.mode === 'navigate') { event.respondWith((async () => { try { return await fetch(req); } catch (e) { const cached = await caches.match(req); return cached || (await caches.match('/offline.html')); } })()); return; } // asset statici stesso-origin -> stale-while-revalidate event.respondWith((async () => { const cache = await caches.open(CACHE); const cached = await cache.match(req); const network = fetch(req).then((res) => { if (res && res.status === 200 && res.type === 'basic') { cache.put(req, res.clone()); } return res; }).catch(() => null); return cached || (await network) || (await caches.match('/offline.html')); })()); });