Message passing between
ServiceWorker
and pages made simple
Understanding the raw API for message passing between ServiceWorker
and pages can be kind of confusing. There's MessageChannel
, ports, postMessage
deeply buried in navigator.serviceWorker.controller.postMessage
, addEventListener
, multiple ServiceWorkerRegistration
instances, and even promises are involved.
ServiceWorker
is too awesome to let this problem hinder its adoption, so I made swivel
in hopes people will find it easier to share messages across pages and their ServiceWorker
. For an introduction of ServiceWorker
you should look at this article.
I named it
swivel
mostly because it starts withsw
and it wasn't taken onnpm
. And, because.
Install it from npm
. You can then include swivel
in your pages and your ServiceWorker
, and Swivel will figure out what API to export depending on whether it's running within the ServiceWorker
or a regular web page.
npm i -S swivel
On your web pages, you can listen for messages from the ServiceWorker
. Remember to wait for ServiceWorker
to become active, and always feature test to ensure ServiceWorker
is available.
if (!('serviceWorker' in navigator)) {
return;
}
navigator.serviceWorker
.register('/service-worker.js')
.then(navigator.serviceWorker.ready)
.then(function () {
swivel.on('data', function handler (context, ...data) {
// do something with ...data
});
});
You can also emit messages to the ServiceWorker
from your pages.
swivel.emit('data', ...data);
Emitting returns a Promise
, in case you want to wait until the message is transferred to do something else.
swivel.emit('data', ...data).then(function () {
// ... more swivelling!
});
In your ServiceWorker
, the API barely changes. You can listen to messages posted from web pages with swivel.emit
using swivel.on
in the ServiceWorker
code.
swivel.on('data', function handler (context, ...data) {
// do something with ...data
});
If you need to reply to this particular page in the ServiceWorker
, you could just use code like the following.
swivel.on('data', function handler (context, ...data) {
context.reply('data', ...response);
});
You guessed correctly, context.reply
returns a Promise
.
swivel.on('data', function handler (context, ...data) {
context.reply('data', ...response).then(function () {
// ... more swivelling!
});
});
You can also emit messages to every page using swivel.broadcast
.
swivel.broadcast(type, ...data);
Broadcast also returns a Promise
that awaits all client.postMessage
signals to be processed.
swivel.broadcast(type, ...data).then(function () {
// ... more swivelling!
});
Pages can then listen for the type
event, which will go out to every page controlled by the ServiceWorker
.
swivel.on(type, function (...data) {
// do something with ...data
});
The swivel
package performs a convenient bit of feature testing in order to decide what API to export. In your web pages, it'll export an API corresponding to web pages. In a ServiceWorker
script, it'll export an API corresponding to the ServiceWorker
side.
import swivel from 'swivel'
If you prefer being explicit, you could import
the individual modules separately.
Here's an example of manual setup for your web pages.
import createChannel from 'swivel/page'
var swivel = createChannel()
Here's an example of manual setup for your ServiceWorker
script.
import createChannel from 'swivel/worker'
var swivel = createChannel()
The public swivel
API exports a number of methods designed for web pages.
This method listens for events emitted by the ServiceWorker
API. You can bind an event handler that receives all arguments emitted at that level. It can be triggered from a ServiceWorker
in 3 different ways. Returns swivel
for chaining.
The following methods -- when called from a ServiceWorker
-- trigger handlers registered with swivel.on(type, fn)
on web pages.
swivel.broadcast(type, ...data)
(message is broadcasted to every page)swivel.emit(clientId, type, ...data)
(message is unicasted usingclient.postMessage
)context.reply(type, ...data)
(message is unicasted usingMessageChannel
)
The handler has a context, ...data
signature.
A context.broadcast
flag in the handler
indicates whether the message was broadcasted or unicasted by the ServiceWorker
.
swivel.on('data', function handler (context, ...data) {
console.log(...data);
});
Equivalent to swivel.on
but will only ever be called once. Returns swivel
for chaining.
swivel.once('data', function handler (context, ...data) {
console.log(...data);
});
Unregisters a handler
of type type
that was previously registered using swivel.on
or swivel.once
. Returns swivel
for chaining.
swivel.on('data', function handler (context, ...data) {
swivel.off('data', handler);
});
This method posts a message to the ServiceWorker
. You can then use swivel.on(type, handler)
from the ServiceWorker
to listen to it.
swivel.emit('data', { foo: 'bar' }, 'baz');
This method returns a Promise
so you can await for the message to be successfully transferred to the ServiceWorker
.
swivel.emit('data', { foo: 'bar' }, 'baz').then(function () {
console.log('success');
});
The swivel.at(worker)
method returns an API identical to swivel
that talks strictly with worker
. The default swivel
API interacts with the worker found at navigator.serviceWorker.controller
. You can use as many channels as necessary.
navigator.serviceWorker
.getRegistration('/other')
.then(function (registration) {
var otherChannel = swivel.at(registration.active);
otherChannel.emit('data', { hello: 'world' });
otherChannel.on('data', function handler (context, ...data) {
console.log(...data);
});
});
The public swivel
API exports a number of methods designed for ServiceWorker
scripts.
This method sends a message of type type
from the ServiceWorker
to every client it controls. Pages can listen for broadcasted messages using swivel.on
or swivel.once
.
swivel.broadcast('urgent', ...data);
This method returns a Promise
so you can await for the message to be successfully transferred to all clients.
swivel.broadcast('urgent', ...data).then(function () {
console.log('success');
});
During fetch
events in a ServiceWorker
, it's possible to message a client using swivel.emit
. The web page can then receive and handle the message using swivel.on
.
self.addEventListener('fetch', function (e) {
swivel.emit(e.clientId, 'data', { foo: 'bar' });
});
Furthermore, swivel.emit
returns a Promise
, so you can await for the message to be successfully transferred.
self.addEventListener('fetch', function (e) {
swivel.emit(e.clientId, 'data', { foo: 'bar' }).then(function () {
console.log('success');
});
});
You can use this method to listen from messages sent from a web page to the ServiceWorker
using swivel.emit
. Returns swivel
for chaining.
swivel.on('data', function handler (context, ...data) {
console.log(...data);
});
The event handler
is provided with a context
object that allows for replies to messages originating from a web page.
Using this method, your ServiceWorker
can reply to messages received from an individual web page.
swivel.on('data', function handler (context, [x, y]) {
context.reply('result', x * y);
});
Furthermore, context.reply
returns a Promise
, so you can await for the reply to be successfully transferred.
swivel.on('data', function handler (context, [x, y]) {
context.reply('result', x * y).then(function () {
console.log('success');
});
});
Equivalent to swivel.on
but will only ever be called once. Returns swivel
for chaining. Also able to use context.reply
in handler(context, ...data)
for bidirectional communication.
swivel.once('data', function handler (context, [x, y]) {
context.reply('result', x * y).then(function () {
console.log('success');
});
});
Unregisters a handler
of type type
that was previously registered using swivel.on
or swivel.once
. Returns swivel
for chaining.
swivel.on('data', function handler (context, ...data) {
swivel.off('data', handler);
});
MIT