- Aggiornamento Viaggi
This commit is contained in:
@@ -117,7 +117,7 @@ self.addEventListener('activate', (event) => {
|
||||
);
|
||||
})
|
||||
);
|
||||
self.clients.claim(); // SERVE? OPPURE NO ?
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
const USASYNC = false;
|
||||
@@ -263,297 +263,572 @@ if (workbox) {
|
||||
networkTimeoutSeconds: 10, // timeout rapido
|
||||
plugins: [
|
||||
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
||||
new ExpirationPlugin({ maxEntries: 20, maxAgeSeconds: 24 * 60 * 60 }), // 1 giorno
|
||||
new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 24 * 60 * 60 }), // 1 giorno
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// API calls - NetworkFirst con fallback cache e timeout veloce
|
||||
// API calls - NetworkFirst con timeout breve
|
||||
registerRoute(
|
||||
({ url }) => url.hostname === API_DOMAIN,
|
||||
new NetworkFirst({
|
||||
cacheName: `${CACHE_PREFIX}-api-cache-${CACHE_VERSION}`,
|
||||
networkTimeoutSeconds: 10,
|
||||
fetchOptions: { credentials: 'include' },
|
||||
networkTimeoutSeconds: 5,
|
||||
plugins: [
|
||||
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
||||
new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 5 * 60 }), // 5 minuti
|
||||
new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 5 * 60 }), // 5 minuti
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
registerRoute(new RegExp('/admin/'), new NetworkOnly());
|
||||
|
||||
function generateUUID() {
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
||||
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
||||
);
|
||||
}
|
||||
|
||||
const syncStore = {};
|
||||
self.addEventListener('message', (event) => {
|
||||
if (
|
||||
event.data &&
|
||||
(event.data.type === 'SKIP_WAITING' || event.data.action === 'skipWaiting')
|
||||
) {
|
||||
self.skipWaiting();
|
||||
// Opzionale: rispondi al client
|
||||
if (event.ports && event.ports[0]) {
|
||||
event.ports[0].postMessage({ success: true });
|
||||
}
|
||||
}
|
||||
if (event.data.type === 'sync') {
|
||||
console.log('addEventListener - message');
|
||||
const id = generateUUID();
|
||||
syncStore[id] = event.data;
|
||||
self.registration.sync.register(id);
|
||||
}
|
||||
console.log(event.data);
|
||||
});
|
||||
|
||||
// Funzione per gestire richieste API
|
||||
async function handleApiRequest(request) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
|
||||
// Se la risposta non è valida, restituisci un errore personalizzato
|
||||
if (!response.ok) {
|
||||
console.warn('[SW] API Response Error:', response.status, response.statusText);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'API error',
|
||||
message: `❌ Invalid response from API: ${response.status} ${response.statusText}`,
|
||||
}),
|
||||
{ status: response.status, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[Service Worker] API request error ❌:', error);
|
||||
|
||||
// Restituisci una risposta di errore personalizzata
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Network error',
|
||||
message: '❌ Unable to fetch from API: ' + error.message,
|
||||
}),
|
||||
{ status: 503, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Funzione per effettuare una richiesta di rete e memorizzare nella cache
|
||||
async function fetchAndCache(request) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
|
||||
// Clona e salva la risposta nella cache solo se valida
|
||||
if (response.ok) {
|
||||
const responseClone = response.clone();
|
||||
cache.put(request, responseClone);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[SW] Fetch and cache error ❌:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategia di caching: stale-while-revalidate
|
||||
async function cacheWithStaleWhileRevalidate(request, event) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
|
||||
// Prova a recuperare la risorsa dalla cache
|
||||
const cachedResponse = await cache.match(request);
|
||||
if (cachedResponse) {
|
||||
// Aggiorna in background mentre restituisci la risposta in cache
|
||||
event.waitUntil(
|
||||
fetchAndCache(request).catch((error) => {
|
||||
console.error('[SW] Background fetch and cache error ❌:', error);
|
||||
})
|
||||
);
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Se non è in cache, fai la richiesta di rete
|
||||
try {
|
||||
return await fetchAndCache(request);
|
||||
} catch (error) {
|
||||
console.error('[SW] Cache miss and network error ❌:', error);
|
||||
|
||||
// Restituisci una risposta di fallback personalizzata
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Network error',
|
||||
message: 'Unable to fetch resource from network or cache.',
|
||||
}),
|
||||
{ status: 503, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Listener per gestire tutte le richieste
|
||||
/*self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
const url = new URL(request.url);
|
||||
try {
|
||||
// Ignora richieste non gestibili
|
||||
if (request.method !== 'GET' || url.protocol !== 'https:') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignora richieste per file di sviluppo (es. /src/)
|
||||
if (url.pathname.startsWith('/src/') || url.search.includes('vue&type')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Gestione richieste API
|
||||
if (url.hostname === API_DOMAIN) {
|
||||
if (debug) console.log("E' una RICHIESTA API!");
|
||||
event.respondWith(handleApiRequest(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Gestione risorse statiche e altre richieste
|
||||
if (debug) console.log("E' una RICHIESTA statica...");
|
||||
event.respondWith(cacheWithStaleWhileRevalidate(request, event));
|
||||
} catch (error) {
|
||||
console.error('[Service Worker] Fetch error ❌:', error);
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// Gestione degli errori non catturati
|
||||
self.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('[Service Worker] Unhandled rejection ❌:', event.reason);
|
||||
});
|
||||
|
||||
// Gestione degli errori globali
|
||||
self.addEventListener('error', (event) => {
|
||||
console.error('[Service Worker] Global error ❌:', event.error);
|
||||
});
|
||||
|
||||
// Funzione di utilità per il logging (decommentare se necessario)
|
||||
// function logFetchDetails(request) {
|
||||
// console.log('[Service Worker] Fetching:', request.url);
|
||||
// console.log('Cache mode:', request.cache);
|
||||
// console.log('Request mode:', request.mode);
|
||||
// }
|
||||
|
||||
self.addEventListener('sync', (event) => {
|
||||
console.log('[Service Worker V5] Background syncing', event);
|
||||
|
||||
let mystrparam = event.tag;
|
||||
let multiparams = mystrparam.split('|');
|
||||
if (multiparams && multiparams.length > 3) {
|
||||
let [cmd, table, method, token, refreshToken, browser_random] = multiparams;
|
||||
|
||||
if (cmd === 'sync-todos') {
|
||||
console.log('[Service Worker] Syncing', cmd, table, method);
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append('content-Type', 'application/json');
|
||||
headers.append('Accept', 'application/json');
|
||||
headers.append('x-auth', token);
|
||||
headers.append('x-refrtok', refreshToken);
|
||||
headers.append('x-browser-random', browser_random);
|
||||
|
||||
event.waitUntil(
|
||||
readAllData(table).then((alldata) => {
|
||||
const myrecs = [...alldata];
|
||||
let errorfromserver = false;
|
||||
|
||||
if (myrecs) {
|
||||
let promiseChain = Promise.resolve();
|
||||
|
||||
for (let rec of myrecs) {
|
||||
//TODO: Risistemare con calma... per ora non penso venga usato...
|
||||
let link = cfgenv.serverweb + '/todos';
|
||||
if (method !== 'POST') link += '/' + rec._id;
|
||||
|
||||
promiseChain = promiseChain.then(() =>
|
||||
fetch(link, {
|
||||
method: method,
|
||||
headers: headers,
|
||||
cache: 'no-cache',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(rec),
|
||||
})
|
||||
.then(() => {
|
||||
deleteItemFromData(table, rec._id);
|
||||
deleteItemFromData('swmsg', mystrparam);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.message === 'Failed to fetch') {
|
||||
errorfromserver = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return promiseChain.then(() => {
|
||||
const mystate = !errorfromserver ? 'online' : 'offline';
|
||||
writeData('config', { _id: 2, stateconn: mystate });
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Notifications
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
const { notification } = event;
|
||||
const { action } = event;
|
||||
|
||||
if (action === 'confirm') {
|
||||
notification.close();
|
||||
} else {
|
||||
event.waitUntil(
|
||||
self.clients.matchAll().then((clis) => {
|
||||
const client = clis.find((c) => c.visibilityState === 'visible');
|
||||
if (client) {
|
||||
client.navigate(notification.data.url);
|
||||
client.focus();
|
||||
} else {
|
||||
self.clients.openWindow(notification.data.url);
|
||||
}
|
||||
notification.close();
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclose', (event) => {
|
||||
console.log('Notification was closed', event);
|
||||
});
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
console.log('Push Notification received', event);
|
||||
let data = event.data
|
||||
? event.data.json()
|
||||
: { title: 'New!', content: 'Something new happened!', url: '/' };
|
||||
|
||||
const options = {
|
||||
body: data.content,
|
||||
icon: data.icon ? data.icon : '/images/android-chrome-192x192.png',
|
||||
badge: data.badge ? data.badge : '/images/badge-96x96.png',
|
||||
data: { url: data.url },
|
||||
tag: data.tag,
|
||||
};
|
||||
|
||||
event.waitUntil(self.registration.showNotification(data.title, options));
|
||||
|
||||
const myid = data.id || '0';
|
||||
self.registration.sync.register(myid);
|
||||
writeData('notifications', { _id: myid, tag: options.tag });
|
||||
});
|
||||
} else {
|
||||
console.warn('Workbox could not be loaded.');
|
||||
}
|
||||
|
||||
console.log('***** FINE CUSTOM-SERVICE-WORKER.JS ***** ');
|
||||
// Generate UUID per sync
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
let syncStore = {};
|
||||
|
||||
// Message event handler
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
// Opzionale: rispondi al client
|
||||
if (event.ports && event.ports[0]) {
|
||||
event.ports[0].postMessage({ success: true });
|
||||
}
|
||||
}
|
||||
if (event.data.type === 'sync') {
|
||||
console.log('addEventListener - message');
|
||||
const id = generateUUID();
|
||||
syncStore[id] = event.data;
|
||||
self.registration.sync.register(id);
|
||||
}
|
||||
console.log(event.data);
|
||||
});
|
||||
|
||||
// Funzione per gestire richieste API
|
||||
async function handleApiRequest(request) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
|
||||
// Se la risposta non è valida, restituisci un errore personalizzato
|
||||
if (!response.ok) {
|
||||
console.warn('[SW] API Response Error:', response.status, response.statusText);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'API error',
|
||||
message: `❌ Invalid response from API: ${response.status} ${response.statusText}`,
|
||||
}),
|
||||
{ status: response.status, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[Service Worker] API request error ❌:', error);
|
||||
|
||||
// Restituisci una risposta di errore personalizzata
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Network error',
|
||||
message: '❌ Unable to fetch from API: ' + error.message,
|
||||
}),
|
||||
{ status: 503, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Funzione per effettuare una richiesta di rete e memorizzare nella cache
|
||||
async function fetchAndCache(request) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
|
||||
// Clona e salva la risposta nella cache solo se valida
|
||||
if (response.ok) {
|
||||
const responseClone = response.clone();
|
||||
cache.put(request, responseClone);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[SW] Fetch and cache error ❌:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategia di caching: stale-while-revalidate
|
||||
async function cacheWithStaleWhileRevalidate(request, event) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
|
||||
// Prova a recuperare la risorsa dalla cache
|
||||
const cachedResponse = await cache.match(request);
|
||||
if (cachedResponse) {
|
||||
// Aggiorna in background mentre restituisci la risposta in cache
|
||||
event.waitUntil(
|
||||
fetchAndCache(request).catch((error) => {
|
||||
console.error('[SW] Background fetch and cache error ❌:', error);
|
||||
})
|
||||
);
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Se non è in cache, fai la richiesta di rete
|
||||
try {
|
||||
return await fetchAndCache(request);
|
||||
} catch (error) {
|
||||
console.error('[SW] Cache miss and network error ❌:', error);
|
||||
|
||||
// Restituisci una risposta di fallback personalizzata
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Network error',
|
||||
message: 'Unable to fetch resource from network or cache.',
|
||||
}),
|
||||
{ status: 503, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Listener per gestire tutte le richieste
|
||||
/*self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
const url = new URL(request.url);
|
||||
try {
|
||||
// Ignora richieste non gestibili
|
||||
if (request.method !== 'GET' || url.protocol !== 'https:') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignora richieste per file di sviluppo (es. /src/)
|
||||
if (url.pathname.startsWith('/src/') || url.search.includes('vue&type')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Gestione richieste API
|
||||
if (url.hostname === API_DOMAIN) {
|
||||
if (debug) console.log("E' una RICHIESTA API!");
|
||||
event.respondWith(handleApiRequest(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Gestione risorse statiche e altre richieste
|
||||
if (debug) console.log("E' una RICHIESTA statica...");
|
||||
event.respondWith(cacheWithStaleWhileRevalidate(request, event));
|
||||
} catch (error) {
|
||||
console.error('[Service Worker] Fetch error ❌:', error);
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// Gestione degli errori non catturati
|
||||
self.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('[Service Worker] Unhandled rejection ❌:', event.reason);
|
||||
});
|
||||
|
||||
// Gestione degli errori globali
|
||||
self.addEventListener('error', (event) => {
|
||||
console.error('[Service Worker] Global error ❌:', event.error);
|
||||
});
|
||||
|
||||
// Background Sync event
|
||||
self.addEventListener('sync', (event) => {
|
||||
console.log('[Service Worker V5] Background syncing', event);
|
||||
|
||||
let mystrparam = event.tag;
|
||||
let multiparams = mystrparam.split('|');
|
||||
if (multiparams && multiparams.length > 3) {
|
||||
let [cmd, table, method, token, refreshToken, browser_random] = multiparams;
|
||||
|
||||
if (cmd === 'sync-todos') {
|
||||
console.log('[Service Worker] Syncing', cmd, table, method);
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append('content-Type', 'application/json');
|
||||
headers.append('Accept', 'application/json');
|
||||
headers.append('x-auth', token);
|
||||
headers.append('x-refrtok', refreshToken);
|
||||
headers.append('x-browser-random', browser_random);
|
||||
|
||||
event.waitUntil(
|
||||
readAllData(table).then((alldata) => {
|
||||
const myrecs = [...alldata];
|
||||
let errorfromserver = false;
|
||||
|
||||
if (myrecs) {
|
||||
let promiseChain = Promise.resolve();
|
||||
|
||||
for (let rec of myrecs) {
|
||||
//TODO: Risistemare con calma... per ora non penso venga usato...
|
||||
let link = cfgenv.serverweb + '/todos';
|
||||
if (method !== 'POST') link += '/' + rec._id;
|
||||
|
||||
promiseChain = promiseChain.then(() =>
|
||||
fetch(link, {
|
||||
method: method,
|
||||
headers: headers,
|
||||
cache: 'no-cache',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(rec),
|
||||
})
|
||||
.then(() => {
|
||||
deleteItemFromData(table, rec._id);
|
||||
deleteItemFromData('swmsg', mystrparam);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.message === 'Failed to fetch') {
|
||||
errorfromserver = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return promiseChain.then(() => {
|
||||
const mystate = !errorfromserver ? 'online' : 'offline';
|
||||
writeData('config', { _id: 2, stateconn: mystate });
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Sync per notifiche (se necessario)
|
||||
if (event.tag === 'sync-notifications') {
|
||||
event.waitUntil(
|
||||
Promise.resolve().then(() => {
|
||||
console.log('[SW] Syncing notifications');
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// 🔔 PUSH NOTIFICATIONS - BACKWARD COMPATIBLE VERSION
|
||||
// ========================================
|
||||
|
||||
// Push event - BACKWARD COMPATIBLE con formato vecchio E nuovo
|
||||
self.addEventListener('push', (event) => {
|
||||
console.log('[Service Worker] 🔔 Push notification received:', event);
|
||||
|
||||
// Default data - supporta ENTRAMBI i formati (vecchio e nuovo)
|
||||
let data = {
|
||||
title: 'New!',
|
||||
body: 'Something new happened!',
|
||||
content: 'Something new happened!', // VECCHIO formato
|
||||
icon: '/images/android-chrome-192x192.png', // VECCHIO path
|
||||
badge: '/images/badge-96x96.png', // VECCHIO path
|
||||
tag: 'default',
|
||||
url: '/',
|
||||
requireInteraction: false,
|
||||
notificationType: null,
|
||||
rideId: null,
|
||||
fromUser: null,
|
||||
actions: []
|
||||
};
|
||||
|
||||
// Parse del payload se presente
|
||||
if (event.data) {
|
||||
try {
|
||||
const payload = event.data.json();
|
||||
data = { ...data, ...payload };
|
||||
|
||||
// BACKWARD COMPATIBILITY: Se c'è 'content' ma non 'body', usa 'content'
|
||||
if (payload.content && !payload.body) {
|
||||
data.body = payload.content;
|
||||
}
|
||||
// Se c'è 'body' ma non 'content', sincronizza
|
||||
if (payload.body && !payload.content) {
|
||||
data.content = payload.body;
|
||||
}
|
||||
|
||||
console.log('[SW] 📦 Parsed notification payload:', data);
|
||||
} catch (e) {
|
||||
console.warn('[SW] ⚠️ Failed to parse push payload as JSON, using text');
|
||||
const textData = event.data.text();
|
||||
data.body = textData;
|
||||
data.content = textData;
|
||||
}
|
||||
}
|
||||
|
||||
// Configura le actions SOLO se c'è un notificationType (nuovo formato)
|
||||
if (data.notificationType && (!data.actions || data.actions.length === 0)) {
|
||||
data.actions = getNotificationActions(data.notificationType, data.rideId);
|
||||
}
|
||||
|
||||
// Opzioni della notifica - supporta ENTRAMBI i path delle icone
|
||||
const options = {
|
||||
body: data.body || data.content, // Fallback a 'content' se 'body' non c'è
|
||||
icon: data.icon,
|
||||
badge: data.badge,
|
||||
tag: data.tag,
|
||||
requireInteraction: data.requireInteraction || false,
|
||||
vibrate: data.vibrate || [200, 100, 200],
|
||||
data: {
|
||||
dateOfArrival: Date.now(),
|
||||
url: data.url || '/',
|
||||
rideId: data.rideId,
|
||||
notificationType: data.notificationType,
|
||||
fromUser: data.fromUser,
|
||||
// BACKWARD COMPATIBILITY: mantieni anche il formato vecchio
|
||||
legacyFormat: !data.notificationType // true se è formato vecchio
|
||||
},
|
||||
actions: data.actions || []
|
||||
};
|
||||
|
||||
// Mostra la notifica
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title, options).then(() => {
|
||||
console.log('[SW] ✅ Notification displayed successfully');
|
||||
|
||||
// Salva la notifica in IndexedDB - BACKWARD COMPATIBLE
|
||||
const myid = data.id || generateUUID();
|
||||
self.registration.sync.register(myid);
|
||||
|
||||
// Formato compatibile: se è vecchio formato usa solo _id e tag
|
||||
const notificationData = options.data.legacyFormat
|
||||
? { _id: myid, tag: options.tag }
|
||||
: {
|
||||
_id: myid,
|
||||
tag: options.tag,
|
||||
type: data.notificationType,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
writeData('notifications', notificationData).catch(err => {
|
||||
console.error('[SW] Failed to save notification to IndexedDB:', err);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Funzione helper per generare actions (SOLO per nuovo formato)
|
||||
function getNotificationActions(notificationType, rideId) {
|
||||
const actions = [];
|
||||
|
||||
switch (notificationType) {
|
||||
case 'newRideRequest':
|
||||
actions.push(
|
||||
{ action: 'view-requests', title: '👀 Visualizza richieste' },
|
||||
{ action: 'dismiss', title: '❌ Ignora' }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'requestAccepted':
|
||||
actions.push(
|
||||
{ action: 'view-ride', title: '🚗 Vedi viaggio' },
|
||||
{ action: 'message', title: '💬 Messaggio' }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'requestRejected':
|
||||
actions.push(
|
||||
{ action: 'search-rides', title: '🔍 Cerca altri viaggi' }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'newMessage':
|
||||
actions.push(
|
||||
{ action: 'open-chat', title: '💬 Apri chat' },
|
||||
{ action: 'dismiss', title: '✓ OK' }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'rideReminder':
|
||||
actions.push(
|
||||
{ action: 'view-ride', title: '📍 Vedi dettagli' },
|
||||
{ action: 'dismiss', title: '✓ OK' }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'rideCancelled':
|
||||
actions.push(
|
||||
{ action: 'search-rides', title: '🔍 Cerca alternativa' }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'feedbackRequest':
|
||||
actions.push(
|
||||
{ action: 'leave-feedback', title: '⭐ Lascia feedback' },
|
||||
{ action: 'dismiss', title: 'Dopo' }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'newFeedbackReceived':
|
||||
actions.push(
|
||||
{ action: 'view-feedback', title: '⭐ Vedi feedback' }
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
actions.push(
|
||||
{ action: 'open-app', title: '📱 Apri app' }
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Notification click event - BACKWARD COMPATIBLE
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
console.log('[Service Worker] 🖱️ Notification clicked:', event);
|
||||
console.log('[SW] Action:', event.action);
|
||||
console.log('[SW] Notification data:', event.notification.data);
|
||||
|
||||
// Chiudi la notifica
|
||||
event.notification.close();
|
||||
|
||||
// Estrai i dati dalla notifica
|
||||
const notificationData = event.notification.data || {};
|
||||
const isLegacyFormat = notificationData.legacyFormat || false;
|
||||
|
||||
let finalUrl;
|
||||
|
||||
// BACKWARD COMPATIBILITY: Se è formato vecchio, usa solo notification.data.url
|
||||
if (isLegacyFormat || !notificationData.notificationType) {
|
||||
console.log('[SW] 📜 Using legacy notification format');
|
||||
finalUrl = notificationData.url || '/';
|
||||
} else {
|
||||
// NUOVO formato: usa il routing intelligente
|
||||
console.log('[SW] 🆕 Using new notification format with smart routing');
|
||||
const rideId = notificationData.rideId;
|
||||
const notificationType = notificationData.notificationType;
|
||||
const action = event.action;
|
||||
|
||||
finalUrl = determineUrlFromNotification(action, notificationType, rideId);
|
||||
}
|
||||
|
||||
console.log('[SW] 🎯 Navigating to:', finalUrl);
|
||||
|
||||
// Se finalUrl è null (action 'dismiss'), non fare nulla
|
||||
if (!finalUrl) {
|
||||
console.log('[SW] Action dismissed, no navigation');
|
||||
return;
|
||||
}
|
||||
|
||||
// Apri l'URL appropriato
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window', includeUncontrolled: true })
|
||||
.then((clientList) => {
|
||||
console.log('[SW] Found', clientList.length, 'open windows');
|
||||
|
||||
// Cerca una finestra già aperta con l'URL desiderato
|
||||
for (const client of clientList) {
|
||||
if (client.url.includes(finalUrl.split('?')[0]) && 'focus' in client) {
|
||||
console.log('[SW] ✅ Focusing existing window');
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Cerca una finestra dell'app aperta e naviga lì
|
||||
for (const client of clientList) {
|
||||
if (client.url.includes(self.location.origin) && 'navigate' in client) {
|
||||
console.log('[SW] ✅ Navigating existing window to:', finalUrl);
|
||||
return client.navigate(finalUrl).then(client => client.focus());
|
||||
}
|
||||
}
|
||||
|
||||
// Altrimenti apri una nuova finestra
|
||||
if (clients.openWindow) {
|
||||
console.log('[SW] 🆕 Opening new window');
|
||||
return clients.openWindow(finalUrl);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[SW] ❌ Error handling notification click:', error);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Funzione per determinare l'URL (SOLO per nuovo formato)
|
||||
function determineUrlFromNotification(action, notificationType, rideId) {
|
||||
// Gestisci prima le actions specifiche
|
||||
if (action) {
|
||||
switch (action) {
|
||||
case 'view-requests':
|
||||
return '/trasporti/miei-viaggi?tab=requests';
|
||||
|
||||
case 'view-ride':
|
||||
return rideId ? `/trasporti/viaggio/${rideId}` : '/trasporti/miei-viaggi';
|
||||
|
||||
case 'message':
|
||||
case 'open-chat':
|
||||
return rideId ? `/trasporti/chat?ride=${rideId}` : '/trasporti/chat';
|
||||
|
||||
case 'search-rides':
|
||||
return '/trasporti?view=search';
|
||||
|
||||
case 'leave-feedback':
|
||||
return rideId ? `/trasporti/feedback/${rideId}` : '/trasporti/feedback';
|
||||
|
||||
case 'view-feedback':
|
||||
return '/trasporti/miei-feedback';
|
||||
|
||||
case 'open-app':
|
||||
return '/trasporti';
|
||||
|
||||
case 'dismiss':
|
||||
return null; // Non fare nulla
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Se non c'è action, usa il tipo di notifica
|
||||
if (notificationType) {
|
||||
switch (notificationType) {
|
||||
case 'newRideRequest':
|
||||
return '/trasporti/miei-viaggi?tab=requests';
|
||||
|
||||
case 'requestAccepted':
|
||||
case 'requestRejected':
|
||||
case 'rideReminder':
|
||||
return rideId ? `/trasporti/viaggio/${rideId}` : '/trasporti/miei-viaggi';
|
||||
|
||||
case 'newMessage':
|
||||
return '/trasporti/chat';
|
||||
|
||||
case 'rideCancelled':
|
||||
return '/trasporti/miei-viaggi?tab=cancelled';
|
||||
|
||||
case 'feedbackRequest':
|
||||
return rideId ? `/trasporti/feedback/${rideId}` : '/trasporti/feedback';
|
||||
|
||||
case 'newFeedbackReceived':
|
||||
return '/trasporti/miei-feedback';
|
||||
|
||||
default:
|
||||
return '/trasporti';
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return '/trasporti';
|
||||
}
|
||||
|
||||
// Notification close event - tracking opzionale
|
||||
self.addEventListener('notificationclose', (event) => {
|
||||
console.log('[Service Worker] 🔕 Notification closed:', event);
|
||||
|
||||
const notificationData = event.notification.data || {};
|
||||
|
||||
// Solo per nuovo formato, traccia le dismissioni
|
||||
if (!notificationData.legacyFormat && notificationData.notificationType) {
|
||||
writeData('notification-dismissals', {
|
||||
_id: generateUUID(),
|
||||
type: notificationData.notificationType,
|
||||
timestamp: Date.now(),
|
||||
tag: event.notification.tag
|
||||
}).catch(err => {
|
||||
console.error('[SW] Failed to track notification dismissal:', err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('***** 🚀 CUSTOM-SERVICE-WORKER.JS - BACKWARD COMPATIBLE VERSION ***** ');
|
||||
|
||||
Reference in New Issue
Block a user