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.s
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.
According to MDN, ServiceWorkerContainer.register method takes a second parameter of options:
Currently available options are:
- scope: A USVString representing a URL that defines a service worker's registration scope; what range of URLs a service worker can control. This is usually a relative URL, and it defaults to '/' when not specified.
As for your updated question, the W3C Service Workers WD says:
A service worker registration of an identical scope url when one already exists in the user agent causes the existing service worker registration to be replaced.
Therefore navigator.serviceWorker.register(url, {scope: '/'}).then(doSomething) should work.
Problem is with the scope URL in your example:
./ should be /