What is the use of `self.Clients.claim()` - javascript

To register a service worker, I can call
navigator.serviceWorker.register('/worker.js')
Every time the page loads it checks for an updated version of worker.js. If an update is found, the new worker won't be used until all the page's tabs are closed and then re-opened. The solution I read was:
self.addEventListener('install', function(event) {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim());
});
I can understand the skipWaiting part, but what exactly does clients.claim() do? I've done some simple tests and it seems to work as expected even without it.

I'm excerpting the following from a guide to the service worker lifecycle:
clients.claim
You can take control of uncontrolled clients by calling
clients.claim() within your service worker once it's activated.
Here's a variation of the demo above which calls clients.claim() in
its activate event. You should see a cat the first time. I say
"should", because this is timing sensitive. You'll only see a cat if
the service worker activates and clients.claim() takes effect before
the image tries to load.
If you use your service worker to load pages differently than they'd
load via the network, clients.claim() can be troublesome, as your
service worker ends up controlling some clients that loaded without
it.
Note: I see a lot of people including clients.claim() as boilerplate,
but I rarely do so myself. It only really matters on the very first
load, and due to progressive enhancement the page is usually working
happily without service worker anyway.

Service worker takes controls from the next page-reload after its registration. By using self.skipWaiting() and self.clients.claim(), you can ask the client to take control over service worker on the first load itself.
e.g
Let's say I cache a files hello.txt, and again If I make a call for hello.txt it will have make server call even though I have resource in my cache. This is the scenario when I don't use self.clients.claim(). However on making a server call for hello.txt on next page reloads, it will be serving the resource from the cache.
To tackle this problem, I have to use combination of self.skipWaiting() and self.clients.claim() so that service worker starts serving content as soon as it is activated.
P.S:
next page-reload means page revisit.
first load signifies the moment when page is visited for the first time.

I had trouble wrapping my head around clients.claim as well and none of the explanations made any sense to me so hopefully this answer helps anyone struggling as well.
To understand Clients.claim we have to look at the worker lifecycle.
Installing; This is the first phase after registration. When the oninstall handler completes, the service worker is considered installed.
Installed; The service worker is waiting for clients using other service workers to be closed.
Activating; There are no clients controlled by other service workers. When the onactive handler completes, the service worker is considered activated.
Activated; The service worker now controls the page.
skipWaiting and Clients.claim are designed to solve different problems.
Clients.claim ONLY has an effect on the very first time your webpage goes from an uncontrolled webpage to a controlled (by a service worker) webpage by registering a service worker.
skipWaiting is exactly what it says. It skips the waiting phase and moves directly to activating. Once activated it is now the active service worker for all clients. Clients being any window or tab that has a webpage open that is within the scope of your service worker.
So why do we need Clients.claim then?
This confused me and I bet it confused you too. The answer is best described in an example.
Imagine your webpage DOES NOT register a service worker and is therefor uncontrolled. You have two tabs (clients) of your webpage open. You make an update to your webpage so that it will now register a service worker.
You decide to reload the first tab (client), it will now fetch the script that registers the service worker and it will start to install. Once installed it notices that no other client is being controlled by a service worker so it does not have to wait and it can immediately safely activate the service worker and any fetch of a resource will now go through your service worker.
However, here is the catch, your other tab (client) will also have an active service worker now, BUT, it is not yet being controlled. Meaning any fetch will not go through the service worker yet. You need to reload any other tab (client) in order for it to be controlled by the active service worker. This is confusing, since if any new service worker hereafter becomes active by forcing it with skipWaiting, the other tabs (clients) will immediately be controlled by the new active service worker. So I emphasize the reload part is needed ONLY when an uncontrolled webpage becomes controlled.
Enter Clients.claim. When you call self.clients.claim() in the first service worker when it becomes activated, like so:
self.addEventListener('activate', event => {
event.waitUntil(clients.claim());
});
It will make sure the other tabs (clients) that were uncontrolled, but have an active service worker, will get controlled by the active service worker. Meaning any fetch to a resource will now go through the active service worker. Without Clients.claim the service worker is not used until the page is reloaded.
Again, if all the webpages are being controlled by a service worker already. If a NEW service worker is detected and installed, it normally waits until all tabs with the webpage (clients) are closed. The next time you visit the webpage it will have activated the new service worker and the webpage is being controlled by it.
However, if you don't close all the clients and you force the new service worker by using skipWaiting it will immediately become active for all clients and also controlled. Meaning any new fetch for a resource from ANY of the clients will now immediately go through your new service worker. Now you don't need to use Clients.claim in order for the other clients to start using your new service worker.
This was my attempt, hopefully it helped someone.

Clients.claim() makes the service worker take control of the page when you first register a service worker. If there is already a service worker on the page, it will make no difference. skipWaiting() makes a new service worker replace an old one. Without it, you would have to close the page (and any other open tabs containing a page in the same scope) before the new service worker was activated.

Related

In what cases does Clients.claim() result in a worker controlling a page loaded by a different service worker?

