5

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',
            },
          },
        }],
      }),
Anurag Singh
  • 727
  • 6
  • 10
  • 27

2 Answers2

13

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();
  }
});
Jeff Posnick
  • 53,580
  • 14
  • 141
  • 167
  • Thanks for making this work. Can i shift to Workbox for service workers implementation, because currenty i m using sw-precache and sw-toolbox inorder to achive this. So it would be helpful if you can provide some resources regarding Workbox implementation using webpack. – Anurag Singh Sep 26 '17 at 04:59
  • installingWorker.onstatechange in this we get error installingWorker is undefined so in place of it newSW need to be used ? – Anurag Singh Sep 26 '17 at 08:24
  • 1
    newSW.state = 'waiting' this does not satisfy instead of this we get newSW.state = 'installed'. Hence we are unable to tap waiting state. – Anurag Singh Sep 26 '17 at 09:46
  • Yes, the code should have been `newSW`. I've updated it. There shouldn't be any situation in which a newly registered SW stays indefinitely in the `installed` state. It should either transition to `waiting` (if there's another SW and you don't call `skipWaiting()` or `active` (if there is no other SW, or if you do call `skipWaiting()`). – Jeff Posnick Sep 26 '17 at 15:35
  • Its in the installed state. The state changes to waiting if i do a refresh then only waiting state is observed in the newSW.onstatechange = () => { ... .. }. Can you provide some hint what might go wrong so that the registered SW state indefinitely in the installed state. Although in the browser console the new SW is in waiting state. – Anurag Singh Sep 26 '17 at 19:06
  • 1
    The 'waiting' state cannot be found in the typescript definitions: `type ServiceWorkerState = "installing" | "installed" | "activating" | "activated" | "redundant";` Does that even exist? – Alex C Feb 05 '19 at 14:43
  • 1
    @AlexC did you figure this out? – itaintme May 29 '19 at 08:10
  • same I am also not getting newSW.state as 'waiting', all I get is 'installing' state change that's it. – Nishant Desai Aug 04 '19 at 15:31
  • As @AlexC points out, there is not 'waiting' state according to the page Jeff is linking to: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#handling_updates – REJH Mar 21 '20 at 07:44
  • @JeffPosnick I am facing a lot of issues making sure all users have latest version of app as soon as possible. I use ```skipWaiting()``` at callback of onstatechange of installingWorker but from time to time ```skipWaiting()``` do not triggered! How can I be sure that it is not happening again? – Mehrnoosh Jul 16 '21 at 10:00
0

So here's what I think... Your new service worker is still waiting.. hence the event listener is still on your older service worker.. any messages you send will still be caught by the older sw(as the new one is waiting).

In case your only case is to get the new sw to act, you can simple refresh your page instead of sending the message

prateekbh
  • 273
  • 1
  • 6
  • But if i do that then cache first strategy will not work because the webpackManifest and doc are cached at run time because of dynamic doc generated by express. In that case i have to switch to network first strategy. – Anurag Singh Sep 22 '17 at 14:19
  • Are we talking about bundles here or APIs or what exactly? – prateekbh Sep 22 '17 at 17:53
  • Its about bundles only, When the client make a request for doc then a doc is sent by the server, then as the doc is parsed the service worker is downloaded and service worker gets installed and activates. After that it pre-caches those resource ( vendor etc.) . Now if i make some changes to mine script and deploy mine new build. Then when the next time client hit the server it finds a change in service worker file and same get downloaded and installed and goes to waiting state, so now i display a pop message && as user clicks i call self.skipwaiting and then reload page – Anurag Singh Sep 24 '17 at 18:51
  • what happens if you dont call skipWaiting and just reload the page? – prateekbh Sep 25 '17 at 14:08
  • if i reload the page still old service worker is controlling the page until i navigate to different url or do a hard refresh, then only the new service worker takes control of the page. – Anurag Singh Sep 25 '17 at 14:11