diff --git a/static/serviceworker.js b/static/serviceworker.js index 0a934e2..66372a6 100644 --- a/static/serviceworker.js +++ b/static/serviceworker.js @@ -1,26 +1,67 @@ let version = 'v1::'; let cacheName = 'website'; -let offlinePage = '/offline'; +let offlinePage = '/offline/'; let offlineFundamentals = [offlinePage, '/']; let maxItems = 100; -self.addEventListener('install', function (event) { - // Cache offline fundamentals - event.waitUntil(caches.open(version + cacheName).then(function (cache) { - return cache.addAll(offlineFundamentals); - })); -}); -function trimCache(name, maxItems) { - caches.open(name).then(function(cache) { - cache.keys().then(function(keys) { - if (keys.length > maxItems) { - cache.delete(keys[0]).then(trimCache(name, maxItems)); - } - }); +function addFundamentals() { + return caches.open(version + cacheName) + .then(cache => cache.addAll(offlineFundamentals)) +} + +// Cache the page(s) that initiate the service worker +function cacheClients() { + return clients.matchAll({ + includeUncontrolled: true + }) + .then(allClients => allClients.map(client => client.url)) + .then(pages => Promise.all([pages, caches.open(cacheName)])) + .then(([pages, cache]) => cache.addAll(pages)) +} + +// Remove caches whose name is no longer valid +function clearInvalidatedCaches() { + return caches.keys() + .then( keys => { + return Promise.all(keys + .filter(key => !key.includes(version)) + .map(key => caches.delete(key)) + ); }); } +function trimCache(name, maxItems) { + return caches.open(name) + .then(cache => Promise.all([cache, cache.keys()])) + // Make sure offlineFundamentals don't get deleted + .then(([cache, keys]) => [ + cache, + keys.filter(key => !offlineFundamentals.includes(key)), + keys.length - maxItems + ]) + .then(([cache, possibleDelete, numToDelete]) => { + // Trim cache until we are of the right size + deleteInProgress = [] + for (let i = 0; i < numToDelete; i++) { + // Keep track of each delete + deleteInProgress.push(cache.delete(possibleDelete[i])); + } + + // Return when everything is resolved + return Promise.all(deleteInProgress); + }) +} + +self.addEventListener('install', function (event) { + // Cache offline fundamentals + event.waitUntil( + addFundamentals() + .then(() => cacheClients()) + .then(() => skipWaiting()) + ) +}); + // Listens for trimCache command which occurs on page load self.addEventListener('message', event => { if (event.data.command == 'trimCache') { @@ -28,65 +69,68 @@ self.addEventListener('message', event => { } }); - // If the version changes, invalidate the cache self.addEventListener('activate', function (event) { event.waitUntil( - caches.keys() - .then(function (keys) { - // Remove caches whose name is no longer valid - return Promise.all(keys - .filter(function (key) { - return key.indexOf(version) !== 0; - }) - .map(function (key) { - return caches.delete(key); - }) - ); - }) + clearInvalidatedCaches() + .then(() => clients.claim()) ); }); +if (registration.navigationPreload) { + addEventListener('activate', event => { + event.waitUntil( + registration.navigationPreload.enable() + ); + }); +} + // Listen for request events self.addEventListener('fetch', function (event) { - let request = event.request; + const request = event.request; + + let isRequestType = function(name) { + return request.headers + .get('Accept') + .includes(name); + } // Always fetch non-GET requests from the network if (request.method !== 'GET') { - event.respondWith( - fetch(request) - .catch(function () { - return caches.match(offlinePage); - }) - ); + // Present offline page when failed to + // fetch a HTML page + if (isRequestType('text/html')) { + event.respondWith( + fetch(request) + .catch(() => caches.match(offlinePage)) + ); + } return; } - let isRequestType = function(name) { return request.headers.get('Accept').includes(name); } - // Network-first Approach event.respondWith( // Attepmt to grab the latest copy from the network - fetch(request).then(function (response) { + Promise.resolve(event.preloadResponse) + .then(preloadResponse => preloadResponse || fetch(request)) + .then(response => { // If successful, create a copy of the response // and save it to the cache // Note: Ignore badges - if (request.url.includes("/badges")) { + if (!request.url.includes("/badges")) { let cacheCopy = response.clone(); - event.waitUntil(caches.open(version + cacheName).then(function (cache) { - return cache.put(request, cacheCopy); - })); + event.waitUntil( + caches.open(version + cacheName) + .then(cache => cache.put(request, cacheCopy)) + ); } return response; - }).catch(function (error) { - // Otherwise, check the cache. - return caches.match(request).then(function (response) { - // Show offline page if cache misses for HTML pages. - if (isRequestType('text/html')) { - return response || caches.match(offlinePage); - } - return response; - }); }) + // Check the cache + .catch(error => + caches.match(request) + // Show offline page for HTML pages if cache miss + .then(response => isRequestType('text/html')? response || caches.match(offlinePage) : response) + ) ); }); \ No newline at end of file