improved config.json error handling, including additional tests (ref. #48)

merge-requests/23/merge
Michał 'rysiek' Woźniak 2024-02-11 23:27:56 +00:00
rodzic 28c79a35e4
commit b50b76d0d5
2 zmienionych plików z 769 dodań i 366 usunięć

Wyświetl plik

@ -195,6 +195,11 @@ self.guessMimeType = async function(ext, content) {
* cdata - config data to verify
*/
let verifyConfigData = (cdata) => {
// cdata needs to be an object
if ( typeof cdata !== "object" || cdata === null ) {
self.log('service-worker', 'fetched config does not contain a valid JSON object')
return false;
}
// 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')
@ -283,140 +288,126 @@ let executeConfig = (config) => {
// working on a copy of the plugins config so that config.plugins remains unmodified
// in case we need it later (for example, when re-loading the config)
let pluginsConfig = [...config.plugins]
// this is the stash for plugins that need dependencies instantiated first
let dependentPlugins = new Array()
// we want to catch any and all possible errors here
try {
// only now load the plugins (config.json could have changed the defaults)
while (pluginsConfig.length > 0) {
// this is the stash for plugins that need dependencies instantiated first
let dependentPlugins = new Array()
// get the first plugin config from the array
let pluginConfig = pluginsConfig.shift()
self.log('service-worker', `handling plugin type: ${pluginConfig.name}`)
// only now load the plugins (config.json could have changed the defaults)
while (pluginsConfig.length > 0) {
// load the relevant plugin script (if not yet loaded)
if (!LibResilientPluginConstructors.has(pluginConfig.name)) {
self.log('service-worker', `${pluginConfig.name}: loading plugin's source`)
self.importScripts(`./plugins/${pluginConfig.name}/index.js`)
}
// do we have any dependencies we should handle first?
if (typeof pluginConfig.uses !== "undefined") {
self.log('service-worker', `${pluginConfig.name}: ${pluginConfig.uses.length} dependencies found`)
// get the first plugin config from the array
let pluginConfig = pluginsConfig.shift()
self.log('service-worker', `handling plugin type: ${pluginConfig.name}`)
// load the relevant plugin script (if not yet loaded)
if (!LibResilientPluginConstructors.has(pluginConfig.name)) {
self.log('service-worker', `${pluginConfig.name}: loading plugin's source`)
self.importScripts(`./plugins/${pluginConfig.name}/index.js`)
// move the dependency plugin configs to LibResilientConfig to be worked on next
for (let i=(pluginConfig.uses.length); i--; i>=0) {
self.log('service-worker', `${pluginConfig.name}: dependency found: ${pluginConfig.uses[i].name}`)
// put the plugin config in front of the plugin configs array
pluginsConfig.unshift(pluginConfig.uses[i])
// set each dependency plugin config to false so that we can keep track
// as we fill those gaps later with instantiated dependency plugins
pluginConfig.uses[i] = false
}
// do we have any dependencies we should handle first?
if (typeof pluginConfig.uses !== "undefined") {
self.log('service-worker', `${pluginConfig.name}: ${pluginConfig.uses.length} dependencies found`)
// move the dependency plugin configs to LibResilientConfig to be worked on next
for (let i=(pluginConfig.uses.length); i--; i>=0) {
self.log('service-worker', `${pluginConfig.name}: dependency found: ${pluginConfig.uses[i].name}`)
// put the plugin config in front of the plugin configs array
pluginsConfig.unshift(pluginConfig.uses[i])
// set each dependency plugin config to false so that we can keep track
// as we fill those gaps later with instantiated dependency plugins
pluginConfig.uses[i] = false
// stash the plugin config until we have all the dependencies handled
self.log('service-worker', `${pluginConfig.name}: not instantiating until dependencies are ready`)
dependentPlugins.push(pluginConfig)
// move on to the next plugin config, which at this point will be
// the first of dependencies for the plugin whose config got stashed
continue;
}
do {
// if the plugin is not enabled, no instantiation for it nor for its dependencies
// if the pluginConfig does not have an "enabled" field, it should be assumed to be "true"
if ( ( "enabled" in pluginConfig ) && ( pluginConfig.enabled != true ) ) {
self.log('service-worker', `skipping ${pluginConfig.name} instantiation: plugin not enabled (dependencies will also not be instantiated)`)
pluginConfig = dependentPlugins.pop()
if (pluginConfig !== undefined) {
let didx = pluginConfig.uses.indexOf(false)
pluginConfig.uses.splice(didx, 1)
}
// stash the plugin config until we have all the dependencies handled
self.log('service-worker', `${pluginConfig.name}: not instantiating until dependencies are ready`)
dependentPlugins.push(pluginConfig)
// move on to the next plugin config, which at this point will be
// the first of dependencies for the plugin whose config got stashed
continue;
}
do {
// if the plugin is not enabled, no instantiation for it nor for its dependencies
// if the pluginConfig does not have an "enabled" field, it should be assumed to be "true"
if ( ( "enabled" in pluginConfig ) && ( pluginConfig.enabled != true ) ) {
self.log('service-worker', `skipping ${pluginConfig.name} instantiation: plugin not enabled (dependencies will also not be instantiated)`)
pluginConfig = dependentPlugins.pop()
if (pluginConfig !== undefined) {
let didx = pluginConfig.uses.indexOf(false)
pluginConfig.uses.splice(didx, 1)
}
continue;
}
// instantiate the plugin
let plugin = LibResilientPluginConstructors.get(pluginConfig.name)(self, pluginConfig)
self.log('service-worker', `${pluginConfig.name}: instantiated`)
// do we have a stashed plugin that requires dependencies?
if (dependentPlugins.length === 0) {
// no we don't; so, this plugin goes directly to the plugin list
self.LibResilientPlugins.push(plugin)
// we're done here
self.log('service-worker', `${pluginConfig.name}: no dependent plugins, pushing directly to LibResilientPlugins`)
break;
}
// at this point clearly there is at least one element in dependentPlugins
// so we can safely assume that the freshly instantiated plugin is a dependency
//
// in that case let's find the first empty spot for a dependency
let didx = dependentPlugins[dependentPlugins.length - 1].uses.indexOf(false)
// assign the freshly instantiated plugin as that dependency
dependentPlugins[dependentPlugins.length - 1].uses[didx] = plugin
self.log('service-worker', `${pluginConfig.name}: assigning as dependency (#${didx}) to ${dependentPlugins[dependentPlugins.length - 1].name}`)
// was this the last one?
if (didx >= dependentPlugins[dependentPlugins.length - 1].uses.length - 1) {
// yup, last one!
self.log('service-worker', `${pluginConfig.name}: this was the last dependency of ${dependentPlugins[dependentPlugins.length - 1].name}`)
// we can now proceed to instantiate the last element of dependentPlugins
pluginConfig = dependentPlugins.pop()
continue
}
// it is not the last one, so there should be more dependency plugins to instantiate first
// before we can instantiate the last of element of dependentPlugins
// but that requires the full treatment, including checing the `uses` field for their configs
self.log('service-worker', `${pluginConfig.name}: not yet the last dependency of ${dependentPlugins[dependentPlugins.length - 1].name}`)
pluginConfig = false
// instantiate the plugin
let plugin = LibResilientPluginConstructors.get(pluginConfig.name)(self, pluginConfig)
self.log('service-worker', `${pluginConfig.name}: instantiated`)
// if pluginConfig is not false, rinse-repeat the plugin instantiation steps
// since we are dealing with the last element of dependentPlugins
} while ( (pluginConfig !== false) && (pluginConfig !== undefined) )
// do we have a stashed plugin that requires dependencies?
if (dependentPlugins.length === 0) {
// no we don't; so, this plugin goes directly to the plugin list
self.LibResilientPlugins.push(plugin)
// we're done here
self.log('service-worker', `${pluginConfig.name}: no dependent plugins, pushing directly to LibResilientPlugins`)
break;
}
}
// finally -- do we want to use MIME type guessing based on content?
// dealing with this at the very end so that we know we can safely set detectMimeFromBuffer
// and not need to re-set it back in case anything fails
if (config.useMimeSniffingLibrary === true) {
// we do not want to hit a NetworkError and end up using the default config
// much better to end up not using the fancy MIME type detection in such a case
try {
// we do! load the external lib
self.importScripts(`./lib/file-type.js`)
} catch (e) {
self.log('service-worker', `error when fetching external MIME sniffing library: ${e.message}`)
// at this point clearly there is at least one element in dependentPlugins
// so we can safely assume that the freshly instantiated plugin is a dependency
//
// in that case let's find the first empty spot for a dependency
let didx = dependentPlugins[dependentPlugins.length - 1].uses.indexOf(false)
// assign the freshly instantiated plugin as that dependency
dependentPlugins[dependentPlugins.length - 1].uses[didx] = plugin
self.log('service-worker', `${pluginConfig.name}: assigning as dependency (#${didx}) to ${dependentPlugins[dependentPlugins.length - 1].name}`)
// was this the last one?
if (didx >= dependentPlugins[dependentPlugins.length - 1].uses.length - 1) {
// yup, last one!
self.log('service-worker', `${pluginConfig.name}: this was the last dependency of ${dependentPlugins[dependentPlugins.length - 1].name}`)
// we can now proceed to instantiate the last element of dependentPlugins
pluginConfig = dependentPlugins.pop()
continue
}
if (typeof fileType !== 'undefined' && "fileTypeFromBuffer" in fileType) {
detectMimeFromBuffer = fileType.fileTypeFromBuffer
self.log('service-worker', 'loaded external MIME sniffing library')
} else {
self.log('service-worker', 'failed to load external MIME sniffing library!')
}
}
// it is not the last one, so there should be more dependency plugins to instantiate first
// before we can instantiate the last of element of dependentPlugins
// but that requires the full treatment, including checing the `uses` field for their configs
self.log('service-worker', `${pluginConfig.name}: not yet the last dependency of ${dependentPlugins[dependentPlugins.length - 1].name}`)
pluginConfig = false
// we're good!
return true;
// if pluginConfig is not false, rinse-repeat the plugin instantiation steps
// since we are dealing with the last element of dependentPlugins
} while ( (pluginConfig !== false) && (pluginConfig !== undefined) )
// exception? no bueno
} catch (e) {
// inform
self.log('service-worker', `error while executing config: ${e.message}`)
// cleanup after a failed config execution
self.LibResilientPluginConstructors = new Map(lrpcBackup)
self.LibResilientPlugins = new Array()
// we are not good
return false;
}
// finally -- do we want to use MIME type guessing based on content?
// dealing with this at the very end so that we know we can safely set detectMimeFromBuffer
// and not need to re-set it back in case anything fails
if (config.useMimeSniffingLibrary === true) {
// we do not want to hit a NetworkError and end up using the default config
// much better to end up not using the fancy MIME type detection in such a case
try {
// we do! load the external lib
self.importScripts(`./lib/file-type.js`)
} catch (e) {
self.log('service-worker', `error when fetching external MIME sniffing library: ${e.message}`)
}
if (typeof fileType !== 'undefined' && "fileTypeFromBuffer" in fileType) {
detectMimeFromBuffer = fileType.fileTypeFromBuffer
self.log('service-worker', 'loaded external MIME sniffing library')
} else {
self.log('service-worker', 'failed to load external MIME sniffing library!')
}
}
// we're good!
return true;
}
@ -530,7 +521,20 @@ let initServiceWorker = async () => {
config = {...self.LibResilientConfig, ...cdata}
// try executing the config
config_executed = executeConfig(config)
// we want to catch any and all possible errors here
try {
config_executed = executeConfig(config)
// exception? no bueno
} catch (e) {
// inform
self.log('service-worker', `error while executing config: ${e.message}`)
// cleanup after a failed config execution
self.LibResilientPluginConstructors = new Map(lrpcBackup)
self.LibResilientPlugins = new Array()
// we are not good
config_executed = false;
}
// if we're using the defaults, and yet loading of the config failed
// something is massively wrong
@ -552,15 +556,22 @@ let initServiceWorker = async () => {
// that is, if it comes from the "v1" cache...
if (use_cache === "v1") {
self.log('service-worker', `successfully loaded config.json; caching in cache: v1:verified`)
cacheConfigJSON(configURL, cresponse, 'v1:verified')
// or, was fetch()ed and valid (no caching if we're going with defaults, obviously)
await cacheConfigJSON(configURL, cresponse, 'v1:verified')
// we used the v1:verified cache; we should cache config.json into the v1 cache
// as that will speed things up a bit next time we need to load the service worker
} else if (use_cache === "v1:verified") {
self.log('service-worker', `successfully loaded config.json; caching in cache: v1`)
await cacheConfigJSON(configURL, cresponse, 'v1')
// or, was fetch()-ed and valid (no caching if we're going with defaults, obviously)
} else if ( (use_cache === undefined) && (cresponse !== false) ) {
self.log('service-worker', `successfully loaded config.json; caching in cache: v1, v1:verified`)
// we want to cache to both, so that:
// 1. we get the extra bit of performance from using the v1 cache that is checked first
// 2. but we get the verified config already in the v1:verified cache for later
cacheConfigJSON(configURL, await cresponse.clone(), 'v1')
cacheConfigJSON(configURL, cresponse, 'v1:verified')
await cacheConfigJSON(configURL, await cresponse.clone(), 'v1')
await cacheConfigJSON(configURL, cresponse, 'v1:verified')
}
// inform