According to https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim:
When a service worker is initially registered, pages won't use it
until they next load. The claim() method causes those pages to be
controlled immediately. Be aware that this results in your service
worker controlling pages that loaded regularly over the network, or
possibly via a different service worker.
The only case I can think of is of a page loaded by a service worker, and then the page becomes un-controlled or detached from the service worker, and then a new updated service worker calls Clients.claim(), which claims the un-controlled page (the page loaded by a different service worker).
As far as pages that were previously loaded by a different service worker, and that are still controlled, my understanding is that once a service worker becomes active (via skipWaiting), it will control all pages that are controlled by that worker, regardless of whether Clients.claim() is called or not. My understanding, based on this: https://w3c.github.io/ServiceWorker/#service-worker-registration-concept is that there can only be a single active worker for a given scope. And that single active worker is the worker controlling all controlled pages for that registration.

What happens to a Service Worker when a new tab is opened under the same scope?

I am implementing a Service Worker with a React app. I am using the Service Worker to add an Authorization header to every fetch request coming from the controlled window (API calls as well as images and other assets). The React app gets a token from an identity provider (Keycloak), and then sends this to the Service Provider. Here is a simplified flow:
The React app loads
The React app loads the Service Worker using a Javascript file on the same host.
The React app registers the Service Worker using
navigator?.serviceWorker?.register('/myServiceWorker.js', { scope: '/' }).then(() => {
navigator.serviceWorker.controller?.postMessage({
type: 'MY_TOKEN'
});
}
The interesting thing is that this works fine on first load, but when I open another tab and run the same app, it doesn't work - I can't access the navigator.serviceWorker.controller and use the postMessage method - because navigator.serviceWorker.controller is undefined.
I think this is because there is only one copy of the Service Worker, but 2 copies of the React app running. When the second tab opens, the React script runs, loads the Service Worker, but the browser sees that the new tab is within the scope of the Service Worker, so it does not run the initialization of the Service Worker again. However, this seems to avoid having the Service Worker claim the new window or something which prevents the Service Worker from registering the new window with a controller? Or is it just a race condition of some kind?
EDIT: Additionally, and somewhat strangely, if I refresh the 2nd tab, it will work every other time. This seems to be reliable.

When does Workbox's precaching go into effect?

I got a error; the worbox precaching my Static files,for example js or css;At this time I setted workbox.routing.registerRoute is don't work;
If I delete workbox precaching (must sure server worker cache file),After the refresh file is from cache;
Responses won't come from the service worker until the registered service worker takes control of the current page. Depending on how you're testing things, that might not happen until you've closed all of your previously open tabs for your origin.
You can learn more at "The Service Worker Lifecycle".
I'd recommend starting from scratch by using a Chrome Incognito window, going through the SW registration, and then reloading that Incognito tab. At that point, the newly registered SW should be in control of the page, and you should see your precached JavaScript being used to satisfy the subresource request.
In general, if you are using Workbox precache and runtime routing in the same service worker, and you list you call to precaching first (which is what you're doing), then precaching will take precedence.

Can a user receive push notifications from a "added to home" PWA after a device reboot?

From my researches, creating a PWA with the ability of receiving push notifications is pretty straightforward.
But what happens if the user reboots the device? Will the app (previously added to home screen, and thus downloaded) able to already receive push notification or the user will have to re-open the app at least once (to start the background service worker)?
If not, there is a way to restart a service worker automagically after device reboot?
The idea that you would restart a service worker at runtime is a complete misunderstanding of service worker architecture.
The whole concept is they are short lived processes, invoked in response to an event and terminated upon completion of their task.
Receiving notifications via the Push API is dependent upon the browser process running, not your specific PWA. Notification subscription endpoints point at browser specific servers which cannot be changed, eg chrome endpoints all start https://fcm.googleapis.com/fcm/send/....
Your application server posts push notifications to these servers, which in turn push them to your subscribers browsers. The browser then launches the relevant service worker which invokes the processes within its push event listener. Once the event.waitUntil promise resolves the service worker exits.
You don't have to open the app after reboot or keep it in the background explicitly to receive push notifications. Its part of web standard and works without manually intervention. iOS support is still limited. You can read more about it here.

Two service workers at the same time

I'd like to use two service workers on my site: one to provide a classic offline cache (/sw.js) for my PWA and another for something like a local database "server" which uses background sync and push (/sw-db.js). Since the latter tends to do heavy work (blocking the event loop for a few ms) it's better to keep it separate.
Since the database sw is not used for fetch requests, I would give it a dummy scope, whereas sw.js is scoped for the whole domain.
Does the first, which responds to "fetch" events, also serve the code/URL for /sw-db.js (keeping it somewhat in-sync with site updates) or are service workers always updated via network.
The sw.js script URL that you pass in to navigatior.serviceWorker.register('/path/to/sw.js') will always be fetched bypassing any other service workers when it's time to check for updates. So to answer your question, the other service worker's fetch handler won't be triggered.
The HTTP cache does come into play whenever there's an update check for a service worker script. So you should make sure you're setting proper HTTP cache control headers for your use case.
Usually, a service worker update check is triggered due to a navigation to a page controlled by a service worker, but if you have a service worker with a "dummy" scope, it won't end up controlling any pages. That being said, when a service worker handlers sync or push events you'll also end up triggering the update check. I'm not sure if every sync or push triggers the check, or just a subset of them, such as the ones which cause a new service worker to spawn. But it will happen at least some of the time.

Categories