importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js'); workbox.setConfig({ debug: false }); const CACHE_NAME = 'app-cache-v2.1.6'; const MIN_VALID_SIZE = 500; const ASSETS_TO_CACHE = [ '/pwa2', '/icon-192x192.png', '/icon-512x512.png', '/offline.html', '/static/Inter-Bold.woff2', '/static/Inter-SemiBold.woff2', '/static/Inter-Regular.woff2', '/static/painter.svg', '/static/event1.jpg', '/static/event2.jpg', '/static/event3.jpg', '/static/event4.jpg', '/static/event5.jpg', '/static/script/share.js', '/static/script/select.js', '/static/event6.jpg', '/static/event7.webp', '/partner', ]; const NOCACHE_PATHS = [ '/api', '/form', ]; const VALIDATE_ON_MODIFY_PATHS = [ '/', '/pwa', '/paintings', '/profile', '/competitions', '/competition', '/exhibitions', '/exhibition', '/login', '/registration', '/artists', '/my-paints', '/partner', '/news', ]; // ——— Lifecycle ——— self.skipWaiting(); workbox.core.clientsClaim(); // Clean old caches + notify clients self.addEventListener('activate', (event) => { event.waitUntil( (async () => { const keys = await caches.keys(); await Promise.all( keys .filter((key) => key !== CACHE_NAME) .map((key) => { console.log(`Deleting old cache: ${key}`); return caches.delete(key); }) ); const clients = await self.clients.matchAll(); clients.forEach((client) => client.postMessage({ type: 'SW_UPDATED' })); console.log('Service Worker activated.'); })() ); }); // ——— Precaching ——— workbox.precaching.precacheAndRoute( ASSETS_TO_CACHE.map((url) => ({ url, revision: null })), { cacheName: CACHE_NAME, } ); // ——— Runtime routes ——— // 1. Never cache these paths (NetworkOnly) workbox.routing.registerRoute( ({ url }) => NOCACHE_PATHS.some( (path) => url.pathname === path || url.pathname.startsWith(path + '/') ), new workbox.strategies.NetworkOnly() ); // 2. Paths that should revalidate on Last-Modified / Content-Length workbox.routing.registerRoute( ({ url }) => VALIDATE_ON_MODIFY_PATHS.some( (path) => url.pathname === path || url.pathname.startsWith(path + '/') ), new workbox.strategies.NetworkFirst({ cacheName: CACHE_NAME, plugins: [ new workbox.cacheableResponse.CacheableResponsePlugin({ statuses: [0, 200], }), { // Custom plugin: only update cache when content actually changed // or when the cached entry is suspiciously small cacheWillUpdate: async ({ request, response, event }) => { if (!response || response.status !== 200) return null; const cache = await caches.open(CACHE_NAME); const cached = await cache.match(request); if (!cached) return response; const contentLength = parseInt( cached.headers.get('Content-Length') || '0', 10 ); const isTooSmall = contentLength > 0 && contentLength < MIN_VALID_SIZE; if (isTooSmall) { console.warn( `Cached file ${request.url} was too small (${contentLength} bytes). Forcing update.` ); return response; } const [cachedModified, netModified] = await Promise.all([ cached.headers.get('Last-Modified'), response.headers.get('Last-Modified'), ]); const [cachedLen, netLen] = await Promise.all([ cached.headers.get('Content-Length'), response.headers.get('Content-Length'), ]); if (cachedModified === netModified && cachedLen === netLen) { console.log( `Content unchanged for ${new URL(request.url).pathname}, keeping cached version` ); return null; // do not update cache } console.log(`Caching/updating: ${new URL(request.url).pathname}`); return response; }, }, ], }) ); // 3. Everything else → CacheFirst (with size safeguard) workbox.routing.registerRoute( ({ request }) => request.method === 'GET', new workbox.strategies.CacheFirst({ cacheName: CACHE_NAME, plugins: [ new workbox.cacheableResponse.CacheableResponsePlugin({ statuses: [0, 200], }), { // Force update when cached response is too small cachedResponseWillBeUsed: async ({ cacheName, request, cachedResponse, }) => { if (!cachedResponse) return null; const contentLength = parseInt( cachedResponse.headers.get('Content-Length') || '0', 10 ); if (contentLength > 0 && contentLength < MIN_VALID_SIZE) { console.warn( `Cached file ${request.url} was too small (${contentLength} bytes). Ignoring cache.` ); return null; // force network } return cachedResponse; }, }, ], }) ); // ——— Offline fallback for navigation requests ——— workbox.routing.setCatchHandler(async ({ event }) => { if (event.request.mode === 'navigate') { const offline = await caches.match('/offline.html'); if (offline) return offline; const root = await caches.match('/'); if (root) return root; } // For non-navigation requests just return whatever we have (or fail) return Response.error(); });