Enhanced offline (#249)

* Notification of updates & reloading

* Using version in service worker & allowing version to appear elsewhere

* Stupid file

* Ditching changelog for now. Using package json.

* Ugh.
This commit is contained in:
Jake Archibald
2018-11-09 09:13:14 -08:00
committed by GitHub
parent 6b76ea0a6f
commit 71f893cb44
11 changed files with 129 additions and 59 deletions

91
src/lib/offliner.ts Normal file
View File

@@ -0,0 +1,91 @@
import { get, set } from 'idb-keyval';
// Just for TypeScript
import SnackBarElement from './SnackBar';
/** Tell the service worker to skip waiting */
async function skipWaiting() {
const reg = await navigator.serviceWorker.getRegistration();
if (!reg || !reg.waiting) return;
reg.waiting.postMessage('skip-waiting');
}
/** Find the service worker that's 'active' or closest to 'active' */
async function getMostActiveServiceWorker() {
const reg = await navigator.serviceWorker.getRegistration();
if (!reg) return null;
return reg.active || reg.waiting || reg.installing;
}
/** Wait for an installing worker */
async function installingWorker(reg: ServiceWorkerRegistration): Promise<ServiceWorker> {
if (reg.installing) return reg.installing;
return new Promise<ServiceWorker>((resolve) => {
reg.addEventListener(
'updatefound',
() => resolve(reg.installing!),
{ once: true },
);
});
}
/** Wait a service worker to become waiting */
async function updateReady(reg: ServiceWorkerRegistration): Promise<void> {
if (reg.waiting) return;
const installing = await installingWorker(reg);
return new Promise<void>((resolve) => {
installing.addEventListener('statechange', () => {
if (installing.state === 'installed') resolve();
});
});
}
/** Set up the service worker and monitor changes */
export async function offliner(showSnack: SnackBarElement['showSnackbar']) {
if (process.env.NODE_ENV === 'production') {
navigator.serviceWorker.register('../sw');
}
const hasController = !!navigator.serviceWorker.controller;
// Look for changes in the controller
navigator.serviceWorker.addEventListener('controllerchange', async () => {
// Is it the first install?
if (!hasController) {
showSnack('Ready to work offline', { timeout: 5000 });
return;
}
// Otherwise reload (the user will have agreed to this).
location.reload();
});
const reg = await navigator.serviceWorker.getRegistration();
// Service worker not registered yet.
if (!reg) return;
// Look for updates
await updateReady(reg);
// Ask the user if they want to update.
const result = await showSnack('Update available', {
actions: ['reload', 'dismiss'],
});
// Tell the waiting worker to activate, this will change the controller and cause a reload (see
// 'controllerchange')
if (result === 'reload') skipWaiting();
}
/**
* Tell the service worker the main app has loaded. If it's the first time the service worker has
* heard about this, cache the heavier assets like codecs.
*/
export async function mainAppLoaded() {
// If the user has already interacted, no need to tell the service worker anything.
const userInteracted = await get<boolean | undefined>('user-interacted');
if (userInteracted) return;
set('user-interacted', true);
const serviceWorker = await getMostActiveServiceWorker();
if (!serviceWorker) return; // Service worker not installing yet.
serviceWorker.postMessage('cache-all');
}