Search code examples
service-workerworkbox

Service worker add files from API call to precache


To enable my app running offline. During installation the service worker should:

  1. fetch a list of URLs from an async API
  2. reformat the response
  3. add all URLs in the response to the precache

For this task I use Googles Workbox in combination with Webpack.

The problem: While the service worker successfully caches all the Webpack assets (which tells me that the workbox basically does what it should), it does not wait for the async API call to cache the additional remote assets. They are simply ignored and neither cached nor ever fetched in the network.

Here is my service worker code:

importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.1.0/workbox-sw.js');

workbox.skipWaiting();
workbox.clientsClaim();
    
self.addEventListener('install', (event) => {
  const precacheController = new 
  workbox.precaching.PrecacheController();
  const preInstallUrl = 'https://www.myapiurl/Assets';
  event.waitUntil(fetch(preInstallUrl)
    .then(response => response.json()
    .then((Assets) => {
      Object.keys(Assets.data.Assets).forEach((key) => {
        precacheController.addToCacheList([Assets.data.Assets[key]]);
      });
    })
  );
});

self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
    
workbox.routing.registerRoute(/^.*\.(jpg|JPG|gif|GIF|png|PNG|eot|woff(2)?|ttf|svg)$/, workbox.strategies.cacheFirst({ cacheName: 'image-cache', plugins: [new workbox.cacheableResponse.Plugin({ statuses: [0, 200] }), new workbox.expiration.Plugin({ maxEntries: 600 })] }), 'GET');

And this is my webpack configuration for the workbox:

new InjectManifest({
  swDest: 'sw.js',
  swSrc: './src/sw.js',
  globPatterns: ['dist/*.{js,png,html,css,gif,GIF,PNG,JPG,jpeg,woff,woff2,ttf,svg,eot}'],
  maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
})

Solution

  • I realised my mistake. I hope this helps others as well. The problem was that I did not call precacheController.install() manually. While this function will be executed automatically it will not wait for additional precache files that are inserted asynchronously. This is why the function needs to be called after all the precaching happened. Here is the working code:

    importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.1.0/workbox-sw.js');
    
    workbox.skipWaiting();
    workbox.clientsClaim();
    
    const precacheController = new workbox.precaching.PrecacheController();
    
    // Hook into install event
    self.addEventListener('install', (event) => {
      // Get API URL passed as query parameter to service worker
      const preInstallUrl = new URL(location).searchParams.get('preInstallUrl');
    
      // Fetch precaching URLs and attach them to the cache list
      const assetsLoaded = fetch(preInstallUrl)
        .then(response => response.json())
        .then((values) => {
          Object.keys(values.data.Assets).forEach((key) => {
            precacheController.addToCacheList([values.data.Assets[key]]);
          });
        })
        .then(() => {
          // After all assets are added install them
          precacheController.install();
        });
      event.waitUntil(assetsLoaded);
    });
    
    self.__precacheManifest = [].concat(self.__precacheManifest || []);
    workbox.precaching.suppressWarnings();
    workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
    
    workbox.routing.registerRoute(/^.*\.(jpg|JPG|gif|GIF|png|PNG|eot|woff(2)?|ttf|svg)$/, workbox.strategies.cacheFirst({ cacheName: 'image-cache', plugins: [new workbox.cacheableResponse.Plugin({ statuses: [0, 200] }), new workbox.expiration.Plugin({ maxEntries: 600 })] }), 'GET');