/* global importScripts */ /* global idbKeyval */ /* global workbox */ /* global cfgenv */ const CACHE_NAME = 'pwa-cache-v2'; // Nome della cache const ORA = "12.57" console.log('***** INIZIO CUSTOM-SERVICE-WORKER.JS ' + ORA); //importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js'); importScripts('workbox/workbox-sw.js') import { clientsClaim } from 'workbox-core' import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL } from 'workbox-precaching'; import { registerRoute, NavigationRoute } from 'workbox-routing'; import { setCacheNameDetails } from 'workbox-core'; import { NetworkOnly, StaleWhileRevalidate, CacheFirst, } from 'workbox-strategies'; import { CacheableResponsePlugin } from 'workbox-cacheable-response'; import { ExpirationPlugin } from 'workbox-expiration'; const debug = false; //process.env.NODE_ENV !== 'production'; if (workbox) { // Imposta configurazione prima di tutto workbox.setConfig({ debug }); workbox.loadModule('workbox-strategies'); console.log('Workbox ESISTE ✅ ' + ORA); } else { console.error('Workbox NON CARICATO ! ❌'); } setCacheNameDetails({ prefix: self.location.hostname, suffix: 'v2', precache: 'precache', runtime: 'runtime', }); const precacheList = (self.__WB_MANIFEST || []).filter(entry => { // Esclude tutto ciò che si trova nella cartella 'upload' if (entry.url.includes('/upload/')) { return false; } return true; }); // Precache solo i file filtrati precacheAndRoute(precacheList); cleanupOutdatedCaches() // Installazione del Service Worker self.addEventListener('install', () => { console.log('[Service Worker] Installing ...'); self.skipWaiting() clientsClaim(); }); // Attivazione del Service Worker self.addEventListener('activate', (event) => { console.log('[Service Worker] Activating...'); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== CACHE_NAME) .map((name) => caches.delete(name)) ); }) ); }); const VITE_APP_VERSION = "1.2.14"; console.log(' [ VER-' + VITE_APP_VERSION + ' ] _---------________------ PAO: this is my custom service worker: ' + ORA); try { importScripts('/js/idb.js', '/js/storage.js'); console.log('Local scripts imported successfully.'); } catch (error) { console.error('Failed to import local scripts ❌:', error); } let port = self.location.hostname.startsWith('test') ? 3001 : 3000; let ISTEST = self.location.hostname.startsWith('test'); let ISLOCALE = self.location.hostname.startsWith('localhost'); console.log('SW- app ver ' + VITE_APP_VERSION); // Function helpers async function writeData(table, data) { console.log('writeData', table, data); await idbKeyval.setdata(table, data); } async function readAllData(table) { return idbKeyval.getalldata(table); } async function clearAllData(table) { await idbKeyval.clearalldata(table); } async function deleteItemFromData(table, id) { await idbKeyval.deletedata(table, id); } if (workbox) { /*if (process.env.MODE !== 'ssr' || process.env.PROD) { registerRoute( new NavigationRoute( createHandlerBoundToURL(process.env.PWA_FALLBACK_HTML), { denylist: [new RegExp(process.env.PWA_SERVICE_WORKER_REGEX), /workbox\workbox-(.)*\.js$/] } ) ) }*/ // Cache strategy registrations registerRoute( new RegExp(/\.(?:png|gif|jpg|jpeg)$/), new CacheFirst({ cacheName: `images-upload-${VITE_APP_VERSION}`, plugins: [ new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 }), // 30 Days ], }) ); registerRoute( new RegExp(/\.(?:svg)$/), new CacheFirst({ cacheName: `svg-${VITE_APP_VERSION}`, plugins: [ new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 }), // 30 Days ], }) ); registerRoute( new RegExp(/.*(?:googleapis|gstatic)\.com.*$/), new StaleWhileRevalidate({ cacheName: `google-fonts-${VITE_APP_VERSION}`, plugins: [ new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxEntries: 30 }), ], }) ); /*registerRoute( new RegExp('^/myicons/.*$'), // Corrisponde a percorsi come "/icons/miaicona.ico" new CacheFirst({ cacheName: `icon-cache-${VITE_APP_VERSION}`, plugins: [ new CacheableResponsePlugin({ statuses: [200] }), new ExpirationPlugin({ maxAgeSeconds: 30 * 24 * 60 * 60 }), // 30 Days ], }) );*/ registerRoute( new RegExp(/\.(?:js|css|font)$/), new StaleWhileRevalidate({ cacheName: `js-css-fonts-${VITE_APP_VERSION}`, plugins: [ new CacheableResponsePlugin({ statuses: [200] }), ], }) ); registerRoute( (routeData) => routeData.event.request.headers.get('accept').includes('text/html'), async (args) => { let response = await caches.match(args.event.request); if (response) return response; try { response = await fetch(args.event.request); const cache = await caches.open('dynamic'); cache.put(args.event.request.url, response.clone()); return response; } catch (err) { return caches.match('/offline'); } } ); 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(); } 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); }); function removeTestPrefix(str) { return str.startsWith('test.') ? str.slice(5) : str; } function extractDomain(url) { return url.replace(/^https?:\/\//, ''); } // Funzione per verificare se una richiesta è cross-origin function isCrossOrigin(url) { try { const requestUrl = new URL(url); const baseUrl = self.location.origin; return requestUrl.origin !== baseUrl; } catch (e) { console.error('Error parsing URL: ❌', e); return true; } } // Costanti di configurazione const DYNAMIC_CACHE = 'dynamic-cache-v2'; const ENABLE_DYNAMIC_CACHING = true; const baseUrl = self.location.origin; console.log('baseUrl', baseUrl); const APP_DOMAIN = extractDomain(baseUrl); let API_DOMAIN = ''; if (ISTEST) { API_DOMAIN = 'testapi.' + removeTestPrefix(APP_DOMAIN); } else { if (APP_DOMAIN.includes('localhost')) { API_DOMAIN = 'localhost:3000'; } else { API_DOMAIN = 'api.' + APP_DOMAIN; } } console.log('API_DOMAIN', API_DOMAIN); // Funzione per gestire specificamente le richieste API async function handleApiRequest(request) { const modifiedRequest = new Request(request.url, { method: request.method, headers: { ...Object.fromEntries(request.headers.entries()), 'Origin': `https://${APP_DOMAIN}`, 'Accept': 'application/json', }, mode: 'cors', credentials: 'include', // Abilita le credenziali (cookie, token) }); try { const response = await fetch(modifiedRequest); // Se la risposta è valida, restituiscila if (response.ok) { return response; } // Logga eventuali errori console.warn('[Service Worker] API response not OK:', response.status, response.statusText); 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', 'Access-Control-Allow-Origin': `https://${APP_DOMAIN}`, }, }); } } // Strategia di caching: stale-while-revalidate async function cacheWithStaleWhileRevalidate(request, event) { const cache = await caches.open(CACHE_NAME); // Prova a recuperare la risorsa dalla cache const cachedResponse = await cache.match(request); if (cachedResponse) { // Restituisci la risposta in cache mentre aggiorni in background event.waitUntil(fetchAndCache(request)); return cachedResponse; } // Se non è in cache, fai la richiesta di rete return await fetchAndCache(request); } // Funzione per fare la richiesta di rete e aggiornare la cache async function fetchAndCache(request) { const cache = await caches.open(CACHE_NAME); try { const response = await fetch(request); if (response.ok) { cache.put(request, response.clone()); } return response; } catch (error) { console.error('[Service Worker] Fetch and cache error ❌:', error); return new Response('Network error', { status: 503 }); } } // Listener per gestire tutte le richieste self.addEventListener('fetch', (event) => { const request = event.request; const url = new URL(request.url); // Ignora richieste non gestibili if (request.method !== 'GET' || url.protocol !== 'https:') { return; } // Gestione richieste API if (url.hostname === API_DOMAIN) { if (debug) { console.log('E\' una RICHIESTA API ! ') } event.respondWith(handleApiRequest(request)); return; } if (debug) { console.log('E\' una RICHIESTA statica...') } // Gestione risorse statiche e altre richieste event.respondWith(cacheWithStaleWhileRevalidate(request, event)); }); // 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] = 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); 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 ***** ');