diff --git a/public/sw.js b/public/sw.js index f521dda..9b4351b 100755 --- a/public/sw.js +++ b/public/sw.js @@ -1,8 +1,8 @@ // Service Worker for Claude Code UI PWA -const CACHE_NAME = 'claude-ui-v1'; +// Cache only manifest (needed for PWA install). HTML and JS are never pre-cached +// so a rebuild + refresh always picks up the latest assets. +const CACHE_NAME = 'claude-ui-v2'; const urlsToCache = [ - '/', - '/index.html', '/manifest.json' ]; @@ -10,44 +10,63 @@ const urlsToCache = [ self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) - .then(cache => { - return cache.addAll(urlsToCache); - }) + .then(cache => cache.addAll(urlsToCache)) ); self.skipWaiting(); }); -// Fetch event +// Fetch event — network-first for everything except hashed assets self.addEventListener('fetch', event => { - // Never cache API requests or WebSocket upgrades - if (event.request.url.includes('/api/') || event.request.url.includes('/ws')) { + const url = event.request.url; + + // Never intercept API requests or WebSocket upgrades + if (url.includes('/api/') || url.includes('/ws')) { return; } - event.respondWith( - caches.match(event.request) - .then(response => { - if (response) { + // Navigation requests (HTML) — always go to network, no caching + if (event.request.mode === 'navigate') { + event.respondWith( + fetch(event.request).catch(() => caches.match('/manifest.json').then(() => + new Response('

Offline

Please check your connection.

', { + headers: { 'Content-Type': 'text/html' } + }) + )) + ); + return; + } + + // Hashed assets (JS/CSS in /assets/) — cache-first since filenames change per build + if (url.includes('/assets/')) { + event.respondWith( + caches.match(event.request).then(cached => { + if (cached) return cached; + return fetch(event.request).then(response => { + const clone = response.clone(); + caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone)); return response; - } - return fetch(event.request); - } - ) + }); + }) + ); + return; + } + + // Everything else — network-first + event.respondWith( + fetch(event.request).catch(() => caches.match(event.request)) ); }); -// Activate event +// Activate event — purge old caches self.addEventListener('activate', event => { event.waitUntil( - caches.keys().then(cacheNames => { - return Promise.all( - cacheNames.map(cacheName => { - if (cacheName !== CACHE_NAME) { - return caches.delete(cacheName); - } - }) - ); - }) + caches.keys().then(cacheNames => + Promise.all( + cacheNames + .filter(name => name !== CACHE_NAME) + .map(name => caches.delete(name)) + ) + ) ); self.clients.claim(); });