1// Only import the core Workbox library. 2// It will manage loading necessary modules like strategies. 3importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js'); 4 5// Set Workbox's global cache name details. 6// This is important for Workbox to manage its caches properly, 7// especially during activation and cleanup. 8// CHANGE THIS SUFFIX (e.g., 'v1.10') FOR EACH SIGNIFICANT DEPLOYMENT 9// TO BUST ALL WORKBOX CACHES. 10workbox.core.setCacheNameDetails({ 11 prefix: 'csusb', 12 suffix: 'v1.15', // <--- IMPORTANT: Change this value for new deployments! 13 precache: 'precache', 14 runtime: 'runtime', 15}); 16 17// Use Workbox's internally managed cache names for consistency 18const imageCacheName = workbox.core.cacheNames.runtime + '-images'; 19const cssJsFontsCacheName = workbox.core.cacheNames.runtime + '-css-js-fonts'; 20const htmlCacheName = workbox.core.cacheNames.runtime + '-html'; 21 22const prodUrl = 'https://www.csusb.edu'; 23 24// Critical pages to cache on install (e.g., homepage, offline page) 25const pagesToPrecache = [ 26 { url: '/offline.html', revision: null }, 27 //{ url: '/', revision: null }, 28 // Add other truly static, critical HTML pages here if needed 29 // { url: '/about', revision: null }, 30]; 31 32if (workbox) { 33 console.log('Workbox is loaded'); 34 // DEBUG FOR DEV ONLY! Uncomment for debugging in development. 35 // workbox.setConfig({ 36 // debug: true, 37 // }); 38} else { 39 console.warn('Workbox failed to load'); 40} 41 42// Precache essential files (like offline fallback page and homepage) 43workbox.precaching.precacheAndRoute(pagesToPrecache); 44workbox.precaching.cleanupOutdatedCaches(); 45 46// Navigation preload cuts TTFB even more 47workbox.navigationPreload.enable(); 48 49const htmlHandler = new workbox.strategies.NetworkFirst({ 50 cacheName: htmlCacheName, 51 networkTimeoutSeconds: 7, // 6â8s is a good sweet spot for your site 52 plugins: [ 53 // Never cache logged-in/private HTML 54 { 55 cacheWillUpdate: async ({response}) => { 56 if (!response || response.status !== 200) return null; 57 const cc = response.headers.get('Cache-Control') || ''; 58 if (/private|no-store|no-cache/i.test(cc)) return null; 59 return response; 60 }, 61 }, 62 new workbox.expiration.ExpirationPlugin({ 63 maxEntries: 10, 64 maxAgeSeconds: 60, // keep HTML very fresh (60â300s) 65 }), 66 ], 67}); 68 69// Use NavigationRoute so preload + respondWith are handled correctly. 70/*const navigationRoute = new workbox.routing.NavigationRoute(htmlHandler, { 71 // block admin pages from SW HTML caching 72 denylist: [/^\/admin(\/|$)/], 73});*/ 74// Instead of htmlHandler/NetworkFirst, use NetworkOnly for navigations 75const navigationRoute = new workbox.routing.NavigationRoute( 76 new workbox.strategies.NetworkOnly(), 77 { denylist: [/^\/admin(\/|$)/] } 78); 79workbox.routing.registerRoute(navigationRoute); 80 81// Route for images (CacheFirst) 82workbox.routing.registerRoute( 83 /\.(?:png|jpg|jpeg|webp|svg|ico)$/, 84 new workbox.strategies.CacheFirst({ 85 cacheName: imageCacheName, 86 plugins: [ 87 new workbox.expiration.ExpirationPlugin({ 88 maxEntries: 30, 89 maxAgeSeconds: 60 * 60, // 1 hour 90 }), 91 new workbox.cacheableResponse.CacheableResponsePlugin({ 92 statuses: [0, 200], 93 }), 94 ] 95 }) 96); 97 98// Route for CSS, JS, and Fonts (StaleWhileRevalidate) 99workbox.routing.registerRoute( 100 ({url, request}) => { 101 // Matches external font/CDN origins 102 const isExternalAsset = ( 103 url.origin === 'https://fonts.googleapis.com' || 104 url.origin === 'https://fonts.gstatic.com' || 105 url.origin === 'https://use.fontawesome.com' || 106 url.origin === 'https://pro.fontawesome.com' || 107 url.origin === 'https://kit.fontawesome.com' || 108 url.origin === 'https://use.typekit.net' 109 ); 110 // Matches internal CSS/JS (including Drupal aggregated files like /sites/default/files/css/...) 111 const isInternalCode = ( 112 url.hostname.includes('csusb') && ( 113 request.destination === 'script' || 114 request.destination === 'style' || 115 request.destination === 'font' 116 ) 117 ); 118 return isExternalAsset || isInternalCode; 119 }, 120 new workbox.strategies.StaleWhileRevalidate({ 121 cacheName: cssJsFontsCacheName, 122 plugins: [ 123 new workbox.expiration.ExpirationPlugin({ 124 maxEntries: 100,
125 //maxAgeSeconds: 60 * 60, // 1 hour 126 maxAgeSeconds: 60 * 60 * 24 * 5, // 5 days 127 }), 128 new workbox.cacheableResponse.CacheableResponsePlugin({ 129 statuses: [0, 200], 130 }), 131 ] 132 }) 133); 134 135// Fallback to offline.html for HTML requests when network fails 136workbox.routing.setCatchHandler(async ({ request }) => { 137 if (request.mode === 'navigate') { 138 return showErrorPage(); 139 } 140 // For other types of requests (e.g., images, scripts) that fail, 141 // Workbox will default to a network error. You can add more specific 142 // fallbacks here if needed (e.g., a default image for failed image loads). 143 return Response.error(); 144}); 145 146 147// Manual addFilesToCacheOnInstall and fetchFilesAndAddToCache functions are no longer needed 148// if you are using Workbox's precaching and routing for HTML. 149 150self.addEventListener('install', function(e) { 151 // Workbox's precaching handles most of what was here. 152 // Add any custom installation steps if needed, but for now, rely on Workbox. 153 print('[Service Worker] Install'); 154 // If you need to ensure the precached items are added during install, 155 // Workbox's precacheAndRoute handles this. 156 // The 'e.waitUntil(addFilesToCacheOnInstall())' might become redundant if all 157 // critical files are in `pagesToPrecache`. 158 // If `pagesToPrecache` isn't comprehensive enough, you might adapt `addFilesToCacheOnInstall` 159 // to work with Workbox's `cache.addAll`. 160 // For simplicity and relying on Workbox's precaching, this install event might be empty 161 // or only contain simple logging. 162 // Removing it might be an option if precaching handles all needs. 163 // For now, let's keep it minimal if Workbox handles the rest: 164 // e.waitUntil(self.skipWaiting()); // This forces the new SW to activate immediately after install. 165}); 166 167 168self.addEventListener('activate', (event) => { 169 event.waitUntil( 170 caches.keys() 171 .then((keys) => { 172 const currentSuffix = workbox.core.cacheNames.suffix; // e.g., v1.15 173 return Promise.all(keys.map((k) => { 174 const isCurrentVersion = k.includes(currentSuffix); 175 const isHtmlBucket = k.endsWith('-html'); 176 if (!isCurrentVersion || isHtmlBucket) { 177 print(`[Service Worker] Deleting cache: ${k}`); 178 return caches.delete(k); 179 } 180 return Promise.resolve(false); 181 })); 182 }) 183 .then(() => clients.claim()) 184 ); 185}); 186 187// This event listener will receive messages from the main thread (your webpage). 188self.addEventListener('message', (event) => { 189 // Check if the message is specifically asking the service worker to skip waiting. 190 if (event.data && event.data.type === 'SKIP_WAITING') { 191 // Calling self.skipWaiting() forces the waiting service worker to become active. 192 self.skipWaiting(); 193 } 194}); 195 196// Your helper functions (showErrorPage, print, logErrors) remain the same. 197async function showErrorPage() { 198 // Guaranteed to work for precached assets: 199 return workbox.precaching.matchPrecache('/offline.html'); 200} 201 202function print(arg) { 203 if (location.origin !== prodUrl) { 204 console.log(arg); 205 } 206} 207 208function logErrors(msg, url, line) { 209 try { 210 fetch(location.origin + '/api/create/log', { 211 method: 'POST', 212 headers: { 213 'Content-Type': 'application/json', 214 Accept: 'application/json', 215 }, 216 body: JSON.stringify({ 217 type: 'service workers', 218 data: { message: msg, url: url, debug: line }, 219 }), 220 }).catch((err) => { 221 console.warn('Logging error failed', err); 222 }); 223 } catch (e) { 224 console.warn('Log error threw exception', e); 225 } 226}
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.