1'use strict'; 2const d = new Date(); 3const staticCacheName = 'static_v-01'; 4const pagesCacheName = 'pages_v-01'; 5const imagesCacheName = 'images_v-02'; 6const assetsCacheName = 'assets_v' + d.getMonth() + '' + d.getDate() + '-01'; 7const maxPages = 50; // Maximum number of pages to cache 8const maxImages = 100; // Maximum number of images to cache 9const timeout = 3000; // Number of milliseconds before timing out 10 11const cacheList = [ 12 staticCacheName, 13 pagesCacheName, 14 imagesCacheName, 15 assetsCacheName 16]; 17 18 19function updateStaticCache() { 20 return caches.open(staticCacheName) 21 .then(staticCache => { 22 // These items won't block the installation of the Service Worker 23 staticCache.addAll([ 24 '/mtu_resources/styles/n/print.css' 25 ]); 26 // These items must be caced for the Service Worker to complete installation 27 return staticCache.addAll([ 28 '/offline/index.php' 29 ]) 30 }) 31 .catch(error=>{ 32 console.log(error); 33 }); 34} 35 36// Cache the page(s) that initiate the service worker 37function cacheClients() { 38 const pages = []; 39 return clients.matchAll({ 40 includeUncontrolled: true 41 }) 42 .then(allClients => { 43 for (const client of allClients) { 44 pages.push(client.url); 45 } 46 }) 47 .then(() => { 48 caches.open(pagesCacheName) 49 .then(pagesCache => { 50 return pagesCache.addAll(pages); 51 }); 52 }) 53} 54 55// Remove caches whose name is no longer valid 56async function clearOldCaches() { 57 const cacheNames = await caches.keys(); 58 return await Promise.all( 59 cacheNames.map(cacheName => { 60 if (!cacheList.includes(cacheName)) { 61 return caches.delete(cacheName); 62 } 63 }) 64 ); 65} 66 67// Remove old versions of a file in cache. 68function clearStaleVersions(cacheName, url, version){ 69 caches.open(cacheName) 70 .then( cache => { 71 cache.keys() 72 .then( keys=> { 73 keys.forEach( key => { 74 if ( 0 < key.url.indexOf( url ) ) { 75 if ( 0 > key.url.indexOf( version )){ 76 cache.delete(key); 77 } 78 } 79 }) 80 }) 81 } ) 82} 83 84function trimCache(cacheName, maxItems) { 85 caches.open(cacheName) 86 .then(cache => { 87 cache.keys() 88 .then(keys => { 89 if (keys.length > maxItems) { 90 cache.delete(keys[0]) 91 .then(() => { 92 trimCache(cacheName, maxItems) 93 }); 94 } 95 }); 96 }); 97} 98 99// Install Service Worker fetchEvent Listener 100addEventListener('install', installEvent => { 101 installEvent.waitUntil( 102 // Update our static cache 103 updateStaticCache() 104 .then(() => { 105 return skipWaiting(); 106 }) 107 ) 108}) 109 110// Activate Service Worker fetchEvent Listener 111addEventListener('activate', fetchEvent => { 112 fetchEvent.waitUntil( 113 // Clear our old caches 114 clearOldCaches() 115 .then( () => { 116 // claim all open tabs with an updated service worker. 117 return clients.claim(); 118 }) 119 ); 120}); 121 122// Message fetchEvent Listener 123self.addEventListener('message', fetchEvent => { 124 if (fetchEvent.data.command == 'trimCaches') { 125 clearOldCaches(); 126 trimCache(pagesCacheName, maxPages); 127 trimCache(imagesCacheName, maxImages); 128 trimCache(assetsCacheName, maxImages); 129 } 130}); 131 132 133// Fetch fetchEvent Listener 134addEventListener('fetch', fetchEvent => { 135 const request = fetchEvent.request; 136 let url = new URL(request.url); 137 const blackList = ['google', 'ytimg', '.pdf', 'project.lib', 'addthis', '/webcams/cache', 'facebook', 'doubleclick', 'bing', '/tips/private', 'tip-line', 'streamingwebcams']; 138 let blackListed = false; 139 140 // Ignore requests to some directories 141 blackList.forEach( term => { 142 if ( 0 < request.url.indexOf( term ) ) { 143 blackListed = true; 144 } 145 } ); 146 147 if ( blackListed === true ) { 148 return; 149 } 150 151 // Check to make sure the request is good with a status 200. If not, return, it could be a redirect. 152 // If it isn't over https return. 153 if ( 0 > request.url.indexOf('https') ) { 154 return; 155 } 156 157 // Check to Cache css and js files 158 if ( url.href.match( /(\.css|\.js)/ ) ) { 159 // If it isn't from our website, a cdn, or jquery return. 160 if ( !url.href.match( /.+(cdn|mtu.edu|jquery)/ ) ) { 161 return; 162 } 163 // If it is from a cdn but it dosen't have an access-control policy return. 164 if ( url.href.match( /.+(cdn)/ ) 165 && !request.headers.get( 'access-control-allow-origin' ) ) { 166 return; 167 } 168 } 169 170 171 172 173 // Ignore non-GET requests 174 if ( 'GET' !== request.method ) { 175 return; 176 } 177 178 if (request.headers.get('Accept').includes('text/html')) { 179 180 fetchEvent.respondWith( 181 new Promise(resolveWithResponse => { 182 183 const timer = setTimeout(() => { 184 // Time out: CACHE 185 caches.match(request) 186 .then(responseFromCache => { 187 if (responseFromCache) { 188 resolveWithResponse(responseFromCache); 189 } 190 }) 191 }, timeout); 192 193 fetch(request) 194 .then(responseFromFetch => { 195 // NETWORK 196 clearTimeout(timer); 197 const copy = responseFromFetch.clone(); 198 // Stash a copy of this page in the pages cache 199 try { 200 fetchEvent.waitUntil( 201 caches.open(pagesCacheName) 202 .then(pagesCache => { 203 return pagesCache.put(request, copy); 204 }) 205 ); 206 } catch (error) { 207 console.warn(error); 208 } 209 return resolveWithResponse(responseFromFetch); 210 }) 211 .catch(fetchError => { 212 clearTimeout(timer); 213 console.error(fetchError); 214 // CACHE or FALLBACK 215 caches.match(request) 216 .then(responseFromCache => { 217 return resolveWithResponse( 218 responseFromCache || caches.match('./offline/index.php') 219 ); 220 }); 221 }); 222 }) 223 ) 224 return; 225 } 226 227 228 if (0 < url.href.indexOf( '.css' ) || 0 < url.href.indexOf( '.js' ) ) { 229 let responseVersion; 230 fetchEvent.respondWith( 231 new Promise(resolveWithResponse => { 232 233 const timer = setTimeout(() => { 234 // Time out: CACHE 235 caches.match(request) 236 .then(responseFromCache => { 237 if (responseFromCache) { 238 resolveWithResponse(responseFromCache); 239 } 240 }) 241 }, timeout); 242 243 fetch(request) 244 .then(responseFromFetch => { 245 // NETWORK 246 clearTimeout(timer); 247 const copy = responseFromFetch.clone(); 248 // Stash a copy of this page in the pages cache 249 try { 250 fetchEvent.waitUntil( 251 caches.open(assetsCacheName) 252 .then(assetsCacheName => { 253 return assetsCacheName.put(request, copy); 254 }) 255 ); 256 } catch (error) { 257 console.error(error); 258 } 259 return resolveWithResponse(responseFromFetch); 260 }) 261 .catch(fetchError => { 262 clearTimeout(timer); 263 console.error(fetchError); 264 // CACHE or FALLBACK 265 caches.match(request) 266 .then(responseFromCache => { 267 return resolveWithResponse(responseFromCache); 268 }); 269 }); 270 }) 271 ) 272 return; 273 } 274 275 // For non-HTML requests, look in the cache first, fall back to the network 276 fetchEvent.respondWith( 277 caches.match(request) 278 .then(responseFromCache => { 279 // CACHE 280 return responseFromCache || fetch(request) 281 .then(responseFromFetch => { 282 // NETWORK 283 // If the request is for an image, stash a copy of this image in the images cache 284 if (request.url.match(/\.(jpe?g|png|gif|mapbox)/)) { 285 const copy = responseFromFetch.clone(); 286 try { 287 fetchEvent.waitUntil( 288 caches.open(imagesCacheName) 289 .then(imagesCache => { 290 return imagesCache.put(request, copy); 291 }) 292 ); 293 } catch (error) { 294 console.error(error); 295 } 296 } 297 return responseFromFetch; 298 }) 299 .catch(fetchError => { 300 console.error(fetchError); 301 // FALLBACK 302 // show an offline placeholder 303 if (request.url.match(/\.(jpe?g|png|gif|mapbox)/)) { 304 return new Response('<svg role="img" aria-labelledby="offline-title" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title id="offline-title">Offline</title><g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/><text fill="#9B9B9B" font-family="Helvetica Neue,Arial,Helvetica,sans-serif" font-size="72" font-weight="bold"><tspan x="93" y="172">offline</tspan></text></g></svg>', { 305 headers: { 306 'Content-Type': 'image/svg+xml', 307 'Cache-Control': 'no-store' 308 } 309 }); 310 } 311 }); 312 }) 313 ); 314});
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.