Simon C3: l'analisi gap ACN non è un artefatto normativo e va eliminata; la conformità emerge da C1 (elenco Misure/Requisiti + valutazione per requisito in org_requisito_state). Ricognizione → migrazione → backup → rimozione (sicuro, 0 perdite): - Tabelle acn_assessments/acn_assessment_responses VUOTE (0 righe) -> droppate (mig.049, guard anti-drop se >0 righe). Ricreabili da 036. Nessun dato utente perso. - Integrazione lg231: /api/services/gap-analysis legge 'assessments' generale, NON il modulo ACN -> intatta. Verificato. - RaciController ripuntato su cfg_nis2_misure (fonte canonica) al posto di acn_measures.json (rimosso); validazione measure_code verificata (/raci/objects?type=measure -> 43 misure). Rimossi: AcnAssessmentController, public/acn-gap.html, application/data/acn_measures.json, routing acn-gap, voci sidebar (common.js + common-bi.js), metodi api.js acn*, sezione help 'acn', chiavi i18n acn.*, mapping demo-selectors. Tour demo step 3 RIPUNTATO su misure-requisiti (no step rotto, 10 step). Sidebar BI ora mostra 'Misure e Requisiti' (gap di common-bi.js sanato). Verifiche prod: /acn-gap.html 404, /api/acn-gap/* 404, /misure-requisiti 200, RaciController OK. Cache-buster ?v=20260624, version 1.18.3, SW v1.18.3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
3.4 KiB
JavaScript
97 lines
3.4 KiB
JavaScript
/*
|
|
* 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.18.3';
|
|
|
|
const PRECACHE = [
|
|
'/offline.html',
|
|
'/manifest.webmanifest',
|
|
'/css/style.css',
|
|
'/js/common.js',
|
|
'/js/common-bi.js',
|
|
'/js/api.js',
|
|
'/js/i18n.js',
|
|
'/js/pwa.js',
|
|
'/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css',
|
|
'/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js',
|
|
'/vendor/bootstrap-italia/dist/svg/sprites.svg',
|
|
'/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'));
|
|
})());
|
|
});
|