1// static/sw.js â Odysseus PWA Service Worker 2// Strategy: 3// - HTML (navigation): stale-while-revalidate. Instant open from cache, 4// background refresh so the next open has latest HTML. 5// - JS/CSS (/static/*.js|.css): network-first, cache fallback for offline. 6// (So code/style edits show up on a normal reload, no manual cache clear.) 7// - Other static assets (images/fonts/libs): cache-first with bg refresh. 8// - API / non-GET: never cached. 9// Bump CACHE_NAME whenever the precache list or SW logic changes. 10const CACHE_NAME = 'odysseus-v376-settings-title-icons'; 11 12// Core shell precached on install so repeat opens are instant without any 13// network wait. Keep this list in sync with the <script type="module"> tags 14// and <link rel="stylesheet"> in index.html. 15const PRECACHE = [ 16 '/', 17 '/static/style.css', 18 '/static/app.js', 19 '/static/js/storage.js', 20 '/static/js/ui.js', 21 '/static/js/markdown.js', 22 '/static/js/dragSort.js', 23 '/static/js/sessions.js', 24 '/static/js/memory.js', 25 '/static/js/skills.js', 26 '/static/js/tourHints.js', 27 '/static/js/fileHandler.js', 28 '/static/js/voiceRecorder.js', 29 '/static/js/models.js', 30 '/static/js/rag.js', 31 '/static/js/presets.js', 32 '/static/js/search.js', 33 '/static/js/spinner.js', 34 '/static/js/tts-ai.js', 35 '/static/js/document.js', 36 '/static/js/gallery.js', 37 '/static/js/chatRenderer.js', 38 '/static/js/codeRunner.js', 39 '/static/js/chatStream.js', 40 '/static/js/chat.js', 41 '/static/js/cookbook.js', 42 '/static/js/search-chat.js', 43 '/static/js/compare/index.js', 44 '/static/js/theme.js', 45 '/static/js/censor.js', 46 '/static/js/settings.js', 47 '/static/js/admin.js', 48 '/static/js/init.js', 49 '/static/js/slashCommands.js', 50 '/static/js/emailInbox.js', 51 '/static/js/emailLibrary/utils.js', 52 '/static/js/emailLibrary/signatureFold.js', 53 '/static/js/emailLibrary/state.js', 54 '/static/js/notes.js', 55 '/static/js/tasks.js', 56 '/static/js/calendar.js', 57 '/static/js/calendar/utils.js', 58 '/static/js/calendar/reminders.js', 59 '/static/js/group.js', 60 '/static/js/keyboard-shortcuts.js', 61 '/static/js/sidebar-layout.js', 62 '/static/js/section-management.js', 63 '/static/lib/highlight.min.js', 64]; 65 66self.addEventListener('install', (e) => { 67 e.waitUntil( 68 caches.open(CACHE_NAME).then(cache => 69 // addAll is atomic â if any item fails, none are cached. Use individual 70 // puts so a single 404 can't block the whole install. 71 Promise.all( 72 PRECACHE.map(url => 73 fetch(url, { cache: 'reload' }) 74 .then(res => res.ok ? cache.put(url, res) : null) 75 .catch(() => null) 76 ) 77 ) 78 ) 79 ); 80 self.skipWaiting(); 81}); 82 83self.addEventListener('activate', (e) => { 84 e.waitUntil( 85 caches.keys().then(keys => 86 Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))) 87 ).then(() => self.clients.claim()) 88 ); 89}); 90 91self.addEventListener('fetch', (e) => { 92 const url = new URL(e.request.url); 93 94 // Never touch API calls or non-GET. 95 if (url.pathname.startsWith('/api/') || e.request.method !== 'GET') return; 96 97 // HTML navigation: stale-while-revalidate the app shell â but ONLY for the 98 // SPA root. Other navigations (e.g. a deep-linked /static/*.html page) must 99 // go to the network/static handlers below; otherwise every navigation was 100 // served the app index, replacing the page the user actually asked for. 101 if (e.request.mode === 'navigate' && url.pathname === '/') { 102 e.respondWith( 103 caches.open(CACHE_NAME).then(async cache => { 104 const cached = await cache.match('/'); 105 const network = fetch(e.request).then(res => { 106 if (res && res.ok) cache.put('/', res.clone()); 107 return res; 108 }).catch(() => cached); 109 return cached || network; 110 }) 111 ); 112 return; 113 } 114 115 // JS/CSS: network-first â always try the network so code/style edits show up 116 // on a normal reload; fall back to cache only when offline. 117 if (url.pathname.startsWith('/static/') && /\.(js|css)(\?|$)/.test(url.pathname + url.search)) { 118 e.respondWith( 119 fetch(e.request).then(res => { 120 if (res && res.ok) { 121 const copy = res.clone(); 122 caches.open(CACHE_NAME).then(cache => cache.put(e.request, copy)); 123 } 124 return res; 125 }).catch(() => caches.match(e.request)) 126 ); 127 return; 128 } 129 130 // Other static assets (images, fonts, libs): cache-first with background refresh. 131 if (url.pathname.startsWith('/static/')) { 132 e.respondWith( 133 caches.open(CACHE_NAME).then(async cache => { 134 const cached = await cache.match(e.request); 135 const fetching = fetch(e.request).then(res => { 136 if (res && res.ok) cache.put(e.request, res.clone()); 137 return res; 138 }).catch(() => cached); 139 return cached || fetching; 140 }) 141 ); 142 return; 143 } 144});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.