Porównaj commity

...

7 Commity

2 zmienionych plików z 332 dodań i 143 usunięć

Wyświetl plik

@ -118,8 +118,12 @@ beforeAll(async ()=>{
* mocking caches.match()
* https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/match#browser_compatibility
*/
caches.match = async (url) => {
let cache = await caches.open('v1')
caches.match = async (url, options={}) => {
let cn = 'v1'
if ('cacheName' in options) {
cn = options.cacheName
}
let cache = await caches.open(cn)
return cache.match(url)
}
@ -210,14 +214,14 @@ beforeAll(async ()=>{
}
// wait for caching of a URL, looped up to `tries` times
window.waitForCacheAction = (url, action="add", tries=100) => {
window.waitForCacheAction = (url, cache_name, action="add", tries=100) => {
if (action != "add" && action != "remove") {
throw new Error('waitForCacheAction()\'s action parameter can only be "add" or "remove".')
}
console.log('*** WAITING FOR CACHE ACTION:', action, '\n - url:', url)
console.log(`*** WAITING FOR CACHE (${cache_name}) ACTION:`, action, '\n - url:', url)
return new Promise(async (resolve, reject)=>{
// get the cache object
let cache = await caches.open('v1')
let cache = await caches.open(cache_name)
// try to match until we succeed, or run out of tries
for (let i=0; i<tries; i++) {
// search the URL
@ -285,11 +289,18 @@ beforeEach(async ()=>{
// clear the caches
await caches
.has('v1')
.then(async (hasv1) => {
if (hasv1) {
.then(async (hasit) => {
if (hasit) {
await caches.delete('v1')
}
})
await caches
.has('v1:verified')
.then(async (hasit) => {
if (hasit) {
await caches.delete('v1:verified')
}
})
// make sure we're starting with a clean slate in LibResilientPluginConstructors
window.LibResilientPluginConstructors = new Map()
// keeping track of whether the SW got installed
@ -337,15 +348,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 = {
@ -368,6 +370,44 @@ describe('service-worker', async () => {
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when fetching config.json throws an exception", async () => {
window.fetch = spy(() => {
throw new Error('Testing exception')
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when fetching config.json returns undefined", async () => {
window.fetch = spy(() => {
return undefined;
})
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertEquals(typeof self.LibResilientConfig, "object")
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 10000)
assertEquals(self.LibResilientConfig.plugins, [{name: "fetch"},{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'fetch', 'cache'])
assertEquals(self.LibResilientConfig.normalizeQueryParams, true)
assertEquals(self.LibResilientConfig.useMimeSniffingLibrary, false)
assertSpyCalls(self.fetch, 1)
})
it("should use default LibResilientConfig values when config.json not valid JSON", async () => {
let mock_response_data = {
@ -488,7 +528,7 @@ describe('service-worker', async () => {
assertSpyCalls(self.fetch, 1)
})
it("should use config values from a valid fetched config.json file, caching it", async () => {
it("should use config values from a valid fetched config.json file, caching it in both caches (v1, v1:verified)", async () => {
let mock_response_data = {
data: JSON.stringify({loggedComponents: ['service-worker', 'cache'], plugins: [{name: "cache"}], defaultPluginTimeout: 5000, normalizeQueryParams: false, useMimeSniffingLibrary: true})
}
@ -510,7 +550,11 @@ describe('service-worker', async () => {
// if we don't make sure that the caching has completed, we will get an error.
// so we wait until config.json is cached, and use that to verify it is in fact cached
assertEquals(
await window.waitForCacheAction(window.location.origin + 'config.json'),
await window.waitForCacheAction(window.location.origin + 'config.json', 'v1'),
mock_response_data.data
);
assertEquals(
await window.waitForCacheAction(window.location.origin + 'config.json', 'v1:verified'),
mock_response_data.data
);
})
@ -762,20 +806,28 @@ describe('service-worker', async () => {
await window.getMockedResponse(config_url, {}, mock_response_data)
)
})
// service worker is a go!
await import("../../service-worker.js?" + window.test_id);
await self.dispatchEvent(new Event('install'))
await self.waitForSWInstall()
assertSpyCalls(self.fetch, 0)
assertEquals(typeof self.LibResilientConfig, 'object')
assertEquals(self.LibResilientConfig.defaultPluginTimeout, 5000)
assertEquals(self.LibResilientConfig.plugins, [{name: "cache"}])
assertEquals(self.LibResilientConfig.loggedComponents, ['service-worker', 'cache'])
assertSpyCalls(self.fetch, 0)
// cacheConfigJSON() is called asynchronously in the Service Worker,
// if we don't make sure that the caching has completed, we will get an error.
// so we wait until config.json is cached, and use that to verify it is in fact cached
assertEquals(
await window.waitForCacheAction(window.location.origin + 'config.json', 'v1:verified'),
mock_response_data.data
);
})
it("should use a stale cached valid config.json file without a fetch, then retrieve and cache a fresh config.json using the configured plugins", async () => {
it("should use a stale cached valid config.json file without a fetch, caching it in v1:verified cache, then retrieve and cache a fresh config.json using the configured plugins", async () => {
// this does not change
var config_url = window.location.origin + 'config.json'
@ -837,6 +889,14 @@ describe('service-worker', async () => {
assertSpyCalls(self.fetch, 0)
assertSpyCalls(resolveConfigFetch, 1)
// cacheConfigJSON() is called asynchronously in the Service Worker,
// if we don't make sure that the caching has completed, we will get an error.
// so we wait until config.json is cached, and use that to verify it is in fact cached
assertEquals(
await window.waitForCacheAction(window.location.origin + 'config.json', 'v1:verified'),
mock_response_data.data
);
// wait until verify the *new* config got cached
// running waitForCacheAdd only once might not be enough, as the cache
// already contained config.json!
@ -845,15 +905,15 @@ describe('service-worker', async () => {
// so as not to end up in a forever loop
for (let i=0; i<100; i++) {
// did we get the new config?
if (await window.waitForCacheAction(config_url) === mock_response_data2.data) {
if (await window.waitForCacheAction(config_url, 'v1') === mock_response_data2.data) {
// we did! we're done
return true;
}
}
fail('New config failed to cache, apparently!')
throw new Error('New config failed to cache, apparently!')
})
it("should use a stale cached valid config.json file without a fetch; invalid config.json retrieved using the configured plugins should not be cached", async () => {
it("should use a stale cached valid config.json file without a fetch, caching it in v1:verified cache; invalid config.json retrieved using the configured plugins should not be cached", async () => {
// this does not change
var config_url = window.location.origin + 'config.json'
@ -915,17 +975,25 @@ describe('service-worker', async () => {
assertSpyCalls(self.fetch, 0)
assertSpyCalls(resolveConfigFetch, 1)
// cacheConfigJSON() is called asynchronously in the Service Worker,
// if we don't make sure that the caching has completed, we will get an error.
// so we wait until config.json is cached, and use that to verify it is in fact cached
assertEquals(
await window.waitForCacheAction(window.location.origin + 'config.json', 'v1:verified'),
mock_response_data.data
);
// waiting for potential caching of the "new" config
for (let i=0; i<100; i++) {
// did we get the new config?
if (await window.waitForCacheAction(config_url) === mock_response_data2.data) {
if (await window.waitForCacheAction(config_url, 'v1') === mock_response_data2.data) {
// we did! that's a paddling!
fail('New config failed to cache, apparently!')
throw new Error('New config should not have been cached!')
}
}
})
it("should use a stale cached valid config.json file without a fetch; valid config.json retrieved using the configured plugins other than fetch should not be cached", async () => {
it("should use a stale cached valid config.json file without a fetch; invalid config.json retrieved using the configured plugins other than fetch should not be cached", async () => {
// this does not change
var config_url = window.location.origin + 'config.json'
@ -954,7 +1022,7 @@ describe('service-worker', async () => {
console.log(await resp.text())
})
// prepare the fresh invalid config request/response
// prepare the fresh valid config request/response
let mock_response_data2 = {
data: JSON.stringify({loggedComponents: ['service-worker', 'resolve-config', 'cache'], plugins: [{name: "resolve-config"}, {name: "cache"}], defaultPluginTimeout: 2000}),
headers: {
@ -990,9 +1058,9 @@ describe('service-worker', async () => {
// waiting for potential caching of the "new" config
for (let i=0; i<100; i++) {
// did we get the new config?
if (await window.waitForCacheAction(config_url) === mock_response_data2.data) {
if (await window.waitForCacheAction(config_url, 'v1') === mock_response_data2.data) {
// we did! that's a paddling
fail('New config failed to cache, apparently!')
throw new Error('New config failed to cache, apparently!')
}
}
})
@ -1302,7 +1370,7 @@ describe('service-worker', async () => {
// cached
assertEquals(
JSON.parse(
await window.waitForCacheAction(window.location.origin + 'test.json')),
await window.waitForCacheAction(window.location.origin + 'test.json', 'v1')),
{ test: "success" }
);
});
@ -1444,7 +1512,7 @@ describe('service-worker', async () => {
// let's see if it got added to cache
assertEquals(
JSON.parse(await window.waitForCacheAction(window.location.origin + 'test.json')),
JSON.parse(await window.waitForCacheAction(window.location.origin + 'test.json', 'v1')),
{ test: "success" }
);
});
@ -1560,7 +1628,7 @@ describe('service-worker', async () => {
// let's see if it got added to cache
assertEquals(
JSON.parse(await window.waitForCacheAction(window.location.origin + 'test.json')),
JSON.parse(await window.waitForCacheAction(window.location.origin + 'test.json', 'v1')),
{ test: "success" }
);
@ -1574,7 +1642,7 @@ describe('service-worker', async () => {
// let's see if it got removed from cache
assertEquals(
await window.waitForCacheAction(window.location.origin + 'test.json', "remove"),
await window.waitForCacheAction(window.location.origin + 'test.json', 'v1', "remove"),
undefined
);
});

Wyświetl plik

@ -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,
@ -242,13 +237,13 @@ let verifyConfigData = (cdata) => {
* configURL - url of the config file
* cresponse - response we're caching
*/
let cacheConfigJSON = async (configURL, cresponse) => {
let cacheConfigJSON = async (configURL, cresponse, use_cache) => {
try {
var cache = await caches.open('v1')
var cache = await caches.open(use_cache)
await cache.put(configURL, cresponse)
self.log('service-worker', 'config cached.')
self.log('service-worker', `config cached in cache: ${use_cache}.`)
} catch(e) {
self.log('service-worker', `failed to cache config: ${e}`)
self.log('service-worker', `failed to cache config in cache ${use_cache}: ${e}`)
}
}
@ -277,93 +272,23 @@ let getConfigJSON = async (cresponse) => {
}
// flag signifying the SW has been initialized already
var initDone = false
// load the plugins
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
/**
* execute on the configuration
*
* load plugin modules, making constructors available
* cycle through the plugin config instantiating plugins and their dependencies
*/
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]
// we want to catch any and all possible errors here
try {
// we'll need this later
var cresponse = null
// get the config
//
// self.registration.scope contains the scope this service worker is registered for
// so it makes sense to pull config from `config.json` file directly under that location
try {
// config.json URL
var configURL = self.registration.scope + "config.json"
// get the config data from cache
cresponse = await caches.match(configURL)
var cdata = await getConfigJSON(cresponse)
// did it work?
if (cdata != false) {
// we need to know if the config was already cached
self.log('service-worker', `valid config file retrieved from cache.`)
// cache failed to deliver
} else {
// try fetching directly from the main domain
self.log('service-worker', `config file not found in cache, fetching.`)
var cresponse = await fetch(configURL)
cdata = await getConfigJSON(cresponse)
// did that work?
if (cdata != false) {
// cache it, asynchronously
cacheConfigJSON(configURL, cresponse)
} else {
// we ain't got nothing useful -- just set cdata to an empty object
cdata = {}
self.log('service-worker', 'ignoring invalid config, using defaults.')
}
}
// merge configs
self.LibResilientConfig = {...self.LibResilientConfig, ...cdata}
self.log('service-worker', 'config loaded.')
} catch (e) {
self.log('service-worker', 'config loading failed, using defaults; error:', e)
}
// first let's deal with the easy part -- do we want to use MIME type guessing based on content?
if (self.LibResilientConfig.useMimeSniffingLibrary === true) {
// we do! load the external lib
self.importScripts(`./lib/file-type.js`)
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!')
}
}
// create the LibResilientPluginConstructors map
// the global... hack is here so that we can run tests; not the most elegant
// 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
// 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()
let dependentPlugins = new Array()
// only now load the plugins (config.json could have changed the defaults)
while (pluginsConfig.length > 0) {
@ -383,7 +308,7 @@ let initServiceWorker = async () => {
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) {
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])
@ -458,12 +383,192 @@ let initServiceWorker = async () => {
}
// 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;
// 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;
}
}
// flag signifying if the SW has been initialized already
var initDone = false
// load the plugins
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 {
// we'll need this later
let cresponse = null
let config = null
// self.registration.scope contains the scope this service worker is registered for
// so it makes sense to pull config from `config.json` file directly under that location
// TODO: this should probably be configurable somehow
let configURL = self.registration.scope + "config.json"
// clean version of LibResilientPlugins
// NOTICE: this assumes LibResilientPlugins is not ever used *before* this runs
// NOTICE: this assumption seems to hold currently, but noting for clarity
self.LibResilientPlugins = new Array()
// create the LibResilientPluginConstructors map
// the global... hack is here so that we can run tests; not the most elegant
// TODO: find a better way
self.LibResilientPluginConstructors = self.LibResilientPluginConstructors || new Map()
// point backup of LibResilientPluginConstructors, in case we need to roll back
// this is used during cleanup in executeConfig()
// TODO: handle in a more elegant way
let lrpcBackup = new Map(self.LibResilientPluginConstructors)
// caches to try: temp cache, main cache
let available_caches = ['v1', 'v1:verified']
// keep track
let config_executed = false
let use_cache = false
do {
// init config data var
let cdata = false
// where are we getting the config.json from this time?
// we eitehr get a string (name of a cache), or undefined (signifying need for fetch())
use_cache = available_caches.shift()
try {
// cache?
if ( typeof use_cache === 'string' ) {
self.log('service-worker', `retrieving config.json from cache: ${use_cache}.`)
cresponse = await caches.match(configURL, {cacheName: use_cache})
// bail early if we got nothing
if (cresponse === undefined) {
self.log('service-worker', `config.json not found in cache: ${use_cache}.`)
continue
}
// regular fetch
// (we don't have any plugin transports at this point, obviously...)
} else {
self.log('service-worker', `retrieving config.json using fetch().`)
cresponse = await fetch(configURL)
}
// extract the JSON and verify it
cdata = await getConfigJSON(cresponse)
// exception? no bueno!
} catch(e) {
cdata = false
self.log('service-worker', `exception when trying to retrieve config.json: ${e.message}`)
}
// do we have anything to work with?
if (cdata === false) {
// cached config.json was invalid; no biggie, try another cache, or fetch()
if (typeof use_cache === "string") {
self.log('service-worker', `cached config.json is not valid; cache: ${use_cache}`)
continue
// if that was a fetch() config, we need to run to defaults!
} else {
self.log('service-worker', `fetched config.json is not valid; using defaults`)
// set an empty object, this will in effect deploy pure defaults
cdata = {}
// clear cresponse which will indicate later on
// that we did not use data from any response, cache nor fetch
cresponse = false
}
// we good!
} else {
self.log('service-worker', `valid-looking config.json retrieved.`)
}
// merge configs
config = {...self.LibResilientConfig, ...cdata}
// try executing the config
config_executed = executeConfig(config)
// if we're using the defaults, and yet loading of the config failed
// something is massively wrong
if ( ( cresponse === false ) && ( config_executed === false ) ) {
// this really should never happen
throw new Error('Failed to load the default config; this should never happen!')
}
// NOTICE: endless loop alert?
// NOTICE: this is not an endless loop because cresponse can only become false if we're using the default config
// NOTICE: and that single case is handled directly above
} while ( ! config_executed )
// we're good
self.LibResilientConfig = config
self.log('service-worker', 'config loaded.')
// we're good, let's cache the config as verified if we need to
// 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)
} 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')
}
// inform
self.log('service-worker', `service worker initialized.\nstrategy in use: ${self.LibResilientPlugins.map(p=>p.name).join(', ')}`)
initDone = true;
// regardless how we got the config file, if it's older than 24h...
if ( (new Date()) - Date.parse(cresponse.headers.get('date')) > 86400000) {
if ( ( cresponse !== false ) && (new Date()) - Date.parse(cresponse.headers.get('date')) > 86400000) {
// try to get it asynchronously through the plugins, and cache it
self.log('service-worker', `config.json stale, fetching through plugins`)
@ -475,22 +580,28 @@ let initServiceWorker = async () => {
var cdata = await getConfigJSON(cresponse)
// did that work?
if (cdata != false) {
if (cdata === false) {
// we got a false in cdata, that means it probably is invalid (or the fetch failed)
self.log('service-worker', 'config.json loaded through transport other than fetch seems invalid, ignoring')
return false
// otherwise, we good for more in-depth testing!
} else {
// if we got the new config.json via a method *other* than plain old fetch(),
// we will not be able to use importScripts() to load any pugins that have not been loaded already
//
// NOTICE: this *only* checks if we have all the necessary plugin constructors already available
// which signifies that relevant code has been successfully loaded; but there are other failure modes!
if (cresponse.headers.get('x-libresilient-method') != 'fetch') {
// go through the plugins in the new config and check if we already have their constructors
// i.e. if the plugin scripts have already been loaded
// FIXME: this does not currently dive into dependencies!
// FIXME: https://gitlab.com/rysiekpl/libresilient/-/issues/48
for (let p in cdata.plugins) {
var pname = cdata.plugins[p].name
let currentPlugin = cdata.plugins.shift()
do {
// plugin constructor not available, meaning: we'd have to importScripts() it
// but we can't since this was not retrieved via fetch(), so we cannot assume
// that the main domain of the website is up and available
// that the main domain of the website is up and available
//
// if we cache this newly retrieved config.json, next time the service worker gets restarted
// we will end up with an error while trying to run importScripts() for this plugin
@ -499,16 +610,26 @@ let initServiceWorker = async () => {
// if the main domain is not available, this would mean the website stops working
// even though we *were* able to retrieve the new config.json via plugins!
// so, ignoring this new config.json.
if (!LibResilientPluginConstructors.has(pname)) {
self.log('service-worker', `config.json was retrieved through plugins other than fetch, but specifies additional plugins (${pname}); ignoring.`)
if (!LibResilientPluginConstructors.has(currentPlugin.name)) {
self.log(
'service-worker',
`warning: config.json loaded through transport other than fetch, but specifies not previously loaded plugin: "${currentPlugin.name}"\nignoring the whole config.json.`)
return false;
}
}
// push any dependencies into the array, at the very front
// thus gradually flattening the config
if ("uses" in currentPlugin) {
cdata.plugins.unshift(...currentPlugin.uses)
}
// get the next plugin to check
currentPlugin = cdata.plugins.shift()
} while ( (currentPlugin !== false) && (currentPlugin !== undefined) )
}
self.log('service-worker', `config.json successfully retrieved through plugins; caching.`)
// cache it, asynchronously
cacheConfigJSON(configURL, cresponse)
self.log('service-worker', `valid config.json successfully retrieved through plugins; caching.`)
// cache it, asynchronously, in the temporary cache
// as the config has not been "execute-tested" yet
cacheConfigJSON(configURL, cresponse, "v1")
}
})
}
@ -517,7 +638,7 @@ let initServiceWorker = async () => {
// we only get a cryptic "Error while registering a service worker"
// unless we explicitly print the errors out in the console
console.error(e)
// we got an error while initializing the plugins
// we got an error while initializing the service worker!
// better play it safe!
self.registration.unregister()
throw e