'use strict'; // Serve app files and individually selected route snapshots independently (v2). importScripts('/js/offline-storage.js'); self.addEventListener('install', event => event.waitUntil(self.skipWaiting())); self.addEventListener('activate', event => event.waitUntil(self.clients.claim())); /** Audio seeks need partial responses even when the entire file is stored locally. */ async function rangedResponse(response, range) { const bytes = await response.arrayBuffer(); const match = /^bytes=(\d*)-(\d*)$/.exec(range); if (!match || (!match[1] && !match[2])) return new Response(null, { status: 416, headers: { 'Content-Range': `bytes */${bytes.byteLength}` } }); const start = match[1] ? Number(match[1]) : Math.max(0, bytes.byteLength - Number(match[2])); const end = match[1] ? (match[2] ? Math.min(Number(match[2]), bytes.byteLength - 1) : bytes.byteLength - 1) : bytes.byteLength - 1; if (start > end || start >= bytes.byteLength) return new Response(null, { status: 416, headers: { 'Content-Range': `bytes */${bytes.byteLength}` } }); const headers = new Headers(response.headers); headers.set('Content-Range', `bytes ${start}-${end}/${bytes.byteLength}`); headers.set('Content-Length', String(end - start + 1)); headers.set('Accept-Ranges', 'bytes'); return new Response(bytes.slice(start, end + 1), { status: 206, headers }); } self.addEventListener('fetch', event => { const request = event.request; const url = new URL(request.url); if (request.method !== 'GET' || url.origin !== self.location.origin || request.headers.has('X-Wegwichtel-Offline-Download') || url.pathname === '/api/offline-manifest') return; event.respondWith((async () => { // A complete snapshot is cache-first, avoiding any wait for a failing mobile network. const cached = await self.Wegwichtel.OfflineStore.match(request.url).catch(() => null); if (cached) { const range = request.headers.get('Range'); return range ? rangedResponse(cached, range) : cached; } return fetch(request); })()); });