Mercurial
diff mrjunejune/src/sw.js @ 273:e02e2036ef84 default tip
add Layer 2 JRPG component system
Add reusable content and window modals, an isolated component sandbox, shared cyberpunk scroll areas, production-safe cache freshness, and server-rendered JRPG panel state.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sat, 08 Aug 2026 02:08:08 -0700 |
| parents | 056790c4fb0d |
| children |
line wrap: on
line diff
--- a/mrjunejune/src/sw.js Fri Aug 07 16:05:29 2026 -0700 +++ b/mrjunejune/src/sw.js Sat Aug 08 02:08:08 2026 -0700 @@ -1,6 +1,12 @@ // Root-scoped Service Worker for MrJuneJune PWA -const CACHE_VERSION = 'v35-role-greeting'; +const CACHE_VERSION = 'v37-network-first'; const CACHE_NAME = `mrjunejune-${CACHE_VERSION}`; +const DEVELOPMENT_SANDBOX_PATHS = [ + '/public/component-sandbox', + '/public/composer-lab', + '/public/mjj-composer.', + '/public/mjj-modal.', +]; // Files to cache immediately on install const STATIC_CACHE = [ @@ -73,7 +79,46 @@ ); }); -// Fetch event - serve from cache, fallback to network +function isCacheable(response) { + return response && + response.status === 200 && + response.type !== 'error'; +} + +async function updateCache(request, response) { + if (!isCacheable(response)) return; + const cache = await caches.open(CACHE_NAME); + await cache.put(request, response.clone()); +} + +async function networkFirst(request, offlineFallback = false) { + try { + const response = await fetch(request, { cache: 'no-cache' }); + await updateCache(request, response); + return response; + } catch (error) { + const cached = await caches.match(request); + if (cached) return cached; + if (offlineFallback) { + const offline = await caches.match('/offline.html'); + if (offline) return offline; + } + throw error; + } +} + +async function staleWhileRevalidate(event, request) { + const cached = await caches.match(request); + const update = fetch(request, { cache: 'no-cache' }).then(async response => { + await updateCache(request, response); + return response; + }); + if (!cached) return update; + event.waitUntil(update.catch(() => undefined)); + return cached; +} + +// Fetch event - current code first, cached media first, offline fallback. self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); @@ -88,6 +133,10 @@ return; } + if (url.origin !== self.location.origin) { + return; + } + // Skip API calls and media uploads (always go to network) if (url.pathname.startsWith('/api/')) { return; @@ -98,60 +147,32 @@ return; } + // Component workbenches must reflect every ibazel rebuild on any hostname. + if (DEVELOPMENT_SANDBOX_PATHS.some(path => + url.pathname.startsWith(path))) { + return; + } + // The HLS tool and media must always reflect the current player version. if (url.pathname.startsWith('/tools/hls_player') || url.pathname.startsWith('/public/hls-sample/')) { return; } - event.respondWith( - caches.match(request).then((cachedResponse) => { - if (cachedResponse) { - console.log('[SW] Serving from cache:', url.pathname); - return cachedResponse; - } - - // Not in cache, fetch from network - return fetch(request).then((networkResponse) => { - // Only cache successful responses - if (!networkResponse || networkResponse.status !== 200 || networkResponse.type === 'error') { - return networkResponse; - } + const acceptsHtml = request.mode === 'navigate' || + request.headers.get('Accept')?.includes('text/html'); + const currentCode = /\.(?:css|js|json|wasm)$/i.test(url.pathname); + const revalidatedMedia = + /\.(?:svg|jpe?g|webp|woff2?|ttf|otf|pdf)$/i.test(url.pathname); - // Cache specific file types - const shouldCache = - url.pathname.endsWith('.css') || - url.pathname.endsWith('.js') || - url.pathname.endsWith('.svg') || - url.pathname.endsWith('.jpg') || - url.pathname.endsWith('.webp') || - url.pathname.endsWith('.woff') || - url.pathname.endsWith('.woff2') || - url.pathname.endsWith('.ttf') || - url.pathname.endsWith('.otf') || - url.pathname.startsWith('/blog/') || - url.pathname.startsWith('/notes/') || - url.pathname === '/'; + if (acceptsHtml || currentCode) { + event.respondWith(networkFirst(request, acceptsHtml)); + return; + } - if (shouldCache) { - const responseToCache = networkResponse.clone(); - caches.open(CACHE_NAME).then((cache) => { - console.log('[SW] Caching new resource:', url.pathname); - cache.put(request, responseToCache); - }); - } - - return networkResponse; - }).catch((error) => { - console.log('[SW] Fetch failed:', error); - - // Return offline page for HTML requests - if (request.headers.get('Accept').includes('text/html')) { - return caches.match('/offline.html'); - } - }); - }) - ); + if (revalidatedMedia) { + event.respondWith(staleWhileRevalidate(event, request)); + } }); // Handle messages from the client