Skip to content

bevacqua/swivel

Repository files navigation

swivel

Message passing between ServiceWorker and pages made simple

Inspiration

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 with sw and it wasn't taken on npm. And, because.

Installation

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

Usage

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
});

Importing

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.

Automatic

import swivel from 'swivel'

If you prefer being explicit, you could import the individual modules separately.

Manual

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()

API in Web Pages

The public swivel API exports a number of methods designed for web pages.

swivel.on(type, handler)

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.

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.

Example
swivel.on('data', function handler (context, ...data) {
  console.log(...data);
});

swivel.once(type, handler)

Equivalent to swivel.on but will only ever be called once. Returns swivel for chaining.

Example
swivel.once('data', function handler (context, ...data) {
  console.log(...data);
});

swivel.off(type, handler)

Unregisters a handler of type type that was previously registered using swivel.on or swivel.once. Returns swivel for chaining.

Example
swivel.on('data', function handler (context, ...data) {
  swivel.off('data', handler);
});

swivel.emit(type, ...data)

This method posts a message to the ServiceWorker. You can then use swivel.on(type, handler) from the ServiceWorker to listen to it.

Example
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.

Example
swivel.emit('data', { foo: 'bar' }, 'baz').then(function () {
  console.log('success');
});

swivel.at(worker)

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.

Example
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);
    });
  });

API in ServiceWorker

The public swivel API exports a number of methods designed for ServiceWorker scripts.

swivel.broadcast(type, ...data)

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.

Example
swivel.broadcast('urgent', ...data);

This method returns a Promise so you can await for the message to be successfully transferred to all clients.

Example
swivel.broadcast('urgent', ...data).then(function () {
  console.log('success');
});

swivel.emit(clientId, type, ...data)

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.

Example
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.

Example
self.addEventListener('fetch', function (e) {
  swivel.emit(e.clientId, 'data', { foo: 'bar' }).then(function () {
    console.log('success');
  });
});

swivel.on(type, handler)

You can use this method to listen from messages sent from a web page to the ServiceWorker using swivel.emit. Returns swivel for chaining.

Example
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.

context.reply(type, ...data)

Using this method, your ServiceWorker can reply to messages received from an individual web page.

Example
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.

Example
swivel.on('data', function handler (context, [x, y]) {
  context.reply('result', x * y).then(function () {
    console.log('success');
  });
});

swivel.once(type, handler)

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.

Example
swivel.once('data', function handler (context, [x, y]) {
  context.reply('result', x * y).then(function () {
    console.log('success');
  });
});

swivel.off(type, handler)

Unregisters a handler of type type that was previously registered using swivel.on or swivel.once. Returns swivel for chaining.

Example
swivel.on('data', function handler (context, ...data) {
  swivel.off('data', handler);
});

License

MIT

About

Message passing between ServiceWorker and pages made simple

Resources

License

Stars

Watchers

Forks

Packages

No packages published