Search code examples
progressive-web-appsservice-workerworkbox

Which service worker event indicates it has controlled the page, and will intercept web traffic?


In my web app, some web requests must be intercepted and modified by the service worker, otherwise the requests will fail. This is especially important on the very first visit for a new user. I use clientsClaim() to ensure that.

Since I need to make sure the service worker is ready before I make the request, I tried to wait for navigator.serviceWorker.ready:

await navigator.serviceWorker.ready;
fetch(myRequest);

However, I found it doesn't work as intended. The very first request on the first visit is not intercepted. So I tried to add some wait time:

await navigator.serviceWorker.ready;
await twoSeconds();
fetch(myRequest);

This works, but it damages user experience because it delays the first meaningful UI. On the other hand, I also can't be sure 2 seconds is long enough for every computer.

What's the event that can tell me as soon as the the sw is ready to intercept traffic? It's only a problem on the first visit, but ideally the event will fire on every reload, because the code is easier to write if I simply await the same thing on every visit.


Solution

  • I think you're looking for a promise that you can use to signal when the current page is under control of a service worker.

    This can be achieved via

    await new Promise(r => {
      if (navigator.serviceWorker.controller) return r();
      navigator.serviceWorker.addEventListener('controllerchange', e => r());
    });
    
    // At this point, the page will be controlled by a service worker.
    

    This code is adapted from this GitHub discussion, and there's more context there.

    Generally speaking, it's not great to design a page that will only work if it's controlled by a service worker, since service workers are intended to be progressive enhancement, rather than a core requirement. But if you have that use case, the code above will help.