diff --git a/service-worker.js b/service-worker.js index 2cd5c65..22ab13f 100644 --- a/service-worker.js +++ b/service-worker.js @@ -92,11 +92,11 @@ self.log = function(component, ...items) { /** - * verifying a config JSON + * verifying and loading a config JSON * * cdata - config data to verify */ -let verifyConfigData = (cdata) => { +let loadConfigData = (cdata) => { // basic check for the plugins field if ( !("plugins" in cdata) || ! Array.isArray(cdata.plugins) ) { self.log('service-worker', 'fetched config does not contain a valid "plugins" field') @@ -107,23 +107,34 @@ let verifyConfigData = (cdata) => { self.log('service-worker', 'fetched config does not contain a valid "loggedComponents" field') return false; } - // defaultPluginTimeout optional + // defaultPluginTimeout is optional if ("defaultPluginTimeout" in cdata) { if (!Number.isInteger(cdata.defaultPluginTimeout)) { self.log('service-worker', 'fetched config contains invalid "defaultPluginTimeout" data (integer expected)') return false; } + // safe to apply defaultPluginTimeout + self.LibResilientConfig.defaultPluginTimeout = cdata.defaultPluginTimeout } + // safe to apply main config data + self.LibResilientConfig.plugins = cdata.plugins + self.LibResilientConfig.loggedComponents = cdata.loggedComponents // we're good return true; } +// flag signifying the SW has been initialized already +var initDone = false // load the plugins -// -// everything in a try-catch block -// so that we get an informative message if there's an error let initServiceWorker = async () => { + // if init has already been done, skip! + if (initDone) { + self.log('service-worker', 'skipping service-worker init, already done') + return false; + } + // everything in a try-catch block + // so that we get an informative message if there's an error try { // get the config @@ -134,19 +145,37 @@ let initServiceWorker = async () => { // TODO: providing config directly from browser-side control script via postMessage? // TODO: `updateViaCache=imports` allows at least config.json to be updated using the cache plugin? try { - //self.importScripts(self.registration.scope + "config.json") - var cdata = await fetch(self.registration.scope + "config.json") - if (cdata.status != 200) { + // config.json URL + var configURL = self.registration.scope + "config.json" + // we need to know if the config was already cached + var wasCached = true + // get the config file, cached or otherwise + var cresponse = await caches.match(configURL) + if (cresponse != undefined) { + self.log('service-worker', `config file retrieved from cache.`) + } else { + self.log('service-worker', `config file not found in cache, fetching.`) + wasCached = false + cresponse = await fetch(configURL) + } + // check for sanity + if (cresponse.status != 200) { self.log('service-worker', `failed to fetch config (${cdata.status} ${cdata.statusText}).`) } else { - cdata = await cdata.json() - if (verifyConfigData(cdata)) { - self.LibResilientConfig.plugins = cdata.plugins - self.LibResilientConfig.loggedComponents = cdata.loggedComponents - if ("defaultPluginTimeout" in cdata) { - self.LibResilientConfig.defaultPluginTimeout = cdata.defaultPluginTimeout - } + // process the data + cdata = await cresponse.clone().json() + if (loadConfigData(cdata)) { self.log('service-worker', 'config loaded.') + // cache the valid config.json + if (!wasCached) { + try { + var cache = await caches.open('v1') + await cache.put(configURL, cresponse) + self.log('service-worker', 'config cached.') + } catch(e) { + self.log('service-worker', `failed to cache config: ${e}`) + } + } } else { self.log('service-worker', 'ignoring invalid config, using defaults.') } @@ -251,6 +280,7 @@ let initServiceWorker = async () => { // inform self.log('service-worker', `DEBUG: Strategy in use: ${self.LibResilientPlugins.map(p=>p.name).join(', ')}`) + initDone = true; } catch(e) { // we only get a cryptic "Error while registering a service worker" @@ -258,6 +288,7 @@ let initServiceWorker = async () => { console.error(e) throw e } + return true; } /** @@ -718,80 +749,83 @@ let getResourceThroughLibResilient = (url, init, clientId, useStashed=true, doSt \* ========================================================================= */ self.addEventListener('install', async (event) => { event.waitUntil(initServiceWorker()) - // TODO: Might we want to have a local cache? // "COMMIT_UNKNOWN" will be replaced with commit ID self.log('service-worker', "0. Installed LibResilient Service Worker (commit: COMMIT_UNKNOWN)."); }); -self.addEventListener('activate', event => { +self.addEventListener('activate', async event => { self.log('service-worker', "1. Activated LibResilient Service Worker (commit: COMMIT_UNKNOWN)."); // TODO: should we do some plugin initialization here? }); -self.addEventListener('fetch', event => { - // if event.resultingClientId is available, we need to use this - // otherwise event.clientId is what we want - // ref. https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/resultingClientId - var clientId = (event.clientId !== null) ? event.clientId : 'unknown-client' - if (event.resultingClientId) { - clientId = event.resultingClientId - // yeah, we seem to have to send the client their clientId - // because there is no way to get that client-side - // and we need that for sane messaging later - // - // so let's also send the plugin list, why not - // - // *sigh* JS is great *sigh* - self.clients - .get(clientId) - .then((client)=>{ - if (client !== null) { - try { - client.postMessage({ - clientId: clientId, - plugins: self.LibResilientPlugins.map((p)=>{return p.name}), - serviceWorker: 'COMMIT_UNKNOWN' - }) - } catch(err) { - self.log("service-worker", `postMessage failed for client: ${client}\n- Error message: ${err}`) +self.addEventListener('fetch', async event => { + return void event.respondWith(async function () { + // initialize the SW + await initServiceWorker() + // if event.resultingClientId is available, we need to use this + // otherwise event.clientId is what we want + // ref. https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/resultingClientId + var clientId = (event.clientId !== null) ? event.clientId : 'unknown-client' + if (event.resultingClientId) { + clientId = event.resultingClientId + // yeah, we seem to have to send the client their clientId + // because there is no way to get that client-side + // and we need that for sane messaging later + // + // so let's also send the plugin list, why not + // + // *sigh* JS is great *sigh* + self.clients + .get(clientId) + .then((client)=>{ + if (client !== null) { + try { + client.postMessage({ + clientId: clientId, + plugins: self.LibResilientPlugins.map((p)=>{return p.name}), + serviceWorker: 'COMMIT_UNKNOWN' + }) + } catch(err) { + self.log("service-worker", `postMessage failed for client: ${client}\n- Error message: ${err}`) + } } - } - }) - } - - // counter! - if (typeof self.activeFetches.get(clientId) !== "number") { - self.activeFetches.set(clientId, 0) - } - - // info - self.log('service-worker', "Fetching!", - "\n+-- url :", event.request.url, - "\n+-- clientId :", event.clientId, - "\n+-- resultingClientId:", event.resultingClientId, - "\n +-- activeFetches[" + clientId + "]:", self.activeFetches.get(clientId) - ) + }) + } + + // counter! + if (typeof self.activeFetches.get(clientId) !== "number") { + self.activeFetches.set(clientId, 0) + } + + // info + self.log('service-worker', "Fetching!", + "\n+-- url :", event.request.url, + "\n+-- clientId :", event.clientId, + "\n+-- resultingClientId:", event.resultingClientId, + "\n +-- activeFetches[" + clientId + "]:", self.activeFetches.get(clientId) + ) - // External requests go through a regular fetch() - if (!event.request.url.startsWith(self.location.origin)) { - self.log('service-worker', 'External request; current origin: ' + self.location.origin) - return void event.respondWith(fetch(event.request)); - } + // External requests go through a regular fetch() + if (!event.request.url.startsWith(self.location.origin)) { + self.log('service-worker', 'External request; current origin: ' + self.location.origin) + return void event.respondWith(fetch(event.request)); + } - // Non-GET requests go through a regular fetch() - if (event.request.method !== 'GET') { - return void event.respondWith(fetch(event.request)); - } - - // clean the URL, removing any fragment identifier - var url = event.request.url.replace(/#.+$/, ''); - - // get the init object from Request - var init = initFromRequest(event.request) + // Non-GET requests go through a regular fetch() + if (event.request.method !== 'GET') { + return void event.respondWith(fetch(event.request)); + } + + // clean the URL, removing any fragment identifier + var url = event.request.url.replace(/#.+$/, ''); + + // get the init object from Request + var init = initFromRequest(event.request) - // GET requests to our own domain that are *not* #libresilient-info requests - // get handled by plugins in case of an error - return void event.respondWith(getResourceThroughLibResilient(url, init, clientId)) + // GET requests to our own domain that are *not* #libresilient-info requests + // get handled by plugins in case of an error + return getResourceThroughLibResilient(url, init, clientId) + }()) });