4

Platform

"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36"

Workbox version

5.0.0-rc.0

I am using the workbox-window update() method to trigger a service worker update check as described in workbox issue #2130. This question also seems related to observations made in workbox issue #2301.

<script type="module">
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-rc.0/workbox-window.prod.mjs';

if ('serviceWorker' in navigator) {
    const wb = new Workbox('/sw.js');
    // Grab the update button from the UI using jQuery and add a listener to it.
    $('#update-button').on('click', function () {
        wb.update();
    });
    ....
}

</script>

If an update is found then a new service worker is loaded and goes into the waiting state. I listen for this event (both waiting and externalwaiting, doing the same thing in both cases), and ask the user if they want to install the update now or later.

function handleWaiting(wb) {
    if (confirm("An update is available for this app. Install now?")) {
        wb.messageSW({type: 'SKIP_WAITING'});
    }
}
wb.addEventListener('waiting', event => {
    handleWaiting(wb);
});
wb.addEventListener('externalwaiting', event => {
    handleWaiting(wb);
});

Most of the time the waiting event is fired, and if the user accepts the update the service worker is activated and we can reload the page to complete the update process:

function handleActivated(wb, event) {
    if (event.isUpdate) {
        window.location.reload();
    } else {
        wb.messageSW({ type: 'CLIENTS_CLAIM' });
    }
}
wb.addEventListener('activated', event => {
    handleActivated(wb, event);
});
wb.addEventListener('externalactivated', event => {
    handleActivated(wb, event);
});

If the waiting event is fired this process works fine.

  1. Make a small update to the app that results in the service worker file changing.
  2. Press the 'Check for Updates' button.
  3. New service worker is found & user is prompted to install the update
  4. Page is reloaded.

But if the externalwaiting event is fired then this code does not work. The new service worker remains in the waiting state, which I can see in Chrome Dev Tools.

Stuck waiting

Skip waiting message received by the old service worker

If the user confirms that they want to update the SKIP_WAITING message is received by the older, activated service worker definition.

Here's an excerpt from my logs that proves it.

  • Log entries from the application/client side are prefixed with 'Application' followed by the app version.
  • Log entries from the service worker are prefixed with 'Service Worker' followed by a DOB datestamp that is unique to each service worker instance.
01 [Application 0.0.1.2019.11.05-48] Checking for updates...
02 [Service Worker 2019-11-05 @ 15:23:14] <<<<<<<<<<<<<<< STARTING >>>>>>>>>>>>>>>
03 [Service Worker 2019-11-05 @ 15:23:14] Yay! Workbox 5.0.0-rc.0 is loaded 
04 [Service Worker 2019-11-05 @ 15:23:14] Lifecycle event: [install]
05 [Application 0.0.1.2019.11.05-48] Service Worker lifecycle event: 06 [externalinstalled]
07 [Application 0.0.1.2019.11.05-48] Service Worker lifecycle event: [externalwaiting]
08 [Application 0.0.1.2019.11.05-48] handle waiting...
09 [Service Worker 2019-11-05 @ 15:20:03] Message event: [SKIP_WAITING]

On line

  1. The user has just pressed the 'check for updates' button.
  2. A new service worker version has been found on 2019-11-05 @ 15:23:14 and is being parsed. We'll call this SWv2.
  3. Workbox is loaded by SWv2.
  4. SWv2 install event handler is executed.
  5. The Workbox window externalinstalled event handler is executed.
  6. The Workbox window externalwaiting event handler is executed.
  7. The workbox window externalwaiting event handler sends a SKIP_WAITING message using workboxWindow.messageSW()
  8. Service Worker v1 (loaded on 2019-11-05 @ 15:20:03) receives the skip waiting message. Nothing happens. SWv2 remains in the waiting state.

Contrast with with plain waiting event

In the following log you can see that the SKIP_WAITING message is received by the waiting service worker: v 2019-11-05 @ 16:07:32. The update process therefore completes successfully.

