1// fallow-ignore-file unused-file 2const CACHE_NAME = 'yomu-docs-shell-v1'; 3const SHELL = [ 4 '/', 5 '/index.html', 6 '/manifest.webmanifest', 7 '/yomu-icon.svg', 8 '/favicon-16x16.png', 9 '/favicon-32x32.png', 10 '/apple-touch-icon.png', 11 '/yomu.css', 12 '/yomu.user.js', 13]; 14const CACHEABLE_PATH_SUFFIXES = [ 15 '/manifest.webmanifest', 16 '/yomu.css', 17 '/yomu.user.js', 18 '/yomu-icon.svg', 19 '/favicon-16x16.png', 20 '/favicon-32x32.png', 21 '/apple-touch-icon.png', 22 '/og-image.png', 23]; 24const DOC_PATHS = [ 25 '/', 26 '/index.html', 27 '/getting-started', 28 '/features', 29 '/tools/', 30 '/guides/', 31 '/support', 32 '/changelog', 33]; 34 35self.addEventListener('install', event => { 36 event.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(SHELL)).catch(() => undefined)); 37 self.skipWaiting(); 38}); 39 40self.addEventListener('activate', event => { 41 event.waitUntil( 42 caches.keys().then(keys => Promise.all(keys 43 .filter(key => key.startsWith('yomu-docs-shell-') && key !== CACHE_NAME) 44 .map(key => caches.delete(key)))), 45 ); 46 self.clients.claim(); 47}); 48 49self.addEventListener('fetch', event => { 50 if (event.request.method !== 'GET' || !isSameOrigin(event.request)) return; 51 if (event.request.mode === 'navigate') { 52 event.respondWith(networkFirstNavigation(event.request)); 53 return; 54 } 55 if (!shouldCacheRequest(event.request)) return; 56 event.respondWith(networkFirst(event.request)); 57}); 58 59async function networkFirstNavigation(request) { 60 try { 61 const response = await fetch(request, { cache: 'no-store' }); 62 if (!response.ok) return await cachedNavigationFallback(request); 63 cacheNavigationResponse(request, response); 64 return response; 65 } catch { 66 return await cachedNavigationFallback(request); 67 } 68} 69 70async function cachedNavigationFallback(request) { 71 const cached = await caches.match(request, { ignoreSearch: true }); 72 if (cached) return cached; 73 const url = new URL(request.url); 74 if (url.pathname !== '/' && url.pathname !== '/index.html') return Response.redirect('/', 302); 75 return await caches.match('/') || await caches.match('/index.html') || Response.error(); 76} 77 78async function networkFirst(request) { 79 try { 80 const response = await fetch(request); 81 cacheNetworkResponse(request, response); 82 return response; 83 } catch { 84 return await caches.match(request, { ignoreSearch: true }) || Response.error(); 85 } 86} 87 88function cacheNavigationResponse(request, response) { 89 if (!response.ok) return; 90 const url = new URL(request.url); 91 if (!isDocsNavigationPath(url.pathname)) return; 92 cacheNetworkResponse(request, response); 93 if (url.pathname === '/' || url.pathname === '/index.html') cacheNetworkResponse('/', response); 94} 95 96function cacheNetworkResponse(request, response) { 97 if (!response.ok) return; 98 const copy = response.clone(); 99 caches.open(CACHE_NAME).then(cache => cache.put(request, copy)).catch(() => undefined); 100} 101 102function shouldCacheRequest(request) { 103 const pathname = new URL(request.url).pathname; 104 return CACHEABLE_PATH_SUFFIXES.some(suffix => pathname.endsWith(suffix)); 105} 106 107function isDocsNavigationPath(pathname) { 108 return DOC_PATHS.some(path => { 109 if (path === '/') return pathname === '/'; 110 const prefix = path.endsWith('/') ? path : `${path}/`; 111 return pathname === path || pathname.startsWith(prefix); 112 }); 113} 114 115function isSameOrigin(request) { 116 return new URL(request.url).origin === self.location.origin; 117}
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.