important hotfix: config.json now properly cached, SW init performed reliably after SW re-start (ref. #31)

merge-requests/12/merge
Michał 'rysiek' Woźniak 2022-01-26 00:26:16 +00:00
rodzic 387009782e
commit 6fe4e94199
1 zmienionych plików z 112 dodań i 78 usunięć

Wyświetl plik

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