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.
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.
// 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')))); });
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]].
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.
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.
navigator.services
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:
ServiceWorkerGlobalScope
object and its associated service worker is not an active worker, return a promise rejected with an "InvalidStateError
" exception.TypeError
.
match(options)
The
match(options)
method MUST run these steps:
matchAll(options)
The
matchAll(options)
method MUST run these steps:
An instance of the ServicePort
interface represents a service
port.
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:
The close()
method MUST run
these steps:
connect
event
The ServicePortConnectEvent interface represents a received connection attempt.
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:
close
event
The Connect algorithm will be re-defined.
ServicePortConnectOptions
instance
Promise
.
true
, then:
TODO: run network header based installation algorithm, which might change registration to be non-null.
DOMException
whose name is "AbortError
" and
terminate these steps.
connect
using
ServicePortConnectEvent
interface at
global.navigator.services
.
Better define these next steps.
acceptConnection
, or null
if no event
listeners called acceptConnection
.
true
or a Promise resolving to
true
, do the following:
DOMException
whose name is "AbortError
".
Figure out sensible behavior when there is no active worker. Probably wait for some worker to become the active worker?
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.