Skip to content

Instantly share code, notes, and snippets.

@sotayamashita
Forked from slightlyoff/push_api_sketch.md
Created February 22, 2016 05:36
Show Gist options
  • Save sotayamashita/5b3ed40b577cce7d9d6e to your computer and use it in GitHub Desktop.
Save sotayamashita/5b3ed40b577cce7d9d6e to your computer and use it in GitHub Desktop.
Service Worker + Push API

Motivation

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

Example

<!DOCTYPE html>
<!-- https://example.com/index.html -->
<html>
  <head>
    <script>
      // 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
        asyncXHR(
          "http://example.com/push/activate" + toQueryString(registration);
        );
      }

      // This is an ugly detail of the current SW design. See:
      //   https://github.com/slightlyoff/ServiceWorker/issues/174
      function swReady() {
        var sw = navigator.serviceWorker;
        return new Promise(function(resolve, reject) {
          if (!sw.active) {
            // 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);
        });
      });


    </script>
  </head>
</html>
// https://example.com/service_worker.js

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.
  e.waitUntil(...);
};
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
  console.log(e.message.channelName);
  // Log the deserialized JSON data object
  console.log(e.message.data);

  // ...

  // From here the SW can write the data to IDB, send it to any open windows,
  // etc.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment