service-worker.js

/**
* @file Defines a Service Worker for the app
* @author Reuben L. Lillie <reubenlillie@gmail.com>
* @since 1.0.0
* @see {@link https://developers.google.com/web/fundamentals/primers/service-workers “Service Workers: an Introduction” by Matt Gaunt on Web Fundamentals}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API Service Worker API on MDN}
* @see {@link https://gomakethings.com/series/service-workers/ Service Workers series by Chris Ferdinandi}
*/


/**
* The service worker version
* @type {string}
*/

var version = 'reubenlillie_dot_com_1.5.0'

/**
* The key for core assets in CacheStorage
* @type {string}
*/

var coreID = `${version}_core`

/**
* The key for HTML pages in CacheStorage
* @type {string}
*/

var pagesID = `${version}_pages`

/**
* The key for images pages in CacheStorage
* @type {string}
*/

var imagesID = `${version}_images`

/**
* Version-appended `cacheName`
* @type {string[]}
*/

var cacheIDs = [coreID, pagesID, imagesID]

/**
* Relative URLs of stylesheets to cache
* @type {string[]}
*/

var css = [
'/css/print.min.css'
]

/**
* Relative URLs of font files to cache
* @type {string[]}
*/

var fonts = [
'/fonts/courier-prime/CourierPrime.woff2',
'/fonts/courier-prime/CourierPrime-Bold.woff2',
'/fonts/courier-prime/CourierPrime-BoldItalic.woff2',
'/fonts/courier-prime/CourierPrime-Italic.woff2',
'/fonts/courier-prime/CourierPrimeCode.woff2',
'/fonts/courier-prime/CourierPrimeCode-Italic.woff2'
]

/**
* Relative URLs of images to cache
* @type {string[]}
*/

var images = [
'/img/zFsB7aOWjs-788.webp'
]

/**
* Relative URLs of pages to cache
* @type {string[]}
*/

var pages = [
'/',
'/offline'
]

/**
* Add core assets to CacheStorage
* @since 1.0.0
* @return {function} Add response objects to a given cache
*/

var cacheCoreAssets = async () => {
/**
* URLs to cache
* @type {string[]}
*/

var toCache = [...css, ...fonts, ...images, ...pages]
console.log(toCache)
/**
* `Cache` keys
* @type {Promise<Object.<Cache>>}
*/

var cache = await caches.open(coreID)

// Add each item to the cache
return cache.addAll(toCache)
}

/**
* Add a requested resource to `CacheStorage`
* @since 1.0.0
* @param {Object.<Request>} request The request from a fetch event
* @param {Object.<Response>} response The response to pair with the request
* @param {string} cacheID The key for the target cache
* @return {Promise} Adds request/response key/value pairs to the cache
*/

var addToCache = async (request, response, cacheID) => {
/**
* `Cache` keys
* @type {Promise<Object.<Cache>>}
*/

var cache = await caches.open(cacheID)

// Add both the request and its matching repsone to the cache
return await cache.put(request, response)
}

/**
* Remove keys from `CacheStorage`
* @since 1.0.0
* @param {string[]} keys Names of `Cache` objects to remove
* @return {void}
*/

var removeFromCache = keys =>
// Delete each key from the cache
keys.filter(key => !cacheIDs.includes(key))
.map(key => caches.delete(key))

/**
* Clear `CacheStorage`
* @since 1.0.0
* @return {function} Sets this service worker for the client
*/

var clearCache = async () => {
/**
* `Cache` keys
* @type {Promise<string[]>}
*/

var keys = await caches.keys()

// Remove all the keys from the cache objects
await Promise.all(removeFromCache(keys))

// Take control from any other registered service workers
return self.clients.claim()
}

/**
* Fetch a resource from the network to add to the appropriate cache
* @since 1.0.0
* @param {Object.<Event>} event A `fetch` event
* @return {Object} The response object
*/

var fetchFromNetwork = async event => {
/**
* The `fetch` response
* @type {Promise<Object.<Response>>}
*/

var response = await fetch(event.request)

// For an image, store a copy of in the images cache
if (event.request.headers.get('Accept').includes('image')) {
/**
* Store a copy of the response for matching with requests
* @type {Object.<Response>}
*/

var copy = response.clone()

// Add the response to the pagesID cache
event.waitUntil(addToCache(event.request, copy, imagesID))
}
return response
}

/**
* Cache items fetched from the network
* @since 1.0.0
* @param {Object.<Event>} event A `fetch` event
* @return {Object|Promise} The response object or the cached object
*/

var networkFirst = async event => {
try {
/**
* The `fetch` response
* @type {Promise<Object.<Response>>}
*/

var response = await fetch(event.request)

// If response is not restricted
if (response.type !== 'opaque') {
/**
* Store a copy of the response for matching with requests
* @type {Object.<Response>}
*/

var copy = response.clone()

// Add the response to the pagesID cache
event.waitUntil(addToCache(event.request, copy, pagesID))
}
return response
} catch (error) {
/**
* The first matching request in the `Cache` object
* @type {Promise<Object.<Response>>}
*/

response = await caches.match(event.request)

// Redirect the user to the offline page when there is not matching response in the cache
return await (response || caches.match('/offline/'))
}
}

/**
* Check the `CacheStorage` before fetching a resource from the network
* @since 1.0.0
* @param {Object.<Event>} event A `fetch` event
* @return {Object|function} The cached object or the response from the network
*/

var offlineFirst = async event => {
/**
* The first matching request in the `Cache` object
* @type {Promise<Object.<Response>>}
*/

var response = await caches.match(event.request)

// Fetch the resource from the network when there is no matching response the cache
return response || fetchFromNetwork(event)
}

// On install, cache core assets
self.addEventListener('install', event => {
// The promise that skipWaiting() returns can be safely ignored
self.skipWaiting()
event.waitUntil(cacheCoreAssets())
})

// On version update, clear stale `CacheStorage`
self.addEventListener('activate', event => event.waitUntil(clearCache()))

// On fetch request, coordinate responses and cached assets
self.addEventListener('fetch', event => {
/**
* The resource request from the fetch event
* @type {Object.<Request>}
*/

var request = event.request

/*
* Bug: Hide 'only-if-cached' fetch error
* @see {@link https://github.com/paulirish/caltrainschedule.io/pull/51/commits CORS–DevTools bug fix Paul Irish on GitHub}
*/

if(request.cache === 'only-if-cached' && request.mode !== 'same-origin') return

// Ignore nonGET requests
if(request.method !== 'GET') return

// Is the response an HTML page?
if(request.headers.get('Accept').includes('text/html')) {
// Follow a network-first approach to caching
return event.respondWith(networkFirst(event))
}

// Is the response an image?
if(request.headers.get('Accept').includes('image')
// Or a font?
|| request.url.includes('*.woff2')) {
// Follow an offline-first approach to caching
return event.respondWith(offlineFirst(event))
}

return
})