/** * diora service worker — caches the app shell for offline use. */ const CACHE = 'diora-v163'; const SHELL = [ '/static/css/app.css', '/static/js/app/01-core.js', '/static/js/app/10-radio.js', '/static/js/app/14-focus.js', '/static/js/app/16-stations.js', '/static/js/app/20-tabs.js', '/static/js/app/24-ambient.js', '/static/js/app/26-sidebar.js', '/static/js/app/28-serviceworker.js', '/static/js/app/29-m3u.js', '/static/js/app/30-contrast.js', '/static/js/app/32-crypto.js', '/static/js/app/33-unlock.js', '/static/js/app/34-wallpaper.js', '/static/js/app/40-epub.js', '/static/js/app/44-books.js', '/static/js/app/46-readpos.js', '/static/js/app/48-book-cache.js', '/static/js/app/50-reader-state.js', '/static/js/app/52-book-list.js', '/static/js/app/53-library.js', '/static/js/app/54-issues.js', '/static/js/app/55-library-tools.js', '/static/js/app/56-cloud-import.js', '/static/js/app/57-cloud-inbox.js', '/static/js/app/58-pdf.js', '/static/js/app/60-tts.js', '/static/js/app/62-reader.js', '/static/js/app/63-pages.js', '/static/js/app/65-article.js', '/static/js/app/64-reader-settings.js', '/static/js/app/66-pdf-paginated.js', '/static/js/app/68-bookmarks.js', '/static/js/app/70-search.js', '/static/js/app/72-highlights.js', '/static/js/app/74-margin.js', '/static/js/app/76-export.js', '/static/js/app/78-footnotes.js', '/static/js/app/80-radio-sidebar.js', '/static/js/app/99-init.js', '/static/js/podcasts-web.js', '/static/js/home.js', '/static/js/feeds.js', '/static/js/audiobooks.js', '/static/js/nav.js', '/static/js/folders.js', '/static/js/covers.js', '/static/js/gencover.js', '/static/js/sources.js', '/static/js/search.js', '/static/js/projects.js', '/static/podcasts/native-podcast.js', '/static/podcasts/podcast-ui.css', '/static/podcasts/podcasts-web.css', '/static/manifest.json', '/static/ambient/index.html', '/static/fonts/SourceSerif4-normal-latin.woff2', '/static/fonts/SourceSerif4-normal-latin-ext.woff2', '/static/fonts/SourceSerif4-italic-latin.woff2', '/static/fonts/SourceSerif4-italic-latin-ext.woff2', ]; // Install: pre-cache the app shell self.addEventListener('install', function (event) { event.waitUntil( caches.open(CACHE).then(function (cache) { return cache.addAll(SHELL); }) ); // Activate immediately without waiting for old tabs to close self.skipWaiting(); }); // Activate: remove stale caches from previous versions (also the old // 'diora-podcast-v1' of episodes the web app used to download) self.addEventListener('activate', function (event) { event.waitUntil( caches.keys().then(function (keys) { return Promise.all( keys.filter(function (key) { return key !== CACHE; }) .map(function (key) { return caches.delete(key); }) ); }).then(function () { return self.clients.claim(); }).then(function () { // Tell all open tabs to reload so they get the latest cached assets return self.clients.matchAll({type: 'window'}).then(function (clients) { clients.forEach(function (client) { client.postMessage({type: 'SW_ACTIVATED'}); }); }); }) ); }); // Fetch: serve from cache, fall back to network self.addEventListener('fetch', function (event) { // Only handle GET requests; let POST/SSE etc. pass through if (event.request.method !== 'GET') return; const url = new URL(event.request.url); // Bypass for API/mutation endpoints if (url.pathname.startsWith('/radio/sse/') || url.pathname.startsWith('/radio/record/') || url.pathname.startsWith('/radio/affiliate/') || url.pathname.startsWith('/tts/') || url.pathname.startsWith('/admin/') || url.pathname.startsWith('/podcasts/progress/') || url.pathname.startsWith('/podcasts/queue/') || url.pathname.startsWith('/podcasts/player/') || url.pathname === '/podcasts/feeds/add' || url.pathname.startsWith('/podcasts/feeds/add') || url.pathname.includes('/remove') || url.pathname.startsWith('/podcasts/feeds/refresh')) { return; } // Cache-first for pre-defined shell assets const isShell = SHELL.some(function (s) { return url.pathname === s; }); if (isShell) { event.respondWith( // ignoreSearch: the Ambient frame loads with ?embed=1&bg=… caches.match(event.request, {ignoreSearch: true}).then(function (cached) { return cached || fetch(event.request); }) ); return; } // Navigation requests (the HTML page itself): network-first, fall back to // last cached version so the app opens offline after being visited once. if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request).then(function (response) { if (response.ok) { var clone = response.clone(); caches.open(CACHE).then(function (cache) { cache.put(event.request, clone); }); } return response; }).catch(function () { return caches.match(event.request); }) ); } });