473 lines
14 KiB
JavaScript
Executable File
473 lines
14 KiB
JavaScript
Executable File
/* eslint-disable import/namespace */
|
|
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
|
|
import { registerRoute } from 'workbox-routing';
|
|
import { clientsClaim, setCacheNameDetails, skipWaiting } from 'workbox-core';
|
|
import {
|
|
NetworkFirst,
|
|
NetworkOnly,
|
|
StaleWhileRevalidate,
|
|
CacheFirst,
|
|
} from 'workbox-strategies';
|
|
|
|
const ENABLE_DYNAMIC_CACHING = false;
|
|
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
|
|
import { ExpirationPlugin } from 'workbox-expiration';
|
|
|
|
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
|
|
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');
|
|
|
|
self.addEventListener('install', () => self.skipWaiting());
|
|
self.addEventListener('activate', (event) => {
|
|
// console.log('CLAIM');
|
|
//event.waitUntil(self.clients.claim())
|
|
|
|
event.waitUntil(async () => {
|
|
// Check for a new service worker version
|
|
const registration = await navigator.serviceWorker.getRegistration();
|
|
if (registration && registration.waiting) {
|
|
// A new service worker is waiting, trigger a page reload
|
|
await registration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
|
self.skipWaiting();
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
const APP_VERSION = "1.1.12";
|
|
|
|
console.log(' [ VER-' + APP_VERSION + ' ] _---------________------ PAO: this is my custom service worker');
|
|
|
|
importScripts('js/idb.js', 'js/storage.js');
|
|
|
|
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 ' + APP_VERSION + ' on port ' + port);
|
|
|
|
|
|
// 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) {
|
|
const debug = false;
|
|
workbox.setConfig({ debug });
|
|
|
|
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;
|
|
});
|
|
|
|
setCacheNameDetails({
|
|
prefix: self.location.hostname,
|
|
suffix: 'v1',
|
|
precache: 'precache',
|
|
runtime: 'runtime',
|
|
});
|
|
|
|
// Precache solo i file filtrati
|
|
precacheAndRoute(precacheList);
|
|
|
|
// Cache strategy registrations
|
|
registerRoute(
|
|
new RegExp(/\.(?:png|gif|jpg|jpeg)$/),
|
|
new CacheFirst({
|
|
cacheName: `images-upload-${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-${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-${APP_VERSION}`,
|
|
plugins: [
|
|
new CacheableResponsePlugin({ statuses: [200] }),
|
|
new ExpirationPlugin({ maxEntries: 30 }),
|
|
],
|
|
})
|
|
);
|
|
|
|
registerRoute(
|
|
new RegExp(/.*\/(?:icons).*$/),
|
|
new CacheFirst({
|
|
cacheName: `icon-cache-${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-${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());
|
|
|
|
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 = uuid();
|
|
syncStore[id] = event.data;
|
|
self.registration.sync.register(id);
|
|
}
|
|
console.log(event.data);
|
|
});
|
|
|
|
// Costanti di configurazione
|
|
const DYNAMIC_CACHE = 'dynamic-cache-v1';
|
|
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 {
|
|
API_DOMAIN = 'api.' + APP_DOMAIN;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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'
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(modifiedRequest);
|
|
|
|
// Se la risposta è ok, restituiscila
|
|
if (response.ok) {
|
|
return response;
|
|
}
|
|
|
|
// Se riceviamo un errore CORS, prova con una richiesta no-cors
|
|
if (response.status === 0 || response.type === 'opaque') {
|
|
console.log('[Service Worker] Fallback to no-cors mode for:', request.url);
|
|
return fetch(new Request(request.url, {
|
|
method: 'GET',
|
|
mode: 'no-cors',
|
|
// credentials: 'include'
|
|
}));
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
console.error('[Service Worker] API request error:', error);
|
|
return new Response(JSON.stringify({
|
|
error: 'Network error',
|
|
message: 'Unable to fetch from API'
|
|
}), {
|
|
status: 503,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': `https://${APP_DOMAIN}`,
|
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type'
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Funzione principale per gestire il fetch
|
|
async function handleFetch(request) {
|
|
try {
|
|
// Verifica se è una richiesta all'API
|
|
const url = new URL(request.url);
|
|
const isApiRequest = url.hostname === API_DOMAIN;
|
|
|
|
// Se è una richiesta API, gestiscila separatamente
|
|
if (isApiRequest) {
|
|
return handleApiRequest(request);
|
|
}
|
|
|
|
// Per le altre richieste, prova prima la cache
|
|
const cachedResponse = await caches.match(request);
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
|
|
// Se non è in cache, fai la richiesta di rete
|
|
const response = await fetch(request);
|
|
|
|
// Verifica la validità della risposta
|
|
if (!response || (!response.ok && response.type !== 'opaque')) {
|
|
console.warn('[Service Worker] Invalid response for:', request.url);
|
|
return response;
|
|
}
|
|
|
|
// Cache solo le risorse dello stesso origine
|
|
if (ENABLE_DYNAMIC_CACHING && !isCrossOrigin(request.url)) {
|
|
try {
|
|
const cache = await caches.open(DYNAMIC_CACHE);
|
|
cache.put(request, response.clone());
|
|
} catch (cacheError) {
|
|
console.error('[Service Worker] Cache error:', cacheError);
|
|
}
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
console.error('[Service Worker] Fetch error:', error);
|
|
|
|
// Personalizza la risposta di errore in base al tipo di richiesta
|
|
if (request.headers.get('Accept')?.includes('application/json')) {
|
|
return new Response(JSON.stringify({
|
|
error: 'Network error',
|
|
message: 'Service unavailable'
|
|
}), {
|
|
status: 503,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': `https://${APP_DOMAIN}`
|
|
}
|
|
});
|
|
}
|
|
|
|
return new Response('Network error', {
|
|
status: 503,
|
|
statusText: 'Service Unavailable'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Event listener per il fetch
|
|
self.addEventListener('fetch', event => {
|
|
// Gestione delle richieste OPTIONS per CORS
|
|
if (event.request.method === 'OPTIONS') {
|
|
event.respondWith(
|
|
new Response(null, {
|
|
status: 204,
|
|
headers: {
|
|
'Access-Control-Allow-Origin': `https://${APP_DOMAIN}`,
|
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type',
|
|
'Access-Control-Max-Age': '86400'
|
|
}
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Ignora le richieste non-GET che non sono OPTIONS
|
|
if (event.request.method !== 'GET') return;
|
|
|
|
// Gestisci il caso 'only-if-cached'
|
|
if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') {
|
|
return;
|
|
}
|
|
|
|
event.respondWith(handleFetch(event.request));
|
|
});
|
|
|
|
// 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) {
|
|
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(
|
|
clients.matchAll().then((clis) => {
|
|
const client = clis.find((c) => c.visibilityState === 'visible');
|
|
if (client) {
|
|
client.navigate(notification.data.url);
|
|
client.focus();
|
|
} else {
|
|
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.');
|
|
} |