[Application 0.0.1.2019.11.05-52] Checking for updates...
[Service Worker 2019-11-05 @ 16:07:32] <<<<<<<<<<<<<<< STARTING >>>>>>>>>>>>>>>
[Service Worker 2019-11-05 @ 16:07:32] Yay! Workbox 5.0.0-rc.0 is loaded 
[Service Worker 2019-11-05 @ 16:07:32] Lifecycle event: [install]
[Application 0.0.1.2019.11.05-52] Service Worker lifecycle event: [installed]
[Application 0.0.1.2019.11.05-52] Service Worker lifecycle event: [waiting]
[Application 0.0.1.2019.11.05-52] handle waiting...
[Service Worker 2019-11-05 @ 16:07:32] Message event: [SKIP_WAITING]
[Application 0.0.1.2019.11.05-52] Service Worker lifecycle event: [redundant]
[Application 0.0.1.2019.11.05-52] Service Worker lifecycle event: [activating]
[Application 0.0.1.2019.11.05-52] Service Worker lifecycle event: [controlling]
[Service Worker 2019-11-05 @ 16:07:32] Lifecycle event: [activate]
[Application 0.0.1.2019.11.05-52] Service Worker lifecycle event: [activated]
[Application 0.0.1.2019.11.05-52] Reloading this window...
[Application 0.0.1.2019.11.05-53] <<<<<<<<<<<<<<< STARTING >>>>>>>>>>>>>>>
[Application 0.0.1.2019.11.05-53] Preloading 125 images...
[Application 0.0.1.2019.11.05-53] ServiceWorker registered!

And sometimes it just hangs...

And sometimes things just get stuck when there is a new service worker version available. The waiting or externalwaiting event handler is never triggered.

Chrome Dev Tools "trying to install"

When this happens there are no errors in the console and there is nothing wrong with the new version of the service worker.

The only way to un-stick things is to stop the active service worker, unregister it and reload.

Why does any of this matter?

I need to make sure that the update process works flawlessly on all platforms before I dare release this app...

If a user encounters either of these issues when I release an update they will struggle to get it and there will be nothing I can do about it. Multiplied by the number of installations == huge headache.

This all boils down to three questions: In order of importance:

  1. How can I handle externalwaiting so that the new version of the service worker is loaded and activated? How do I make sure that the SKIP_WAITING message is received by the waiting service worker?
  2. Why do the lifecycle events vary between the plain and external varieties?
    • For testing purposes I am making the same update to the service worker each time (a new version of a precached file).
    • I only ever have the app loaded in one tab.
    • The switch from plain to external events seems random.
  3. Why does the process sometimes get stuck before the waiting phase of the lifecycle and what can I do about this?
daffinm
  • 177
  • 1
  • 13
  • Hey, after this long time, I found the same issue . have you solved this? – Nika Kurashvili Feb 13 '20 at 13:44
  • Yes. But not with workbox window. Had to use vanilla service worker API. I'll try to find time to post on this. It's complex. But bottom line is that I had to abandon workbox window for this reason, though still using workbox for the service worker, which works great. – daffinm Feb 15 '20 at 16:03
  • Can we have some kind of chat or something ? i am working on this and would appreciate if i have to not spend huge amount of time on fixing it. You could quickly help me out and I'd ask the right questions. let me know how I can reach you. Thank you for answering !!! – Nika Kurashvili Feb 16 '20 at 14:35
  • I'll try to find time to extract my code into a publicly accessible example and post a link here. How urgently do you need a solution? (Mine send to work well in Apple and other platforms/browsers. Such a shame that no one in the workbox team responded to this...) – daffinm Feb 18 '20 at 17:46
  • Completelly agree. it's a shame. I made this work too. Wasn't that difficult. – Nika Kurashvili Feb 19 '20 at 07:33
  • OK. Does that mean you no longer need a chat/other input? In short, I used navigator.serviceWorker.register on the sw.js, then called reg.update(), then check the state of the registration: if not reg.installing or not reg.waiting then you know that no updates are found. Else, if reg.waiting then you handle the update by calling reg.waiting.postMessage({message: 'SKIP_WAITING'}). You listen for this event in the sw.js and call self.skipWaiting(). You then have to listen for the updatefound event in your application code. If there is an active service worker you reload the app. Helpful? – daffinm Feb 20 '20 at 12:23

1 Answers1

0

Please note that this has been solved now in Workbox window. There's an updated advanced recipe:

https://developers.google.com/web/tools/workbox/guides/advanced-recipes#offer_a_page_reload_for_users

Note also that there are (or were) issues with the code in this recipe. See the following issue for fixed code.

https://github.com/GoogleChrome/workbox/issues/2430

daffinm
  • 177
  • 1
  • 13