199 lines
5.6 KiB
JavaScript
199 lines
5.6 KiB
JavaScript
/* -------- CACHE --------- */
|
|
|
|
const CURRENT_CACHE = `SW_CACHE:${$VERSION}`;
|
|
const APP_SHELL_CACHE = 'SW_APP_SHELL';
|
|
|
|
const isValidResponse = res => (res.ok || (res.status === 0 && res.type === 'opaque'));
|
|
const isNavigation = req => req.mode === 'navigate' || (req.method === 'GET' && req.headers.get('accept').includes('text/html'));
|
|
|
|
/* -------- CACHE LISTENERS --------- */
|
|
|
|
self.addEventListener('install', handleInstall);
|
|
self.addEventListener('activate', handleActivate);
|
|
if ($Cache.precache || $Cache.offline || $Cache.strategy) {
|
|
self.addEventListener('fetch', handleFetch);
|
|
}
|
|
|
|
/* -------- CACHE HANDLERS --------- */
|
|
|
|
function handleInstall(event) {
|
|
logger.log('Entering install handler.');
|
|
self.skipWaiting();
|
|
if ($Cache.precache) {
|
|
event.waitUntil(precache());
|
|
}
|
|
}
|
|
|
|
function handleActivate(event) {
|
|
logger.log('Entering activate handler.');
|
|
const cachesCleared = caches.keys().then(cacheNames => {
|
|
logger.group('cleanup');
|
|
return Promise.all(cacheNames.map(cacheName => {
|
|
if (CURRENT_CACHE !== cacheName) {
|
|
logger.log(`Deleting cache key: ${cacheName}`, 'cleanup');
|
|
return caches.delete(cacheName);
|
|
}
|
|
return Promise.resolve();
|
|
})).then(() => logger.groupEnd('cleanup'));
|
|
});
|
|
event.waitUntil(cachesCleared);
|
|
}
|
|
|
|
function handleFetch(event) {
|
|
if (isNavigation(event.request)) {
|
|
if ($Cache.offline) {
|
|
event.respondWith(
|
|
fetchAndCacheAppShell(event.request)
|
|
.catch(() => caches.match(APP_SHELL_CACHE))
|
|
.catch(() => undefined)
|
|
);
|
|
}
|
|
} else if (event.request.method === 'GET') {
|
|
const strategy = getStrategyForUrl(event.request.url);
|
|
if (strategy) {
|
|
logger.group(event.request.url);
|
|
logger.log(`Using strategy ${strategy.type}.`, event.request.url);
|
|
event.respondWith(
|
|
applyEventStrategy(strategy, event).then(response => {
|
|
logger.groupEnd(event.request.url);
|
|
return response;
|
|
}).catch(() => undefined)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------- CACHE HELPERS --------- */
|
|
|
|
function applyEventStrategy(strategy, event) {
|
|
const request = event.request;
|
|
switch (strategy.type) {
|
|
case 'offline-only':
|
|
return fetchAndCache(request, strategy)().catch(getFromCache(request));
|
|
case 'fallback-only':
|
|
return fetchAndCache(request, strategy)().then(fallbackToCache(request));
|
|
case 'prefer-cache':
|
|
return getFromCache(request)().catch(fetchAndCache(request, strategy));
|
|
case 'race':
|
|
return getFromFastest(request, strategy)();
|
|
default:
|
|
return Promise.reject(`Strategy not supported: ${strategy.type}`);
|
|
}
|
|
}
|
|
|
|
function insertInCache(request, response) {
|
|
logger.log('Inserting in cache.', request.url);
|
|
return caches.open(CURRENT_CACHE)
|
|
.then(cache => cache.put(request, response));
|
|
}
|
|
|
|
function getFromCache(request) {
|
|
return () => {
|
|
return caches.match(request).then(response => {
|
|
if (response) {
|
|
logger.log('Found entry in cache.', request.url);
|
|
return response;
|
|
}
|
|
logger.log('No entry found in cache.', request.url);
|
|
throw new Error(`No cache entry found for ${request.url}`);
|
|
});
|
|
};
|
|
}
|
|
|
|
function getStrategyForUrl(url) {
|
|
if ($Cache.strategy) {
|
|
return $Cache.strategy.find(strategy => {
|
|
return strategy.matches.some(match => {
|
|
const regex = new RegExp(match);
|
|
return regex.test(url);
|
|
});
|
|
});
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function fetchAndCache(request) {
|
|
return () => {
|
|
logger.log('Fetching remote data.', request.url);
|
|
return fetch(request).then(response => {
|
|
if (isValidResponse(response)) {
|
|
logger.log('Caching remote response.', request.url);
|
|
insertInCache(request, response.clone());
|
|
} else {
|
|
logger.log('Fetch error.', request.url);
|
|
}
|
|
return response;
|
|
});
|
|
};
|
|
}
|
|
|
|
function fetchAndCacheAppShell(request) {
|
|
return fetch(request).then(response => {
|
|
if (isValidResponse(response)) {
|
|
logger.log('Caching app shell.', request.url);
|
|
insertInCache(APP_SHELL_CACHE, response.clone());
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
function fallbackToCache(request) {
|
|
return (response) => {
|
|
if (!isValidResponse(response)) {
|
|
return getFromCache(request)();
|
|
}
|
|
return response;
|
|
};
|
|
}
|
|
|
|
function getFromFastest(request, strategy) {
|
|
return () => new Promise((resolve, reject) => {
|
|
var errors = 0;
|
|
|
|
function raceReject() {
|
|
errors += 1;
|
|
if (errors === 2) {
|
|
reject(new Error('Network and cache both failed.'));
|
|
}
|
|
}
|
|
|
|
function raceResolve(response) {
|
|
if (response instanceof Response) {
|
|
resolve(response);
|
|
} else {
|
|
raceReject();
|
|
}
|
|
}
|
|
|
|
getFromCache(request)()
|
|
.then(raceResolve)
|
|
.catch(raceReject);
|
|
|
|
fetchAndCache(request, strategy)()
|
|
.then(raceResolve)
|
|
.catch(raceReject);
|
|
});
|
|
}
|
|
|
|
function precache() {
|
|
logger.group('precaching');
|
|
return caches.open(CURRENT_CACHE).then(cache => {
|
|
return Promise.all(
|
|
$Cache.precache.map(urlToPrefetch => {
|
|
logger.log(urlToPrefetch, 'precaching');
|
|
const cacheBustedUrl = new URL(urlToPrefetch, location.href);
|
|
cacheBustedUrl.search += (cacheBustedUrl.search ? '&' : '?') + `cache-bust=${Date.now()}`;
|
|
|
|
const request = new Request(cacheBustedUrl, { mode: 'no-cors' });
|
|
return fetch(request).then(response => {
|
|
if (!isValidResponse(response)) {
|
|
logger.error(`Failed for ${urlToPrefetch}.`, 'precaching');
|
|
return undefined;
|
|
}
|
|
return cache.put(urlToPrefetch, response);
|
|
});
|
|
})
|
|
);
|
|
}).then(() => logger.groupEnd('precaching'));
|
|
}
|