Skip to content

Instantly share code, notes, and snippets.

Last active June 7, 2018 07:15
Show Gist options
  • Save slightlyoff/8927885 to your computer and use it in GitHub Desktop.
Save slightlyoff/8927885 to your computer and use it in GitHub Desktop.
Service Worker + Push API


The current Push API Draft specifies a system that has no notion of push channel persistence. Further, it does not include push message payloads, a feature that many developers want.

This gist outlines an API which:

  • integrates with the Service Worker to enable delivery of push messages to applications which do not have visible tabs
  • enables a JSON-formatted body of content
  • guards access to registration for pushes on potential user consent


<!DOCTYPE html>
<!-- -->
      // Notes:
      //  - PushRegistration has been extended with a "channelName" property
      //  - PushMessage has been extended with "data" and "channelName" properties
      //  - The "version" property of PushMessage has been removed.

      var logError = console.error.bind(console);

      function toQueryString(obj) {
        var encode = encodeURIComponent;
        for (var x in obj) {
          components.push(encode(x) + "=" + encode(obj[x]));
        return "?" + components.join("&");

      function registerWithAppServer(registration) {
        // We've been handed channel name with a PushRegistration which
        // we send to the app server
          "" + toQueryString(registration);

      // This is an ugly detail of the current SW design. See:
      function swReady() {
        var sw = navigator.serviceWorker;
        return new Promise(function(resolve, reject) {
          if (! {
            // Note that the promise returned from register() resolves as soon
            // as the script for the SW is downloaded and evaluates without issue.
            // At this point it is NOT considered installed and will not receive
            // push events. We will, however, allow the registration for pushes
            // to proceed at this point. Push registrations that succeed and generate
            // messages will see those messages queued until the SW is installed
            // and activated, perhaps at some time in the near future (e.g., once
            // resources for the application itself are downloaded).
            sw.register("/service_worker.js", { scope: "*" }).then(resolve, reject);
          } else {
            sw.ready().then(resolve, reject);

      // Only try to register for a push channel when we have valid SW
      swReady().then(function(sw) {
        var gcmSenderId = "......";
        var apnSenderId = "......";
        var cn = "channel name";
        var push = navigator.push;

        // The push object is an async map that can be used to enumerate and
        // test for active channels.
        push.has(cn).catch(function() {
          // register() is async and can prompt the user. On success, the
          // channel name is added to the map.
          push.register(cn, {
            // the "sender" field is push-server-specific and is optional.
            // Some servers (e.g., GCM) will require that this field be
            // populated with a pre-arranged ID for the app. The system
            // selects between the supplied keys to decide which one to
            // offer the "local" push server for this registration.
            sender: {
              apn: apnSenderId,
              gcm: gcmSenderId,
              // ...
          }).then(registerWithAppServer, logError);


this.oninstall = function(e) {
  // Note that push messages won't be delivered to this SW
  // until the install and activate steps are completed. This
  // May mean waiting until caches are populated, etc.
this.onactivate = function(e) { ... }

// Note that we no longer need navigator.hasPendingMessages() because the UA
// can queue push messages for the SW and deliver them as events whenever it
// deems necessary.
this.onpush = function(e) {
  // Log the channel name
  // Log the deserialized JSON data object

  // ...

  // From here the SW can write the data to IDB, send it to any open windows,
  // etc.
Copy link

twirl commented Feb 11, 2014

  1. What ServiceWorker will get push messages if there are several workers matching different URL patterns?
  2. Why .has(nc).catch(...)? Using .has(cn).then(function (res) { if (res === false) ... }) seems more logical.

Copy link

  1. that's a good question. One thought I had was that the push messages would be dispatched to the SW for the URL that registered them, in this case Dispatch could happen based on usual longest-prefix-with-globbing matches the way navigations are matched.
  2. Hrm...I need to think about that. Thanks for the suggestion!

Copy link

twirl commented Feb 12, 2014

AFAIK, current push server implementations don't care about webapp url at all. In my opinion, the best way to handle this issue is to alter .registerServiceWorker signature like that:
sw.register("/service_worker.js", { scope: "*", handlePushMessages: true }); // last registration wins
Other alternatives which came to my mind:

  • alter .register signature (.register(cn, sw)) - raises the same problem of binding message stream to runtime-only entity
  • direct push messages to the root (/) of a domain - unobvious

Copy link

xogeny commented Feb 12, 2014

Not sure if this is relevant, but you might find this project interesting. It's a server side messaging library that uses HAL+JSON. It has a topic taxonomy where you can subscribe at different levels. The API itself might be interesting.

Copy link

For push message, it seems channel name should be the key to match the Service Worker to be spun up as the scope is for navigation matching. And as the active worker is the prerequisite for push registration, the UA can be informed the channel information either in SW registration time:

sw.register("/service_worker.js", { pushChannel: "twitter" });

or in push registration time:

swReady().then(function(sw) {
  push.register(sw, "twitter");

I think the latter is a bit more extensible option for other APIs:

// e.g. Task Scheduler API:
var alarm = navigator.taskScheduler;

swReady().then(function(sw) {
  alarm.add(sw, + (10 * 60000), { 
    message: "Alright. Let's go for lunch now!" 

Copy link

If push only works with a serivceworker, shouldn't registration also happen in the serviceworker? That does away with the weird swReady stuff.

Copy link

Re: scope matching. I had thought that perhaps the URL of the registering page would be used for mapping messages on the channel to active SW's. It's more implicit, which isn't great, but will Just Work (TM) most of the time.


Copy link

tobie commented Feb 17, 2014

FWIW, current Push Notification spec avoids sending data along with the event for privacy reasons. Push servers are generally going to be owned by third parties (e.g. the operator), so you don't want to send unencrypted content through them.

Sequence would be:

  1. App Server notifies push server of new event.
  2. Push Server notifies User Agent of new event.
  3. User Agent wakes up appropriate Service Worker.
  4. Service Worker makes XHR request to App Server to get data.

Copy link

After last week's discussion with Bryan, we're going to remove the channel name. If we need it, it can go in the payload.

Copy link

pwFoo commented Jun 7, 2018

I'm new with notification / push api and searching for a lib with closed window notification support for background running browsers or android devices.
Has your Code support for that feature.

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