reactjsservice-workerprogressive-web-appssw-precachesw-toolbox

Service Worker skipWaiting unable to activate currently waiting SW


Description:

We are using sw precache for caching the scripts before hand, hence to update the scripts we are giving reload option, for that we are listening the worker message to skip waiting the newly install service worker for unknown reason we are not getting correct

importScript

// GETTING OLD SW reference (self) and NOT getting newly installed SW reference

self.addEventListener('message', function(event) {
    *// not working*
    self.skipWaiting();
});



// But if we put skipWaiting() in 'install' listener 
// it is getting correct new SW reference and working correctly

self.addEventListener('install', function(event) {
    // self.skipWaiting();
});

SW registration

if('serviceWorker' in window.navigator) {
      window.addEventListener('load', function() {
        window.navigator.serviceWorker.register("/serviceWorker.js").then(function(registration) {
          console.log("ServiceWorker registration successful with scope: ", registration);
          registration.onupdatefound = function() {
            console.log('NEW WILD WORKER HAS SPAWNED.!', registration);
            var installedWorker = registration.installing;
            installedWorker.onstatechange = function() {
              if (installedWorker.state === 'installed') {
                if (navigator.serviceWorker.controller) {
                  console.log('Updated content is available RELOAD!', navigator.serviceWorker.controller);
                  var el = document.getElementById('feature');
                  el.style['display'] = 'block';
                }
              }
            }
          }
        }).catch(function(error) {
          console.error("ServiceWorker registration failed: ", error);
        });
      });
      window.navigator.serviceWorker.addEventListener('controllerchange', function() {
        console.log('SERVICE WORKER UPDATED');
      });
    }

webpack config

new SWPrecacheWebpackPlugin({
        cacheId: 'pwa',
        filename: 'serviceWorker.js',
        staticFileGlobsIgnorePatterns: [/\.map$/, /\.json$/, /_nch\.[0-9a-z]+\.[js, css]+/g, /webpackManifest\.[0-9a-z]+\.js/g, /.DS_Store\.[0-9a-z]+/g],
        importScripts: ['offline/offline.1a2b3c4df1.js'],
        dontCacheBustUrlsMatching: /./,
        minify: false,
        skipWaiting: false,
        runtimeCaching: [ {
          urlPattern: /_nch\.[0-9a-z]+\.[js, css]+/g,
          handler: 'fastest',
          options: {
            cache: {
              name: 'jd-internal-script',
              maxEntries: 10,
            },
          },
        }, {
          urlPattern: /webpackManifest\.[0-9a-z]+\.js/g,
          handler: 'networkFirst',
          options: {
            cache: {
              name: 'jd-root-doc',
            },
          },
        }],
      }),

Solution

  • The best documentation for skipWaiting() can be found at https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle#skip_the_waiting_phase

    You can either call it unconditionally in the install handler, or follow the model that you seem to be doing, which is to listen for a message event and call skipWaiting() conditionally.

    If you go the conditional route, then you should modify your client page's code to properly detect when the service worker you're registering enters the waiting state, and give the user the option of interacting with the page in a way that results in a corresponding postMessage() to tell the service worker to skipWaiting(). Based on what you're saying, you've tried this, but it looks like you're sending the message to the wrong service worker instance.

    Here's what you page's code should look like:

    // On your page:
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', function() {
        navigator.serviceWorker.register('service-worker.js').then(function(reg) {
          reg.onupdatefound = function() {
            var newSW = reg.installing;
            newSW.onstatechange = function() {
              if (newSW.state === 'waiting') {
                // This assumes there's a button with id='skip-waiting-button' that
                // users should click to get the new SW to activate immediately.
                var button = document.querySelector('#skip-waiting-button');
                button.addEventListener('click', function() {
                  newSW.postMessage('skipWaiting');
                });
                // Assume that 'display' is 'none' initially.
                button.style.display = 'inline';
              }
              // Handle whatever other SW states you care about, like 'active'.
            };
          };
        })
      });
    }
    
    // In your service worker:
    self.addEventListener('message', event => {
      if (event.data === 'skipWaiting') {
        self.skipWaiting();
      }
    });