From 4bf42c1c93cc011a4709da297e397963b41a3ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=27rysiek=27=20Wo=C5=BAniak?= Date: Mon, 29 Jan 2024 23:22:15 +0000 Subject: [PATCH] preparing for fully revertable config deployment: executeConfig() (ref. #48) --- .../service-worker/service-worker.test.js | 9 - service-worker.js | 223 ++++++++++-------- 2 files changed, 119 insertions(+), 113 deletions(-) diff --git a/__tests__/service-worker/service-worker.test.js b/__tests__/service-worker/service-worker.test.js index 44828f5..08a2264 100644 --- a/__tests__/service-worker/service-worker.test.js +++ b/__tests__/service-worker/service-worker.test.js @@ -337,15 +337,6 @@ describe('service-worker', async () => { window.test_id = 0 - it("should set-up LibResilientPlugins", async () => { - // we cannot import the same module multiple times: - // https://github.com/denoland/deno/issues/6946 - // - // ...so we have to use a query-param hack, sigh - await import("../../service-worker.js?" + window.test_id); - assert(self.LibResilientPlugins instanceof Array) - }) - it("should use default LibResilientConfig values when config.json is missing", async () => { let mock_response_data = { diff --git a/service-worker.js b/service-worker.js index 17ec8b5..7928148 100644 --- a/service-worker.js +++ b/service-worker.js @@ -2,11 +2,6 @@ * LibResilient Service Worker. */ -// initialize the LibResilientPlugins array -if (!Array.isArray(self.LibResilientPlugins)) { - self.LibResilientPlugins = new Array() -} - // initialize the LibResilientConfig array // // this also sets some sane defaults, @@ -277,6 +272,118 @@ let getConfigJSON = async (cresponse) => { } +/** + * execute on the configuration + * + * load plugin modules, making constructors available + * cycle through the plugin config instantiating plugins and their dependencies + */ +let executeConfig = (pluginsConfig) => { + + // clean version of LibResilientPlugins + // NOTICE: this assumes LibResilientPlugins is not ever used *befure* this runs + // NOTICE: this assumption seems to hold currently, but noting for clarity + self.LibResilientPlugins = new Array() + + // this is the stash for plugins that need dependencies instantiated first + let dependentPlugins = new Array() + + // only now load the plugins (config.json could have changed the defaults) + while (pluginsConfig.length > 0) { + + // 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`) + } + + // 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) + } + 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 + + // 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) ) + + } + +} + + // flag signifying the SW has been initialized already var initDone = false @@ -363,106 +470,14 @@ let initServiceWorker = async () => { // TODO: find a better way self.LibResilientPluginConstructors = self.LibResilientPluginConstructors || new Map() - // copy of the plugins config - // we need to work on it so that self.LibResilientConfig.plugins remains unmodified + // point backup of LibResilientPluginConstructors, in case we need to roll back + // TODO: handle in a more elegant way + let lrpcBackup = new Map(self.LibResilientPluginConstructors) + + // working on a copy of the plugins config so that + // self.LibResilientConfig.plugins remains unmodified // in case we need it later (for example, when re-loading the config) - var pluginsConfig = [...self.LibResilientConfig.plugins] - - // this is the stash for plugins that need dependencies instantiated first - var dependentPlugins = new Array() - - // only now load the plugins (config.json could have changed the defaults) - while (pluginsConfig.length > 0) { - - // 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`) - } - - // 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 (var 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) - } - 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 - - // 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) ) - - } + executeConfig([...self.LibResilientConfig.plugins]) // inform self.log('service-worker', `service worker initialized.\nstrategy in use: ${self.LibResilientPlugins.map(p=>p.name).join(', ')}`)