Skip to content

Instantly share code, notes, and snippets.

@jungkees
Last active August 29, 2015 14:03
Show Gist options
  • Save jungkees/3154398b8deee7c70139 to your computer and use it in GitHub Desktop.
Save jungkees/3154398b8deee7c70139 to your computer and use it in GitHub Desktop.
Writing ServiceWorker-Using Specifications

This document is obsolete. See the extensibility section of the spec instead.

This is a sketch of the guide to binding an API space to a Service Worker. This content will be incorporated in the section 7. Extensibility of Service Workers spec upon necessary iterations.

Bind to a Service Worker

Promise<T> apiSpace.method(ArgumentList, optional ServiceWorker serviceWorker);
  • serviceWorker is an optional argument. When omitted, the document's Service Worker registration's active worker is used; when specified, the specified worker is used.
  • API-SW binding requires an active worker. That is, if the designated Service Worker is not an active worker at the time of the method invocation, it fails (i.e. rejects the promise if the return type of method is a promise.)
  • I have an impression that navigator.serviceWorker.ready should resolve only with an active worker ("activating" or "activated") not a worker in waiting.
    • When an API space successfully registers it with a worker in waiting, the corresponding functional event is still not getting fired. And if we allow this, it may end up with a situation where two different versions of the Service Workers are running at the same time.

[Template algorithm] Register API space with a Service Worker

apiSpace.method(list of arguments, serviceWorker) must run these steps:

  1. Let p be a newly-created promise.
  2. Return p.
  3. Run these steps asynchronously:
  4. If the optional argument serviceWorker is omitted, then: 1. Let registration be the result of running [[MatchScope]] algorithm with entry settings object's API base URL as its argument. 1. If registration is null, then:
    1. Reject p with an "InvalidStateError" exception.
    2. Abort these steps. 1. Else if registration.activeWorker is null, then:
    3. Reject p with an "InvalidStateError" exception.
    4. Abort these steps. 1. Else,
    5. Bind the apiSpace with registration.
    6. Do the necessary steps specific to the API.
    7. Resolve p with T object.
  5. Else, 1. If serviceWorker.state is either "activating" or "activated", then:
    1. Let registration be the result of running [[GetRegistration]] algorithm with serviceWorker.scope as its argument.
    2. Bind the apiSpace with registration.
    3. Do the necessary steps specific to the API.
    4. Resolve p with T object. 1. Else,
    5. Reject p with an "InvalidStateError" exception.

Examples

// will happen to be successful if the document has an active worker on
// its registration
navigator.push.register();
// but it's uncertain, so you may wait for the active worker
navigator.serviceWorker.ready.then((sw) => {
  // no need to specify the worker here
  navigator.push.register();
  // though you can
  // navigator.push.register(sw);
});

// If API init with a specific Service Worker is required, 
// this is allowed
// e.g example.com/index.html has buttons to register list of push
// services within exmple.com and this is one of them
navigator.serviceWorker.register(
  "/cocacola/sw.js", 
  { scope: "/cocacola/*" }
).then((sw) => {
  sw.onstatechange = (e) => {
    if (sw.state === "activated") {
      navigator.push.register(sw);
    }
  };
});

Define DerivedEvent and EventHandler for its functional event

Specifications may define their functional events (i.e. event interfaces derived from Event interface) and the corresponding event handlers. The event handlers should be added on the ServiceWorkerGlobalScope interface as such:

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onfunctionalevent;
};

[Template algorithm] Fire a functional event to a Service Worker

  1. Let scope be an absolute URL, serialized, representing the URL scope of the Service Worker which the API is registered with.
  2. Let event be a new initialized DerivedEvent.
  3. Invoke [[HandleFunction]] with scope and event.

[[HandleFunction]] will be defined in Service Workers spec as roughly the following algorithm: Input: scope, event Output: None

  1. Let registration be the result of running [[GetRegistration]] with scope as its argument.
  2. If registration is null, then:
  3. Return null.
  4. Let matchedWorker be registration.activeWorker.
  5. If matchedWorker is null, then:
  6. Return null.
  7. Run a worker for a script with URL matchedWorker.scriptURL and a script settings object settings object.
  8. Fire an event event on the ServiceWorkerGlobalScope associated with the Service Worker.
  9. Return.
@jakearchibald
Copy link

API-SW binding requires an active worker … navigator.serviceWorker.ready should resolve only with an active worker

I don't think this is right. Here are the things that are using ServiceWorker or are likely to use it:

  • Push
  • Background sync
  • Notification
  • Alarms
  • Geofencing

If I register for one of these, then later the active serviceworker is replaced with another via an update, I wouldn't expect to lose those registrations. I feel the push registration, for example, is made against the serviceworker registration and not the active serviceworker. If I unregistered the serviceworker, my push registration would be gone (there's an argument for push/sync to live within the serivceworker object, but I guess that would be too much typing.

Although push etc is linked to registrations, you can't register for them until there's at least a waiting SW. This avoids the case where you successfully create a registration for the first time, but the install fails & the SW is implicitly unregistered. Once there's a waiting SW, the registration is stable, that's why serivceWorker.ready resolves at that point.

The events that push/sync/noticiations etc create go to the active worker if there is one, otherwise the waiting worker.

@jungkees
Copy link
Author

jungkees commented Jul 2, 2014

I feel the push registration, for example, is made against the serviceworker registration and not the active serviceworker.

Agreed as I specified in the algorithm like "Bind the apiSpace with registration."

The events that push/sync/noticiations etc create go to the active worker if there is one, otherwise the waiting worker.

How about fetch? Currently [[HandleFetch]] makes it go to the network if there's no active worker in the registration.

The question can be rephrased as "Will the functional events be queued up for worker in waiting?"

@jakearchibald
Copy link

You're totally correct, as usual. I misread & misunderstood.

onfetch doesn't happen before onactivate, it'd be weird for other functional events to fire earlier. So yeah, navigator.serviceWorker.ready should resolve only with an active worker & attempting to register for push before this would reject.

@annevk
Copy link

annevk commented Jul 3, 2014

It seems weird that /scope/ is the identifier of the service worker. Is that really how this should work? What if the service worker changes its scope (if we give it that ability at some point), do all of these update?

@jungkees
Copy link
Author

jungkees commented Jul 7, 2014

A document uses a registration and its active worker is controlling that document. An API using SW binds its API space to a registration. It'd be natural for APIs to look up a valid registration, it registered to, to locate an active worker to fire its functional events on. The key for a registration is a scope. IMHO, the ability to change SW's scope seems sort of an excessive setting.

@jakearchibald
Copy link

We have ServiceWorkerRegistration now, so I guess that replaces the serviceworker argument (if the API can't add to the ServiceWorkerRegistration interface)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment