- sistemato il carrello su GruppoMacro e su PiuCheBuono. - Corretto visualizzazione della scontistica. - Se un prodotto viene cancellato ora lo cancella anche sul carrello.
468 lines
14 KiB
JavaScript
Executable File
468 lines
14 KiB
JavaScript
Executable File
/* global importScripts */
|
|
/* global idbKeyval */
|
|
/* global workbox */
|
|
/* global cfgenv */
|
|
|
|
const VITE_APP_VERSION = '1.2.71';
|
|
|
|
// Costanti di configurazione
|
|
const DYNAMIC_CACHE = 'dynamic-cache-v2';
|
|
const baseUrl = self.location.origin;
|
|
|
|
const CACHE_VERSION = VITE_APP_VERSION;
|
|
const CACHE_PREFIX = self.location.hostname || 'app';
|
|
|
|
function extractDomain(url) {
|
|
return url.replace(/^https?:\/\//, '');
|
|
}
|
|
|
|
function removeTestPrefix(str) {
|
|
return str.startsWith('test.') ? str.slice(5) : str;
|
|
}
|
|
|
|
// Funzione per determinare il dominio API
|
|
function determineApiDomain(appDomain) {
|
|
if (ISTEST) {
|
|
return 'testapi.' + removeTestPrefix(appDomain);
|
|
}
|
|
return appDomain.includes('localhost') ? 'localhost:3000' : 'api.' + appDomain;
|
|
}
|
|
|
|
const APP_DOMAIN = extractDomain(baseUrl);
|
|
const API_DOMAIN = determineApiDomain(APP_DOMAIN);
|
|
|
|
console.log('API_DOMAIN', API_DOMAIN);
|
|
|
|
const CACHE_NAME = 'pwa-cache-' + VITE_APP_VERSION; // Nome della cache
|
|
|
|
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, NetworkFirst, 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 ✅ ');
|
|
} 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 && name !== DYNAMIC_CACHE).map((name) => caches.delete(name))
|
|
);
|
|
})
|
|
);
|
|
});
|
|
|
|
console.log(' [ VER-' + VITE_APP_VERSION + ' ] _---------________------ PAO: this is my custom service worker: ');
|
|
|
|
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$/] }
|
|
)
|
|
)
|
|
}*/
|
|
|
|
// Static assets (JS, CSS, Fonts) - CacheFirst: caricamento rapidissimo
|
|
registerRoute(
|
|
({ request }) => ['script', 'style', 'font'].includes(request.destination),
|
|
new CacheFirst({
|
|
cacheName: `${CACHE_PREFIX}-static-assets-${CACHE_VERSION}`,
|
|
plugins: [
|
|
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
|
new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 30 * 24 * 60 * 60 }), // 30 giorni
|
|
],
|
|
})
|
|
);
|
|
|
|
// Immagini - CacheFirst con scadenza e limite
|
|
registerRoute(
|
|
({ request }) => request.destination === 'image',
|
|
new CacheFirst({
|
|
cacheName: `${CACHE_PREFIX}-images-${CACHE_VERSION}`,
|
|
plugins: [
|
|
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
|
new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 }), // 30 giorni
|
|
],
|
|
})
|
|
);
|
|
|
|
// Google Fonts - StaleWhileRevalidate per aggiornamenti trasparenti
|
|
registerRoute(
|
|
/^https:\/\/fonts\.(?:googleapis|gstatic)\.com/,
|
|
new StaleWhileRevalidate({
|
|
cacheName: `${CACHE_PREFIX}-google-fonts-${CACHE_VERSION}`,
|
|
plugins: [new CacheableResponsePlugin({ statuses: [0, 200] }), new ExpirationPlugin({ maxEntries: 30 })],
|
|
})
|
|
);
|
|
|
|
// HTML documents - NetworkFirst: garantisce contenuti aggiornati e fallback cache
|
|
registerRoute(
|
|
({ request }) => request.destination === 'document',
|
|
new NetworkFirst({
|
|
cacheName: `${CACHE_PREFIX}-html-cache-${CACHE_VERSION}`,
|
|
networkTimeoutSeconds: 5, // timeout rapido
|
|
plugins: [
|
|
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
|
new ExpirationPlugin({ maxEntries: 20, maxAgeSeconds: 24 * 60 * 60 }), // 1 giorno
|
|
],
|
|
})
|
|
);
|
|
|
|
// API calls - NetworkFirst con fallback cache e timeout veloce
|
|
registerRoute(
|
|
({ url }) => url.hostname === API_DOMAIN,
|
|
new NetworkFirst({
|
|
cacheName: `${CACHE_PREFIX}-api-cache-${CACHE_VERSION}`,
|
|
networkTimeoutSeconds: 5,
|
|
fetchOptions: { credentials: 'include' },
|
|
plugins: [
|
|
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
|
new ExpirationPlugin({ maxEntries: 50, 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();
|
|
}
|
|
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] = 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 ***** ');
|