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
*/
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)
}())
});