manage-your-printer/static/js/performance-service-worker.min.js
2025-06-04 10:03:22 +02:00

25 lines
7.5 KiB
JavaScript

const CACHE_VERSION='v2.0';const STATIC_CACHE=`myp-static-${CACHE_VERSION}`;const DYNAMIC_CACHE=`myp-dynamic-${CACHE_VERSION}`;const FONTS_CACHE=`myp-fonts-${CACHE_VERSION}`;const IMAGES_CACHE=`myp-images-${CACHE_VERSION}`;const CRITICAL_ASSETS=['/','/static/css/critical-inline.css','/static/css/app-bundle.min.css','/static/js/performance-service-worker.js','/static/js/app-bundle.min.js'];const CACHE_STRATEGIES={css:{cache:STATIC_CACHE,strategy:'cache-first',maxAge:86400000},js:{cache:STATIC_CACHE,strategy:'cache-first',maxAge:86400000},fonts:{cache:FONTS_CACHE,strategy:'cache-first',maxAge:2592000000},images:{cache:IMAGES_CACHE,strategy:'cache-first',maxAge:604800000},html:{cache:DYNAMIC_CACHE,strategy:'network-first',maxAge:3600000},api:{cache:DYNAMIC_CACHE,strategy:'network-first',maxAge:300000}};self.addEventListener('install',event=>{console.log('[SW] Installing Performance Service Worker...');event.waitUntil(Promise.all([caches.open(STATIC_CACHE).then(cache=>{console.log('[SW] Pre-caching critical assets...');return cache.addAll(CRITICAL_ASSETS);}),self.skipWaiting()]));});self.addEventListener('activate',event=>{console.log('[SW] Activating Performance Service Worker...');event.waitUntil(Promise.all([caches.keys().then(cacheNames=>{return Promise.all(cacheNames.map(cacheName=>{if(!cacheName.includes(CACHE_VERSION)){console.log('[SW] Deleting old cache:',cacheName);return caches.delete(cacheName);}}));}),self.clients.claim()]));});self.addEventListener('fetch',event=>{if(event.request.method!=='GET')return;const url=new URL(event.request.url);const strategy=getResourceStrategy(url);event.respondWith(handleRequest(event.request,strategy));});function getResourceStrategy(url){const pathname=url.pathname;if(pathname.endsWith('.css'))return CACHE_STRATEGIES.css;if(pathname.endsWith('.js'))return CACHE_STRATEGIES.js;if(pathname.match(/\.(woff2?|ttf|eot)$/))return CACHE_STRATEGIES.fonts;if(pathname.match(/\.(jpg|jpeg|png|gif|webp|svg|ico)$/))return CACHE_STRATEGIES.images;if(pathname.startsWith('/api/'))return CACHE_STRATEGIES.api;return CACHE_STRATEGIES.html;}
async function handleRequest(request,strategy){const cacheName=strategy.cache;switch(strategy.strategy){case'cache-first':return cacheFirst(request,cacheName,strategy.maxAge);case'network-first':return networkFirst(request,cacheName,strategy.maxAge);default:return fetch(request);}}
async function cacheFirst(request,cacheName,maxAge){try{const cache=await caches.open(cacheName);const cachedResponse=await cache.match(request);if(cachedResponse&&!isExpired(cachedResponse,maxAge)){if(isCriticalResource(request.url)){updateInBackground(request,cache);}
return cachedResponse;}
const networkResponse=await fetchWithTimeout(request,5000);if(networkResponse&&networkResponse.ok){await cache.put(request,networkResponse.clone());return networkResponse;}
return cachedResponse||createFallbackResponse(request);}catch(error){console.warn('[SW] Cache-first failed:',error);const cache=await caches.open(cacheName);const cachedResponse=await cache.match(request);return cachedResponse||createFallbackResponse(request);}}
async function networkFirst(request,cacheName,maxAge){try{const networkResponse=await fetchWithTimeout(request,3000);if(networkResponse&&networkResponse.ok){const cache=await caches.open(cacheName);await cache.put(request,networkResponse.clone());return networkResponse;}
throw new Error('Network response not ok');}catch(error){console.warn('[SW] Network-first fallback to cache:',error);const cache=await caches.open(cacheName);const cachedResponse=await cache.match(request);if(cachedResponse&&!isExpired(cachedResponse,maxAge)){return cachedResponse;}
return createFallbackResponse(request);}}
function fetchWithTimeout(request,timeout){return new Promise((resolve,reject)=>{const timer=setTimeout(()=>reject(new Error('Timeout')),timeout);fetch(request).then(response=>{clearTimeout(timer);resolve(response);}).catch(error=>{clearTimeout(timer);reject(error);});});}
function isExpired(response,maxAge){const date=response.headers.get('date');if(!date)return false;const responseTime=new Date(date).getTime();const now=Date.now();return(now-responseTime)>maxAge;}
function isCriticalResource(url){return CRITICAL_ASSETS.some(asset=>url.includes(asset));}
async function updateInBackground(request,cache){try{const response=await fetch(request);if(response&&response.ok){await cache.put(request,response);console.log('[SW] Background update completed for:',request.url);}}catch(error){console.warn('[SW] Background update failed:',error);}}
function createFallbackResponse(request){const url=new URL(request.url);if(url.pathname.endsWith('.css')){return new Response(getFallbackCSS(),{headers:{'Content-Type':'text/css'}});}
if(url.pathname.endsWith('.js')){return new Response('console.log("Fallback JS loaded");',{headers:{'Content-Type':'application/javascript'}});}
if(url.pathname.match(/\.(jpg|jpeg|png|gif|webp)$/)){return new Response(getFallbackImage(),{headers:{'Content-Type':'image/svg+xml'}});}
return new Response('Offline',{status:503});}
function getFallbackCSS(){return`body{font-family:system-ui,sans-serif;margin:0;padding:20px;background:#f8fafc;color:#1f2937}.offline{background:#fef3c7;border:1px solid#f59e0b;border-radius:8px;padding:16px;text-align:center;margin:20px 0}.btn{background:#0073ce;color:white;border:none;padding:8px 16px;border-radius:4px;cursor:pointer}.card{background:white;border:1px solid#e5e7eb;border-radius:8px;padding:16px;margin:16px 0}`;}
function getFallbackImage(){return`<svg width="300"height="200"xmlns="http://www.w3.org/2000/svg"><rect width="100%"height="100%"fill="#f3f4f6"/><text x="50%"y="50%"text-anchor="middle"dy=".3em"fill="#6b7280">Bild offline</text></svg>`;}
self.addEventListener('message',event=>{const{type,data}=event.data;switch(type){case'SKIP_WAITING':self.skipWaiting();break;case'CLEAR_ALL_CACHES':clearAllCaches().then(()=>{event.ports[0].postMessage({success:true});});break;case'PREFETCH_URLS':prefetchUrls(data.urls).then(()=>{event.ports[0].postMessage({success:true});});break;case'GET_CACHE_INFO':getCacheInfo().then(info=>{event.ports[0].postMessage(info);});break;}});async function clearAllCaches(){const cacheNames=await caches.keys();await Promise.all(cacheNames.map(name=>caches.delete(name)));console.log('[SW] All caches cleared');}
async function prefetchUrls(urls){for(const url of urls){try{const response=await fetch(url);if(response.ok){const strategy=getResourceStrategy(new URL(url));const cache=await caches.open(strategy.cache);await cache.put(url,response);console.log('[SW] Prefetched:',url);}}catch(error){console.warn('[SW] Prefetch failed for:',url,error);}}}
async function getCacheInfo(){const cacheNames=await caches.keys();const info={};for(const name of cacheNames){const cache=await caches.open(name);const keys=await cache.keys();info[name]={entries:keys.length,urls:keys.map(req=>req.url)};}
return info;}
let performanceData={cacheHits:0,cacheMisses:0,networkRequests:0,backgroundUpdates:0};self.addEventListener('sync',event=>{if(event.tag==='background-sync'){console.log('[SW] Background sync triggered');event.waitUntil(doBackgroundSync());}});async function doBackgroundSync(){console.log('[SW] Background sync completed');}
console.log('[SW] Performance Service Worker loaded and ready');setInterval(async()=>{console.log('[SW] Starting automatic cache cleanup...');await cleanupOldCaches();},24*60*60*1000);async function cleanupOldCaches(){const cacheNames=await caches.keys();const oldCaches=cacheNames.filter(name=>!name.includes(CACHE_VERSION));await Promise.all(oldCaches.map(name=>{console.log('[SW] Cleaning up old cache:',name);return caches.delete(name);}));}