// Service Worker for Notes English PWA (Stage 2) // Strategy: Cache-first for static assets, Network-first for API calls. const CACHE_NAME = 'notes-v5.5'; const OFFLINE_URL = '/'; const PRECACHE_URLS = [ '/', '/static/css/style.css', '/static/js/app.js', '/static/js/modules/api.js', '/static/js/modules/ui.js', '/static/js/modules/dictionary.js', '/static/js/modules/srs.js', '/static/js/modules/reader.js', '/static/js/modules/cefr.js', '/static/js/modules/grammar.js', '/manifest.json', ]; // ----------------------------------------------------------------------- // Install: pre-cache core assets // ----------------------------------------------------------------------- self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(PRECACHE_URLS); }).then(() => self.skipWaiting()) ); }); // ----------------------------------------------------------------------- // Activate: delete old caches // ----------------------------------------------------------------------- self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => { return Promise.all( keys .filter((key) => key !== CACHE_NAME) .map((key) => caches.delete(key)) ); }).then(() => self.clients.claim()) ); }); // ----------------------------------------------------------------------- // Fetch: strategy based on request type // ----------------------------------------------------------------------- self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Skip cross-origin requests if (url.origin !== location.origin) return; // API calls → Network-first (never serve API from cache) if (url.pathname.startsWith('/api/')) { event.respondWith( fetch(request).catch(() => { return new Response( JSON.stringify({ error: 'Offline. Please check your connection.' }), { status: 503, headers: { 'Content-Type': 'application/json' } } ); }) ); return; } // Static assets (CSS, JS, icons) → Cache-first (with ignoreSearch) if ( url.pathname.startsWith('/static/') || url.pathname === '/manifest.json' || url.pathname === '/sw.js' ) { event.respondWith( caches.match(request, { ignoreSearch: true }).then((cached) => { return cached || fetch(request).then((response) => { if (response.ok) { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); } return response; }); }) ); return; } // HTML navigation (SPA root) → Network-first with offline fallback event.respondWith( fetch(request) .then((response) => { if (response.ok) { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); } return response; }) .catch(() => { return caches.match(OFFLINE_URL).then((cached) => { return cached || new Response('
Please check your connection.
', { headers: { 'Content-Type': 'text/html' }, }); }); }) ); });