/* ── IPFS Portal — Service Worker v3 ── * * Cache strategieën per route type: * - Precache: alleen publieke pages (auth-protected pages worden * gecachet via stale-while-revalidate tijdens normaal gebruik) * - Cache-first: static assets (fonts, images, CSS, JS) * - Network-first: API calls (/_next/data, /api/*) * - Stale-while-revalidate: navigatie requests * * Let op: auth-protected pages (dashboard, upload, settings, etc.) * worden NIET geprecached — tijdens installatie is er geen sessie- * cookie, dus de middleware redirect naar /login. Alleen publieke * pagina's in de precache. */ const CACHE = 'ipfs-portal-v4'; const STATIC_CACHE = 'ipfs-portal-static-v4'; const DATA_CACHE = 'ipfs-portal-data-v4'; const PRECACHE_URLS = [ '/', '/login', ]; /* ── Install: precache app shell ── */ self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE).then((cache) => cache.addAll(PRECACHE_URLS)) ); self.skipWaiting(); }); /* ── Activate: clean old caches ── */ self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys .filter((k) => k !== CACHE && k !== STATIC_CACHE && k !== DATA_CACHE) .map((k) => caches.delete(k)) ) ) ); self.clients.claim(); }); /* ── Helper: is this a navigation request? ── */ function isNavigation(req) { return req.mode === 'navigate'; } function isStaticAsset(url) { return /\.(png|jpg|jpeg|gif|svg|webp|ico|woff2?|ttf|eot|css|js|json)$/i.test(url.pathname); } function isAPIRequest(url) { return url.pathname.startsWith('/api/') || url.pathname.startsWith('/_next/data/'); } /* ── Fetch handler ── */ self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Same-origin only if (url.origin !== self.location.origin) return; // Only cache GET/HEAD — POST/PUT/DELETE/OPTIONS can't use Cache API if (request.method !== 'GET' && request.method !== 'HEAD') { return; // pass through, no caching } // 1. Static assets → cache-first if (isStaticAsset(url)) { event.respondWith(cacheFirst(request, STATIC_CACHE)); return; } // 2. API / Next.js data → network-first if (isAPIRequest(url)) { event.respondWith(networkFirst(request, DATA_CACHE)); return; } // 3. Navigation → network-first (nooit stale HTML serveren, HMR breekt) if (isNavigation(request)) { event.respondWith(networkFirst(request, CACHE)); return; } // 4. All other requests → network-first with cache fallback event.respondWith(networkFirst(request, CACHE)); }); /* ── Cache strategies ── */ async function cacheFirst(request, cacheName) { const cached = await caches.match(request); if (cached) return cached; try { const response = await fetch(request); // Cache API supports GET/HEAD only if (response.ok && request.method === 'GET') { const cache = await caches.open(cacheName); cache.put(request, response.clone()); } return response; } catch { return new Response('Offline', { status: 503 }); } } async function networkFirst(request, cacheName) { try { const response = await fetch(request); // Cache API supports GET/HEAD only — POST/PUT/DELETE must be skipped if (response.ok && request.method === 'GET') { const cache = await caches.open(cacheName); cache.put(request, response.clone()); } return response; } catch { const cached = await caches.match(request); if (cached) return cached; return new Response('Offline', { status: 503 }); } } async function staleWhileRevalidate(request, cacheName) { const cache = await caches.open(cacheName); const cached = await cache.match(request); const fetchPromise = fetch(request) .then((response) => { if (response.ok && request.method === 'GET') cache.put(request, response.clone()); return response; }) .catch(() => cached); return cached || fetchPromise; }