136 lines
3.4 KiB
JavaScript
136 lines
3.4 KiB
JavaScript
const appCacheName = 'simple-pwa';
|
|
const appContentToCache = [
|
|
'/',
|
|
'index.html',
|
|
'app.js',
|
|
'favicon.ico',
|
|
'manifest.webmanifest',
|
|
'icons/icon-512.png',
|
|
];
|
|
const dataCacheName = `${appCacheName}-data`;
|
|
|
|
// Communication channel with clients
|
|
const channel = new BroadcastChannel('sw-messages');
|
|
|
|
/**
|
|
* First of all, the service worker will react to an 'install' event (triggered automatically)
|
|
* We'll put the application content into cache.
|
|
*/
|
|
self.addEventListener('install', (event) => {
|
|
event.waitUntil(install());
|
|
});
|
|
|
|
/**
|
|
* Then take immediate control on the page that triggered the installation
|
|
*/
|
|
self.addEventListener('activate', () => {
|
|
self.clients.claim();
|
|
channel.postMessage({ ready: true });
|
|
});
|
|
|
|
/**
|
|
* Then, the service worker will be involved every time a request is made
|
|
*/
|
|
self.addEventListener('fetch', (e) => {
|
|
e.respondWith(fetch_resource(e.request));
|
|
});
|
|
|
|
/**
|
|
* handle installation request:
|
|
* - fetch application resources and store them in appCache
|
|
*/
|
|
async function install() {
|
|
return new Promise(async (resolve, reject) => {
|
|
try {
|
|
await caches.delete(appCacheName);
|
|
const cache = await caches.open(appCacheName);
|
|
await cache.addAll(appContentToCache);
|
|
resolve();
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* fetch a resource:
|
|
* - if resource is in app cache, return in
|
|
* - if resource can be obtained from remote server, fetch it
|
|
* - if resource is in data cache, return it
|
|
* - otherwise return HTTP-408 response
|
|
*/
|
|
async function fetch_resource(resource) {
|
|
response = await get_from_cache(resource, appCacheName);
|
|
if (response) {
|
|
return response;
|
|
} else {
|
|
channel.postMessage({ loading: true });
|
|
try {
|
|
response = await fetch(resource);
|
|
await put_into_cache(resource, response);
|
|
channel.postMessage({ loading: false });
|
|
channel.postMessage({ offline: false });
|
|
return response;
|
|
} catch (error) {
|
|
// TODO: check if error is because we're offline
|
|
response = await get_from_cache(resource);
|
|
channel.postMessage({ loading: false });
|
|
channel.postMessage({ offline: true });
|
|
if (response) {
|
|
// resource was found in data cache
|
|
return response;
|
|
} else {
|
|
return new Response('offline', {
|
|
status: 408,
|
|
headers: { 'Content-Type': 'text/plain' },
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* reset app cache that contains application code to be cached for offline use
|
|
*/
|
|
async function reset_app_cache() {
|
|
log('resetting app cache');
|
|
await caches.delete(appCacheName);
|
|
const cache = await caches.open(appCacheName);
|
|
await cache.addAll(appContentToCache);
|
|
}
|
|
|
|
/**
|
|
* query cache for resource
|
|
*/
|
|
async function get_from_cache(resource, cacheName = dataCacheName) {
|
|
try {
|
|
const cache = await caches.open(cacheName);
|
|
const response = await cache.match(resource);
|
|
if (response) {
|
|
log(
|
|
`FROM ${cacheName === appCacheName ? 'APP' : 'DATA'} CACHE: ${
|
|
resource.url
|
|
}`,
|
|
);
|
|
}
|
|
return response;
|
|
} catch (error) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* put resource into cache
|
|
*/
|
|
async function put_into_cache(request, response, cacheName = dataCacheName) {
|
|
const cache = await caches.open(cacheName);
|
|
await cache.put(request, response.clone());
|
|
}
|
|
|
|
/**
|
|
* Log a message to console
|
|
*/
|
|
function log(message) {
|
|
console.log(`[Service Worker] ${message}`);
|
|
}
|