Search code examples
javascriptfirefoxservice-workerworkbox

How to hard refresh Firefox after Service Worker update?


I have implemented the code below to clear service worker cache and reloads - after the user has accepted update of the service worker. The code works well in Chrome and Edge, but Firefox will not reload the page. Firefox will keep asking to install the same version until I hard refresh (shift reload) the page.

service-worker-base.js

// Imports

const CACHE_DYNAMIC_NAME = 'DEBUG-035'

setCacheNameDetails({ prefix: 'myApp', suffix: CACHE_DYNAMIC_NAME });

// Cache then network for css
registerRoute(
  '/dist/main.css',
  new StaleWhileRevalidate({
    cacheName: `${CACHE_DYNAMIC_NAME}-css`,
    plugins: [
      new ExpirationPlugin({
        maxEntries: 10, // Only cache 10 requests.
        maxAgeSeconds: 60 * 60 * 24 * 7 // Only cache requests for 7 days
      })
    ]
  })
)

// Cache then network for images
//...

// Use a stale-while-revalidate strategy for all other requests.
setDefaultHandler(new StaleWhileRevalidate())

precacheAndRoute(self.__WB_MANIFEST)

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting()
  }
})

// Clear cache before installing new service worker
self.addEventListener('activate', (event) => {
  var cachesToKeep = ['none'];
  event.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(keyList.map((key) => {
        if (cachesToKeep.indexOf(key) === -1) {
          console.log('Delete cache', key)
          return caches.delete(key);
        }
      }));
    })
  );

  event.waitUntil(self.clients.claim());
});

//...

app.js

const enableServiceWorker = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'qa'
const serviceWorkerAvailable = ('serviceWorker' in navigator) ? true : false
if (enableServiceWorker && serviceWorkerAvailable) {

  const wb = new Workbox('/service-worker.js');
  let registration;

  const showSkipWaitingPrompt = (event) => {
    if (window.confirm("New version available! Refresh?")) {
      wb.addEventListener('controlling', (event) => {
        window.location.reload();
      });

      console.log('registration', registration) //<-- LINE 13 
      // In Chrome and Edge this logs a service worker registration object
      // In Firefox, this is undefined !!? 

      if (registration && registration.waiting) {
        messageSW(registration.waiting, {type: 'SKIP_WAITING'});
      }
    }
  }

  // Add an event listener to detect when the registered service worker has installed but is waiting to activate.
  wb.addEventListener('waiting', showSkipWaitingPrompt);
  wb.addEventListener('externalwaiting', showSkipWaitingPrompt);

  wb.register().then((r) => {
    registration = r
    console.log('Service worker registered', registration) //<-- LINE 23
  }).catch(registrationError => {
    console.error('Service worker error', registrationError )
  })
}

// Install prompt event handler
export let deferredPrompt
window.addEventListener('beforeinstallprompt', (event) => {
  event.preventDefault() // Prevent Chrome 76 and later from showing the mini-infobar
  deferredPrompt = event // Stash the event so it can be triggered later.
 
  // Update UI notify the user they can add to home screen
  try{
    showInstallPromotion()
  }catch(e){
    // console.log('showInstallPromotion()', e)
  }
})
window.addEventListener('appinstalled', (event) => {
  console.log('a2hs installed')
})

In Firefox dev-tools I can see the new service worker precache, but all other cache belongs to previous version. After shift-reload the new service worker gets "fully activated".

How can I get Firefox to hard reload the page automatically after new service worker install?

UPDATE: It seems like Firefox is missing a handle to the service worker on line 13 of app-js.

UPDATE: Console output indicates that the code sequence differs between browsers?

Chrome / Edge

registration > ServiceWorkerRegistration {installing: null, waiting: ServiceWorker, active: ServiceWorker, navigationPreload: NavigationPreloadManager, scope: "http://127.0.0.1:8080/", …} app.js:13 
**PAGE RELOAD***
Service worker registered ServiceWorkerRegistration {installing: null, waiting: null, active: ServiceWorker, navigationPreload: NavigationPreloadManager, scope: "http://127.0.0.1:8080/", …} app.js:23

Firefox

registration undefined app.js:13:14
Service worker registered > ServiceWorkerRegistration { installing: null, waiting: ServiceWorker, active: ServiceWorker, scope: "http://127.0.0.1:8080/", updateViaCache: "imports", onupdatefound: null, pushManager: PushManager } app.js:23:12

Kind regards /K


Solution

  • I created a special case since Firefox seems to install the new service-worker differently from chromium (does not have a handle to the service-worker registration on line 13)

    When the new service worker is waiting showSkipWaitingPrompt gets triggered and

    1. in Chromium the service-worker registration is ready ---> we call SKIP_WAITING --> the browser reloads and replaces the service worker
    2. in Firefox the service-worker registration handle is not accessible yet --> we cannot call SKIP_WAITING

    The solution, for me, was to add the below line in the registration. This tells Firefox to skip waiting when the new service-worker is in waiting state and we have a registration handle.

     wb.register().then((r) => {
        registration = r
        if(registration.waiting){ mySkipWaitingNow() } // Fix for firefox
        ...
    

    The mySkipWaitingNow() tells the service-worker to SKIP_WAITING without prompting the user.

    This will never trigger in Chrome/Edge since the browser reloads in showSkipWaitingPrompt() - see point 1 above.

    To prevent a possible eternal loop I also created a global variable skipWaitingConfirmed that gets set in showSkipWaitingPrompt() and checked in mySkipWaitingNow().

    /K