Updating the configuration during disruption

If a website is experiencing an outage, but LibResilient is deployed on it and is working correctly for the visitors whose browsers were able to install and activate it before the disruption started, it is possible to update LibResilient configuration using any alternative transport plugins available.

It is not, however, possible to load plugins that have not been previously loaded based on the previous config.json, as this would require a call to importScripts() and thus: a direct HTTPS request to the original domain.

ServiceWorker API and importScripts()

The Service Worker API allows for updating the service worker script itself and any imported scripts (via importScripts()) only via a direct HTTPS request to the original domain.

LibResilient expects configuration in a config.json file, not in a script, so that it can be retrieved and used without a call to importScripts(). This allows updating the configuration during disruptions. The importScripts() limitation still affects plugin scripts, however: it is not possible to load new plugin scripts while the original website is experiencing disruption or outage.

LibResilient’s Service Worker lifecycle

LibResilient’s Service Worker needs the config.json configuration script every time it is installed or re-started.

Service Worker installation happens upon loading the website for the first time. Re-start of the Service Worker can happen at any time — it’s up to the browser to decide when to stop a Service Worker. This usually happens after all relevant tabs are closed, or some time after the last request even if the tabs are open. A previously installed Service Worker is started whenever there is an event for it to handle; in case of LibResilient that will almost always be a fetch event.

When a Service Worker is stopped, all context (variables, state, etc) is lost. Only the Service Worker script (in our case, service-worker.js) and any scripts imported in it using importScripts() remain cached, along with other contents of the cache (and other WebAPIs that are not relevant here).

For LibResilient this means that when an already installed, but stopped, Service Worker gets started again, all plugin initialization needs to happen from scratch. And for that we have to have the config.json available.

Initial loading of the config file

When installing or handling any kind of event, the LibResilient Service Worker script first tries to retrieve the config.json file. The first place to look is the cache. If there is a valid config.json available there, it is used to configure and initialize the plugins.

If there is no config.json available in cache, the only way to get it at this stage (as plugins are not configured yet) is a regular HTTPS fetch() request to the original domain. If that works, the retrieved config.json is cached for future use (that is, next time when the Service Worker has been stopped and is being started again) and used to configure and initialize the plugins.

If there is no config.json available in cache, and the fetch() request does not produce a valid config.json either, LibResilient falls back to the default config (that is, a pretty trivial set-up using fetch and cache). At this stage LibResilient Service Worker can start handling requests generated by the open tabs.

Updating the config file

After configuring and initializing the plugins, LibResilient checks if the cached config.json (if available) is older than 24h, based on the Date: header in the response. If it is, it is considered stale, and a new background request for config.json is started, this time using the now available already-initialized plugin infrastructure. At this stage, if alternative transport plugins are configured, updated config.json can be retrieved even if the original website is experiencing disruptions or downtime.

However, due to the importScripts() limitation, if a config.json file is retrieved using alternative transport plugins (not via the fetch plugin), it is expected not to introduce plugins that have not been used before — only to change the configuration of already-loaded plugins. If a plugin that has not been loaded beforehand is present, that config.json file is ignored and discarded.

The reasoning behind this is that at this stage we do have a working configuration, as we were able to retrieve config.json even though the original website is not available. So it’s better to keep using that proven configuration, rather than risk having to call importScripts() for a new plugin, which might fail due to original website apparently not being available.