This document describes an API to facilitate cross origin communication between service workers and webpages.

This specification is not being actively maintained. It may be revived in the future, but for now should be considered obsolete. Some of the functionality it tried to offer can be achieved using foreign fetch instead.

Introduction

With the navigator.connect API clients (either webpages or some flavor of worker) can connect to services, with these services being implemented by service workers.

Example

// http://example.com/webapp.js
navigator.services.connect('https://example.com/services/echo').then(
  function(port) {
    port.postMessage('hello');

    navigator.services.addEventListener('message', function(event) {
      if (event.source != port) return;
      // Handle reply from the service.
    });

    navigator.services.addEventListener('close', function(event) {
      if (event.source != port) return;
      // The connection to the service was clsoed.
    });
  }
);
// https://example.com/serviceworker.js
navigator.services.addEventListener('connect', function(event) {
  // Could optionally check event.origin in addition to checking targetURL to determine if that
  // origin should be allowed access to this service.
  if (event.targetURL === 'https://example.com/services/echo') {
    event.respondWith({accept: true, name: 'echo_client'}).then(
      (port) => port.postMessage('You are connected!')
    );
  }
});

navigator.services.addEventListener('message', function(event) {
  if (event.source.name === 'echo_client') {
    // Handle messages from the client.
    event.source.postMessage(event.data, event.ports);
  } else {
    // Connections no longer of interest.
    event.source.close();
  }
});
// https://acme.com/client-sw.js
self.addEventListener('activate', function(event) {
  event.waitUntil(
    navigator.services.connect('https://example.com/services/analytics', {name: 'analytics'}));
});

