1/** 2 * Copyright 2016 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17// This generated service worker JavaScript will precache your site's resources. 18// The code needs to be saved in a .js file at the top-level of your site, and registered 19// from your pages in order to be used. See 20// https://github.com/googlechrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js 21// for an example of how you can register this script and handle various service worker events. 22 23/* eslint-env worker, serviceworker */ 24/* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren */ 25'use strict'; 26 27 28 29 30importScripts("scripts/sw/sw-toolbox.js","scripts/sw/runtime-caching.js"); 31 32 33/* eslint-disable quotes, comma-spacing */ 34var PrecacheConfig = []; 35/* eslint-enable quotes, comma-spacing */ 36var CacheNamePrefix = 'sw-precache-v1-web-starter-kit-' + (self.registration ? self.registration.scope : '') + '-'; 37 38 39var IgnoreUrlParametersMatching = [/^utm_/]; 40 41 42 43var addDirectoryIndex = function(originalUrl, index) { 44 var url = new URL(originalUrl); 45 if (url.pathname.slice(-1) === '/') { 46 url.pathname += index; 47 } 48 return url.toString(); 49 }; 50 51var getCacheBustedUrl = function(url, param) { 52 param = param || Date.now(); 53 54 var urlWithCacheBusting = new URL(url); 55 urlWithCacheBusting.search += (urlWithCacheBusting.search ? '&' : '') + 56 'sw-precache=' + param; 57 58 return urlWithCacheBusting.toString(); 59 }; 60 61var isPathWhitelisted = function(whitelist, absoluteUrlString) { 62 // If the whitelist is empty, then consider all URLs to be whitelisted. 63 if (whitelist.length === 0) { 64 return true; 65 } 66 67 // Otherwise compare each path regex to the path of the URL passed in. 68 var path = (new URL(absoluteUrlString)).pathname; 69 return whitelist.some(function(whitelistedPathRegex) { 70 return path.match(whitelistedPathRegex); 71 }); 72 }; 73 74var populateCurrentCacheNames = function(precacheConfig, 75 cacheNamePrefix, baseUrl) { 76 var absoluteUrlToCacheName = {}; 77 var currentCacheNamesToAbsoluteUrl = {}; 78 79 precacheConfig.forEach(function(cacheOption) { 80 var absoluteUrl = new URL(cacheOption[0], baseUrl).toString(); 81 var cacheName = cacheNamePrefix + absoluteUrl + '-' + cacheOption[1]; 82 currentCacheNamesToAbsoluteUrl[cacheName] = absoluteUrl; 83 absoluteUrlToCacheName[absoluteUrl] = cacheName; 84 }); 85 86 return { 87 absoluteUrlToCacheName: absoluteUrlToCacheName, 88 currentCacheNamesToAbsoluteUrl: currentCacheNamesToAbsoluteUrl 89 }; 90 }; 91 92var stripIgnoredUrlParameters = function(originalUrl, 93 ignoreUrlParametersMatching) { 94 var url = new URL(originalUrl); 95 96 url.search = url.search.slice(1) // Exclude initial '?' 97 .split('&') // Split into an array of 'key=value' strings 98 .map(function(kv) { 99 return kv.split('='); // Split each 'key=value' string into a [key, value] array 100 }) 101 .filter(function(kv) { 102 return ignoreUrlParametersMatching.every(function(ignoredRegex) { 103 return !ignoredRegex.test(kv[0]); // Return true iff the key doesn't match any of the regexes. 104 }); 105 }) 106 .map(function(kv) { 107 return kv.join('='); // Join each [key, value] array into a 'key=value' string 108 }) 109 .join('&'); // Join the array of 'key=value' strings into a string with '&' in between each 110 111 return url.toString(); 112 }; 113 114 115var mappings = populateCurrentCacheNames(PrecacheConfig, CacheNamePrefix, self.location); 116var AbsoluteUrlToCacheName = mappings.absoluteUrlToCacheName; 117var CurrentCacheNamesToAbsoluteUrl = mappings.currentCacheNamesToAbsoluteUrl; 118 119function deleteAllCaches() { 120 return caches.keys().then(function(cacheNames) { 121 return Promise.all( 122 cacheNames.map(function(cacheName) { 123 return caches.delete(cacheName); 124 }) 125 ); 126 }); 127} 128 129self.addEventListener('install', function(event) { 130 event.waitUntil( 131 // Take a look at each of the cache names we expect for this version. 132 Promise.all(Object.keys(CurrentCacheNamesToAbsoluteUrl).map(function(cacheName) { 133 return caches.open(cacheName).then(function(cache) { 134 // Get a list of all the entries in the specific named cache. 135 // For caches that are already populated for a given version of a 136 // resource, there should be 1 entry. 137 return cache.keys().then(function(keys) { 138 // If there are 0 entries, either because this is a brand new version 139 // of a resource or because the install step was interrupted the 140 // last time it ran, then we need to populate the cache. 141 if (keys.length === 0) { 142 // Use the last bit of the cache name, which contains the hash, 143 // as the cache-busting parameter. 144 // See https://github.com/GoogleChrome/sw-precache/issues/100 145 var cacheBustParam = cacheName.split('-').pop(); 146 var urlWithCacheBusting = getCacheBustedUrl( 147 CurrentCacheNamesToAbsoluteUrl[cacheName], cacheBustParam); 148
149 var request = new Request(urlWithCacheBusting, 150 {credentials: 'same-origin'}); 151 return fetch(request).then(function(response) { 152 if (response.ok) { 153 return cache.put(CurrentCacheNamesToAbsoluteUrl[cacheName], 154 response); 155 } 156 157 console.error('Request for %s returned a response status %d, ' + 158 'so not attempting to cache it.', 159 urlWithCacheBusting, response.status); 160 // Get rid of the empty cache if we can't add a successful response to it. 161 return caches.delete(cacheName); 162 }); 163 } 164 }); 165 }); 166 })).then(function() { 167 return caches.keys().then(function(allCacheNames) { 168 return Promise.all(allCacheNames.filter(function(cacheName) { 169 return cacheName.indexOf(CacheNamePrefix) === 0 && 170 !(cacheName in CurrentCacheNamesToAbsoluteUrl); 171 }).map(function(cacheName) { 172 return caches.delete(cacheName); 173 }) 174 ); 175 }); 176 }).then(function() { 177 if (typeof self.skipWaiting === 'function') { 178 // Force the SW to transition from installing -> active state 179 self.skipWaiting(); 180 } 181 }) 182 ); 183}); 184 185if (self.clients && (typeof self.clients.claim === 'function')) { 186 self.addEventListener('activate', function(event) { 187 event.waitUntil(self.clients.claim()); 188 }); 189} 190 191self.addEventListener('message', function(event) { 192 if (event.data.command === 'delete_all') { 193 console.log('About to delete all caches...'); 194 deleteAllCaches().then(function() { 195 console.log('Caches deleted.'); 196 event.ports[0].postMessage({ 197 error: null 198 }); 199 }).catch(function(error) { 200 console.log('Caches not deleted:', error); 201 event.ports[0].postMessage({ 202 error: error 203 }); 204 }); 205 } 206}); 207 208 209self.addEventListener('fetch', function(event) { 210 if (event.request.method === 'GET') { 211 var urlWithoutIgnoredParameters = stripIgnoredUrlParameters(event.request.url, 212 IgnoreUrlParametersMatching); 213 214 var cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters]; 215 var directoryIndex = 'index.html'; 216 if (!cacheName && directoryIndex) { 217 urlWithoutIgnoredParameters = addDirectoryIndex(urlWithoutIgnoredParameters, directoryIndex); 218 cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters]; 219 } 220 221 var navigateFallback = ''; 222 // Ideally, this would check for event.request.mode === 'navigate', but that is not widely 223 // supported yet: 224 // https://code.google.com/p/chromium/issues/detail?id=540967 225 // https://bugzilla.mozilla.org/show_bug.cgi?id=1209081 226 if (!cacheName && navigateFallback && event.request.headers.has('accept') && 227 event.request.headers.get('accept').includes('text/html') && 228 /* eslint-disable quotes, comma-spacing */ 229 isPathWhitelisted([], event.request.url)) { 230 /* eslint-enable quotes, comma-spacing */ 231 var navigateFallbackUrl = new URL(navigateFallback, self.location); 232 cacheName = AbsoluteUrlToCacheName[navigateFallbackUrl.toString()]; 233 } 234 235 if (cacheName) { 236 event.respondWith( 237 // Rely on the fact that each cache we manage should only have one entry, and return that. 238 caches.open(cacheName).then(function(cache) { 239 return cache.keys().then(function(keys) { 240 return cache.match(keys[0]).then(function(response) { 241 if (response) { 242 return response; 243 } 244 // If for some reason the response was deleted from the cache, 245 // raise and exception and fall back to the fetch() triggered in the catch(). 246 throw Error('The cache ' + cacheName + ' is empty.'); 247 }); 248 }); 249 }).catch(function(e) { 250 console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e); 251 return fetch(event.request); 252 }) 253 ); 254 } 255 } 256}); 257 258 259 260
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.