[w3c/ServiceWorker] [Proposal] ServiceWorkerContainer.installing(scope) promise (#1364)

## Motivation

I'm developing a web component to observe the whole service workers lifecycle. Something like:

```html
<sw-lifecycle-events
    scope="/"
    registration-state="{{registrationState}}"
    state="{{state}}"
    on-service-worker-installing="handleInstallingEvent"
    on-service-worker-updated="handleUpdatedEvent"
    on-service-worker-installed="handleInstalledEvent"
    on-service-worker-activating="handleActivatingEvent"
    on-service-worker-activated="handleActivatedEvent"
    on-service-worker-redundant="handleRedundantEvent">
</sw-lifecycle-events>
```

Valid values of `registration-state` property (attribute) are `installing`, `waiting` and `active`.

Valid values of `state` property (attribute) are `installing`, `installed`, `activating`, `activated` and `redundant`.

When the user inserts this custom HTML element into the page, it finds the service worker, updates its properties (attributes) and creates custom events. The user can listen to these events and e.g. show popups when the service worker has been installed for the first time ("Content is now available offline") or the new service worker has been installed, replacing the current service worker ("New or updated content is available"), etc.

The first idea that came to my mind was to use `ready` promise:

```js
navigator.serviceWorker.ready.then(registration => {
```

The good thing about `ready` promise is it will never reject, and waits indefinitely until the `ServiceWorkerRegistration` associated with the current page has an `active` worker.

But this is a bad idea.

## Problems

* `ready` promise resolves to the registration when the state is `activating` or `activated`. This means it's not possible to get `installing` and `installed` states. So it's not possible to show popups when the service worker has been installed for the first time or the new service worker has been installed, replacing the current service worker.

* `ready` doesn't have the `scope` parameter. What if the web app has multiple service workers and I need to get the service worker with a certain scope?

The next idea was to use `getRegistration()` method.

The good thing about `getRegistration()` promise is it has the `scope` parameter.

But it also has problems.

## Problems

* `getRegistration()` dosn't wait indefinitely (like `ready`). It can resolves to `undefined` (if `getRegistration()` is called before `register()`). That means I need to find the right moment to call `getRegistration()`. If I call it too soon, I will get `undefined`. If I call it too late, I may miss `installing` / `installed` / `activating` states.

The good practice is to use the window load event to register service worker (to keep the page load performant):

```js
window.addEventListener('load', () => {
  navigator.serviceWorker.register('service-worker.js');
```

But it doesn't mean that everyone is doing it.

The first thing I did in my web component was to place `getRegistration()` inside `connectedCallback` lifecycle callback:

```js
connectedCallback() {
  super.connectedCallback();
  navigator.serviceWorker.getRegistration(this.scope).then(registration => {
```

Here are the problems I encountered:

## Problems

* If the registered service worker doesn't use the window load event, there are 2 options:

  * `connectedCallback` (and `getRegistration()`) is called before `register()`, I will get `undefined`.
  * `register()` is called before `connectedCallback` (and `getRegistration()`), I may miss some of `installing` / `installed` / `activating` states.

The next idea was to use the window load event to call `getRegistration()` inside `connectedCallback` lifecycle callback (it should fix the case when `getRegistration()` is called before the window load event (and before `register()`):

```js
connectedCallback() {
  super.connectedCallback();
  window.addEventListener('load', () => {
    navigator.serviceWorker.getRegistration(this.scope).then(registration => {
```

But this is also a bad idea:

## Problems

* Anyway, there is no guarantee that `register()` will be called before the `getRegistration()`. Both use the window load event.
* If the user adds web component to the page dynamically, then `connectedCallback` will be called after `window.onload`. But the code inside `connectedCallback` waits the window load event. This means that the code inside `connectedCallback` will not execute.

There is also a case when the web app has a service worker (for precaching static content) [with scope `/`] and the second service worker (for receiving push notifications) [with the different scope] downloads from CDN and registers dynamically later (after the user gives permission to receive push notifications). This is how e.g. Firebase Notifications works (@gauntface knows).

The user of my web component may want to observe the whole lifecycle of this lazy-loaded service workers (starting from `installing` state). But it seems that at the moment it's impossible. If I'm wrong, please correct me.

## Proposal

* A new (similar to `ready`) promise `navigator.serviceWorker.installing(scope)` that will never reject, waits indefinitely and resolves to the registration when the state is `installing`. Also it should have the optional `scope` parameter.

* In the future, it would be great to have a methods, based on [observables](https://github.com/tc39/proposal-observable) instead of promises. The biggest difference between promises and observables is that an observable can receive multiple values over time while a promise only represents a single value (when an async operation completes or fails). I.e. a promise represents a single future event, an observable represents a stream of future events. It should solve the problem with multiple dynamically (at any time) registered service workers. E.g. an observable-based method that returns a (changing over time) array of registrations / service workers.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/ServiceWorker/issues/1364

Received on Sunday, 28 October 2018 10:30:26 UTC