Using multiple javascript service workers at the same domain but different folders

digitai picture digitai · Feb 26, 2017 · Viewed 8k times · Source

My website offers an array of web apps for each client. Each client has a different mix of apps that can be used.

Each web app is a hosted in a different folder.

So I need to cache for each client only it's allowed web apps instead of caching all the apps, many of them he the user, will not use at all.

I naively created a global service worker for the shell of the website and custom named service worker for each folder or app.

However I noticed that after the first service worker, say sw_global.js service worker registers, installs, activates, fetches succesfully and creates a cache named cache-global, the second service worker, say sw_app1,js, which creates it's own cache cache-app1, clears all cache-global.

How can I use custom service worker for each folder?. Please keep in mind that each folder is a microservice or a web app which is not necesarilly allowed for all users, so it's imperative to not cache all content from a unique service worker.

Actually I code on vanilla.js, no angular, no react, no vue, no nada.

Answer

Jeff Posnick picture Jeff Posnick · Feb 27, 2017

There's two things to keep in mind here:

  1. It's possible to register an arbitrary number of service workers for a given origin, as long as each service worker has its own unique scope. It sounds like you're doing this already, registering both a top-level service worker with a scope of / and then additional service workers scoped to the paths from which each of your independent web apps run.

  2. The Cache Storage API (and other storage mechanisms, like IndexedDB) are shared throughout the entire origin, and by default there's no "sharding" or namespace segregation.

Taking those two facts together, my guess is that what's happening is that you have some cache cleanup code in the activate handler of one of the service workers that's scoped to a specific web app, and that code retrieves a list of current cache names and deletes any caches that it thinks is out of date. That's a common thing to do in a service worker, but you can run into trouble if you don't take into account the fact that caches.keys() will return a list of all the caches in your origin, even the caches that were created by other service worker instances.

Assuming that's what's going on here, a model that I'd recommend following is to include the value of registration.scope as part of the cache name when you create your caches. When it comes time to delete old cache entries in your activate handler, it's easy to make sure that you're only cleaning up caches that your specific service worker is supposed to manage by filtering out those that don't match registration.scope.

E.g., something like this:

const CACHE_VERSION = 'v2';
const CACHE_NAME = `${registration.scope}!${CACHE_VERSION}`;

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      // Add entries to the cache...
    })
  );
});

self.addEventListener('activate', (event) => {
  const cleanup = async () => {
    const cacheNames = await caches.keys();
    const cachesToDelete = cachesNames.filter(
        (cacheName) => cacheName.startsWith(`${registration.scope}!`) &&
                       cacheName !== CACHE_NAME);
    await Promise.all(cachesToDelete.map(
      (cacheName) => caches.delete(cacheName)));
  };

  event.waitUntil(cleanup());
});