enhanced security and optimization

This commit is contained in:
2026-10-14 20:55:20 +02:00
parent b31fac8eaa
commit d9d7990ad9
6 changed files with 3130 additions and 409 deletions
+101 -43
View File
@@ -1,63 +1,121 @@
const CACHE_NAME = 'fleet-c2-v2'; // Incrementiamo la versione
const urlsToCache = [
const CACHE_NAME = 'fleet-c2-v3'; // incrementare a ogni modifica degli asset precaricati
const PRECACHE_URLS = [
'/',
'/manifest.json',
'/icon-512.png'
];
// --- 1. GESTIONE CACHE (Il tuo codice originale) ---
// --- 1. INSTALL: precache "best effort" -------------------------------------
// Se un file manca (es. icona non ancora caricata) l'installazione NON fallisce:
// un SW che non si installa significa niente notifiche push.
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
.then(cache => Promise.allSettled(PRECACHE_URLS.map(url => cache.add(url))))
.then(() => self.skipWaiting())
);
});
// --- 2. ACTIVATE: elimina le cache delle versioni precedenti -----------------
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys()
.then(keys => Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))))
.then(() => self.clients.claim())
);
});
// --- 3. FETCH: network-first, cache come ripiego offline ---------------------
// Gestiamo solo le GET della nostra origine. API e WebSocket (socket.io) passano
// sempre direttamente dalla rete: i dati della flotta non vanno mai serviti "vecchi".
self.addEventListener('fetch', event => {
const req = event.request;
if (req.method !== 'GET') return;
const url = new URL(req.url);
if (url.origin !== self.location.origin) return;
if (url.pathname.startsWith('/api/') ||
url.pathname.startsWith('/socket.io/') ||
url.pathname === '/sw.js') return;
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
fetch(req)
.then(res => {
if (res && res.ok && res.type === 'basic') {
const copy = res.clone();
caches.open(CACHE_NAME).then(cache => cache.put(req, copy)).catch(() => {});
}
return res;
})
.catch(async () => {
const cached = await caches.match(req);
if (cached) return cached;
if (req.mode === 'navigate') {
const shell = await caches.match('/');
if (shell) return shell;
}
return new Response('Offline', {
status: 503,
statusText: 'Offline',
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
});
})
);
});
// --- 2. GESTIONE NOTIFICHE PUSH (Il nuovo codice) ---
self.addEventListener('push', function(event) {
console.log('[Service Worker] Notifica Push ricevuta.');
// --- 4. NOTIFICHE PUSH -------------------------------------------------------
self.addEventListener('push', event => {
let data = { title: 'Fleet Alert', body: 'Nuovo messaggio dal sistema.' };
let data = { title: 'Fleet Alert', body: 'Nuovo messaggio dal sistema.' };
if (event.data) {
try {
data = Object.assign(data, event.data.json());
} catch (e) {
data.body = event.data.text();
}
}
if (event.data) {
try {
data = event.data.json();
} catch (e) {
data.body = event.data.text();
const options = {
body: data.body,
icon: '/icon-512.png',
badge: '/icon-512.png',
vibrate: [200, 100, 200],
// Stesso tag = la nuova notifica dello stesso nodo sostituisce la precedente
// (invece di accumulare 10 notifiche) e rifà vibrare/suonare.
tag: data.tag || 'fleet-alert',
renotify: true,
data: {
dateOfArrival: Date.now(),
url: data.url || '/'
},
actions: [
{ action: 'explore', title: 'Apri Dashboard' },
{ action: 'close', title: 'Chiudi' }
]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// --- 5. CLICK SULLA NOTIFICA -------------------------------------------------
// Click sul corpo O su "Apri Dashboard": porta in primo piano la finestra già aperta,
// altrimenti ne apre una nuova. "Chiudi" chiude soltanto.
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'close') return;
const target = new URL((event.notification.data && event.notification.data.url) || '/', self.location.origin).href;
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(list => {
for (const client of list) {
if (new URL(client.url).origin === self.location.origin && 'focus' in client) {
return client.focus();
}
}
const options = {
body: data.body,
icon: '/icon-512.png',
badge: '/icon-512.png',
vibrate: [200, 100, 200],
data: {
dateOfArrival: Date.now(),
primaryKey: '1'
},
actions: [
{ action: 'explore', title: 'Apri Dashboard' },
{ action: 'close', title: 'Chiudi' }
]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
self.addEventListener('notificationclick', function(event) {
event.notification.close();
if (event.action === 'explore') {
event.waitUntil(
clients.openWindow('/')
);
}
}
return clients.openWindow(target);
})
);
});