How to prevent the same service worker from registering over multiple pages?

KROrb picture KROrb · Nov 20, 2015 · Viewed 8.6k times · Source

I have a service worker script that is registering repeatedly over multiple site levels.

In other words the same service worker is registered for www.site.ca/, www.site.ca/text-text, www.site.ca/example-example, and etc.

The site is built on php with different urls being produced depending on the content so kind of API-like. The reason that the service worker registration is on those pages is because most of the site traffic lands on those pages rather than the home page. The result being the same service worker being registered with different ids over different pages.

Does anyone have a way to prevent multiple registrations of the same script over multiple sub levels?

EDIT:

The purpose of the service worker is to set up notifications even if the user isn't on the site. Also the issue I'm having would negatively affect any attempts to update the service worker in the future since there would be multiple copies to update. The result being that only one copy would ever be updated when the user lands in the site again on a specific page. There would be no guarantee that all the copies of that service worker file would be updated. Also the result of the multiple copies has resulted in users getting stacks of notifications from the site so it is a problem.

EDIT:

The below is what I have so far and to my understanding scope defines where it gets registered. However the problem is that it needs to be able to register wherever a person enters the site thus the scope was set to '/'.

The issue I want to solve is to prevent subsequent registers of the same service worker file.

Localhost is a placeholder for the actual site.

navigator.serviceWorker.register('localhost/service-worker.js', {scope: './'})
.then(initialiseState);

In Chrome dev tools for debugging, the resulting service workers are in the format like this:

Scope: localhost/url/stuff/
Registration ID: 110
Active worker:
Installation Status: ACTIVATED
Running Status: STOPPED
Script: localhost/service-worker.js
...

Scope: localhost/
Registration ID: 111
Active worker:
Installation Status: ACTIVATED
Running Status: STOPPED
Script: localhost/service-worker.js
...

Scope: localhost/url
Registration ID: 112
Active worker:
Installation Status: ACTIVATED
Running Status: STOPPED
Script: localhost/service-worker.js
...

Ideally I want to somehow just keep it to one entry somehow.

Answer

Jeff Posnick picture Jeff Posnick · Nov 23, 2015

In the site-with-sub-directories scenario, where you want all of your sub-pages to be controlled by the same top-level service worker, I would suggest using an absolute URL for the service worker script location, and then relying on the default behavior that takes place when options.scope is omitted.

Note that the MDN docs quoted in the other response were incorrect about the default behavior. The service worker specification was updated in January 2015 to change the default scope to be equivalent to new URL('./', serviceWorkerScriptUrl).toString(). (The MDN docs have since been updated and should be accurate.)

To illustrate why I recommend omitting options.scope in this use case, consider the following alternatives, assuming you want a single, shared service worker registration:

  • navigator.serviceWorker.register('/sw.js') will give you a registration with a scope of /, which is what you want.
  • navigator.serviceWorker.register('/sw.js', {scope: '/'}) will give you a registration with a scope of /, which is also what you want, but doesn't offer any benefits over what you get with the default behavior.
  • navigator.serviceWorker.register('/subsite/sw.js', {scope: '/'}) will fail to register, because the scope of / is too broad for a service worker script that lives at /subsite/sw.js.
  • navigator.serviceWorker.register('/subsite/sw.js') will give you a registration with a scope of /subsite, which is what you want.
  • navigator.serviceWorker.register('/subsite/sw.js', {scope: '/subsite'}) would work, but again, it doesn't give you any benefits over the default behavior.

The only times I'd recommend explicitly setting a value for options.scope is when you need to explicitly limit the scope of the service worker to be narrower than the default scope.

Note that if you do decide to provide a value for options.scope, and that value is a relative URL like './', the URL is interpreted as being relative to the location of the script/page calling register(). {scope: './'} is not interpreted as being relative to the location of the service worker script URL. I was confused by this point for a while, so take some time to fully understand the implications there. That explains why you ended up with multiple registrations in your initial question.