self.addEventListener('fetch', function(event) {
  navigator.services.match({name: 'analytics'}).then(
    (port) => port.postMessage('log fetch'));
});
// https://example.com/serviceworker.js
self.addEventListener('push', function(event) {
  event.waitUntil(navigator.services.matchAll({targetURL: 'https://example.com/services/push').then(
    (ports) => ports.forEach((port) => port.postMessage('received push'))));
});

Terminology

The terms event handler, event handler event type, and queue a task are defined in [[!HTML5]].

Promise is defined in [[!ECMASCRIPT]].

EventInit, DOMException, and AbortError are defined in [[!DOM]].

MessageChannel, MessagePort, and MessageEvent are defined in [[!WEBMESSAGING]]

Service Worker, ServiceWorkerRegistration, and ServiceWorkerGlobalScope are defined in [[!SERVICE-WORKERS]].

Security and privacy considerations

Most of what is described here can already be achieved using cross origin iframes, regular cross origin messaging, and regular service worker messaging. As such the security and privacy implications of this API are minor. Having said that, there are a few parts that are a few interesting security and privacy considerations to keep in mind:

User agents should not leak any information on sites a user has visited etc to other webpages. In particular this means that a service rejecting a connection attempt should be indistinguishable from no service being installed to service a particular URL.

Currently this API only allows connecting to services that have already been installed. If in the future some mechanism where attempting to connect to a service can trigger installation of a service worker, this of course has its own set of security and privacy implications.

Model

The model definition is in flux. Especially, the concepts around a service port internal slot, a message channel between two service ports, and entangle operation need to be re-defined.

A service port is a messageable port.

A service port has its name (a string).

A service port has its data.

A service port has an associated connection (a connection).

A connection is an entangled message channel.

A connection has an associated client (an environment settings object) and an associated service (an environment settings object whose global object is a ServiceWorkerGlobalScope object).

A connection has an associated client side port (a service port) and an associated service side port (a service port).

A connection has its target url (a string).

A service worker registration has an associated name to service port map.

An environment settings object whose global object is not a ServiceWorkerGlobalScope object has an associated name to service port map.

A service worker's environment settings object does not own a name to service port map. A service worker uses its containing service worker registration's name to service port map.

A name to service port map is a Multimap of the Record {[key], [value]} where [key] is a name and [value] is a service port.

API Description

navigator.services

readonly attribute ServicePortCollection services

ServicePortCollection interface

Promise<ServicePort> connect(DOMString url, optional ServicePortConnectOptions options)
Promise<ServicePort> match(ServicePortMatchOptions options)
Promise<sequence<ServicePort>> matchAll(optional ServicePortMatchOptions options)
[Exposed=ServiceWorker] attribute EventHandler onconnect
attribute EventHandler onmessage
attribute EventHandler onclose
DOMString name
any data
boolean installWorker
DOMString name
DOMString targetURL

A ServicePortCollection object has an associated settings object, which is an environment settings object whose global object is associated with the Navigator object or the WorkerNavigator object that the ServicePortCollection object is retrieved from.

The onconnect attribute is an event handler whose corresponding event handler event type is connect.

The onmessage attribute is an event handler whose corresponding event handler event type is message.

The onclose attribute is an event handler whose corresponding event handler event type is close.

connect(url, options)

The connect(url, options) method MUST run these steps:

  1. If the context object's settings object's global object is a ServiceWorkerGlobalScope object and its associated service worker is not an active worker, return a promise rejected with an "InvalidStateError" exception.
  2. Let url be the result of parsing url with entry settings object's API base URL.
  3. If url is failure, return a promise rejected with a TypeError.
  4. Return the result of running Connect algorithm with url and options as arguments.

match(options)

The match(options) method MUST run these steps:

  1. To be defined.

matchAll(options)

The matchAll(options) method MUST run these steps:

  1. To be defined.

ServicePort interface

An instance of the ServicePort interface represents a service port.

readonly attribute DOMString targetURL
readonly attribute DOMString name
readonly attribute any data
void postMessage(any message, optional sequence<Transferable> transfer)
void close()

The targetURL attribute MUST return the service port's connection's target url.

The name attribute MUST return the service port's name.

The data attribute MUST return the service port' data.

The postMessage(message, transfer) method MUST run these steps:

  1. To be defined.

The close() method MUST run these steps:

  1. To be defined.

The connect event

The ServicePortConnectEvent interface represents a received connection attempt.

readonly attribute DOMString targetURL
readonly attribute DOMString origin
Promise<ServicePort> respondWith(Promise<ServicePortConnectResponse> response)
DOMString targetURL
DOMString origin
boolean accept
DOMString name
any data

The targetURL attribute MUST return the value it was initialized to.

The origin attribute MUST return the value it was initialized to.

The respondWith(response) method MUST run these steps:

  1. To be defined.

The close event

readonly attribute ServicePort source
ServicePort source
readonly attribute ServicePort source
ServicePort source

Algorithms

Connect

The Connect algorithm will be re-defined.

Input
url, an an absolute URL
options, a ServicePortConnectOptions instance
Output
promise, a promise
  1. Let promise be a new Promise.
  2. Run the following substeps in parallel:
    1. Let registration be the result of running the Match Service Worker Registration algorithm on url
    2. If registration is null, and options.installWorker is true, then:
      1. TODO: run network header based installation algorithm, which might change registration to be non-null.

    3. If registration is null, reject promise with a DOMException whose name is "AbortError" and terminate these steps.
    4. If registration's active worker is not null, then:
      1. Run the Handle Functional Event algorithm with registration and callbackSteps that when invoked with global run the following substeps:
        1. Fire an event named connect using ServicePortConnectEvent interface at global.navigator.services.

          Better define these next steps.

        2. Let shouldAccept be the parameter passed to the first call of acceptConnection, or null if no event listeners called acceptConnection.
        3. If shouldAccept is true or a Promise resolving to true, do the following:
          1. Let connection be a new connection.
          2. Set connection's name to url.
          3. Generate a new UUID and set connection's id to the value.
          4. Set connection's client to connect client.
          5. Set connection's service to registration's active worker.
          6. Create a new port whose owner is the connect client, and set connection's client port to the result value.
          7. Create a new port whose owner is the registration's active worker's environment settings object, and set connection's service port to the result value.
          8. Entangle the client port and the service port.
          9. Set a newly-created Record {[key]: connection's name, [value]: connection} to name to connection map.
        4. Else, reject promise with a DOMException whose name is "AbortError".
    5. Else:

      Figure out sensible behavior when there is no active worker. Probably wait for some worker to become the active worker?

  3. Return promise.

Somehow phrase this to allow other ways for a user agent to connect to services, in particular services that are not implemented as a service worker.