// TODO: increase `version` number to force cache update when publishing a new release
const version = 'v1';
const config = {
cacheRemote: true,
version: version+'::',
preCachingItems: [
'index.html',
'offline.html',
'404.html',
'sw.js'
],
blacklistCacheItems: [
'index.html',
'service-worker.js'
],
offlineImage: '<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="#aaa" d="M0 0h400v300H0z"/>' + '<text fill="#222" font-family="monospace" font-size="32" font-weight="bold">' + '<tspan x="136" y="156">offline</tspan></text></g></svg>',
offlinePage: 'offline.html',
notFoundPage: '404.html'
};
function cacheName(key, opts) {
return `${opts.version}${key}`;
}
function addToCache(cacheKey, request, response) {
if (response.ok) {
const copy = response.clone();
caches.open(cacheKey).then(cache => {
cache.put(request, copy);
});
}
return response;
}
function fetchFromCache(event) {
return caches.match(event.request).then(response => {
if (!response) {
throw Error(`${event.request.url} not found in cache`);
} else if (response.status === 404) {
return caches.match(config.notFoundPage);
}
return response;
});
}
function offlineResponse(resourceType, opts) {
if (resourceType === 'content') {
return caches.match(opts.offlinePage);
}
if (resourceType === 'image') {
return new Response(opts.offlineImage, {
headers: { 'Content-Type': 'image/svg+xml' }
});
}
return undefined;
}
self.addEventListener('install', event => {
event.waitUntil(caches.open(
cacheName('static', config)
)
.then(cache => cache.addAll(config.preCachingItems))
.then(() => self.skipWaiting())
);
});
self.addEventListener('activate', event => {
function clearCacheIfDifferent(event, opts) {
return caches.keys().then(cacheKeys => {
const oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
const deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
return Promise.all(deletePromises);
});
}
event.waitUntil(
clearCacheIfDifferent(event, config)
.then(() => self.clients.claim())
);
});
self.addEventListener('fetch', event => {
const request = event.request;
const url = new URL(request.url);
if (request.method !== 'GET'
|| (config.cacheRemote !== true && url.origin !== self.location.origin)
|| (config.blacklistCacheItems.length > 0 && config.blacklistCacheItems.indexOf(url.pathname) !== -1)) {
// default browser behavior
return;
}
let cacheKey;
let resourceType = 'content';
if (/(.jpg|.jpeg|.webp|.png|.svg|.gif)$/.test(url.pathname)) {
resourceType = 'image';
} else if (/.\/fonts.(?:googleapis|gstatic).com/.test(url.origin)) {
resourceType = 'font';
}
cacheKey = cacheName(resourceType, config);
if (resourceType === 'content') {
// Network First Strategy
event.respondWith(
fetch(request)
.then(response => addToCache(cacheKey, request, response))
.catch(() => fetchFromCache(event))
.catch(() => offlineResponse(resourceType, config))
);
} else {
// Cache First Strategy
event.respondWith(
fetchFromCache(event)
.catch(() => fetch(request))
.then(response => addToCache(cacheKey, request, response))
.catch(() => offlineResponse(resourceType, config))
);
}
});