Web push notifications API

Drew Sears edited this page Jul 6, 2017 · 3 revisions

https://developers.google.com/web/fundamentals/engage-and-retain/push-notifications/

The Push API and Notification API enable mobile web apps to send notifications with the same capabilities as a native Android app.

The Push API uses service workers.

Push and Notification use different APIs. Push is a server giving instructions to a service worker. A notification is what the service worker renders.

Example

Having registered a service worker, call showNotification to display a notification.

serviceWorkerRegistration.showNotification(title, options);

Where options is defined as:

{
  "body": "Did you make a $1,000,000 purchase at Dr. Evil...",
  "icon": "images/ccard.png",
  "vibrate": [200, 100, 200, 100, 200, 100, 400],
  "tag": "request",
  "actions": [
    { "action": "yes", "title": "Yes", "icon": "images/yes.png" },
    { "action": "no", "title": "No", "icon": "images/no.png" }
  ]
}

How Push works

Steps in implementing push:

  • Add the client side logic to subscribe a user to push (Javascript UI)
  • API call from my server that triggers a push
  • The service worker that receives the push event and displays the notification

Step 1

First, we subscribe the user. To do that:

  1. Request permission from the user
  2. Get a PushSubscription object from the browser
  3. Send the PushSubscription object back to my server

Before subscribing the user I need to generate application server keys, also known as VAPID keys.

Step 2: Send a push message

Making the push call involves an API request to a push service.

The push service receives and validates a request, and either delivers it to the browser's service worker, or queues it for later delivery if the browser is offline.

You know the endpoint because it comes back in the PushSubscription object:

{
  "endpoint": "https://random-push-service.com/some-kind-of-unique-id-1234/v2/",
  "keys": {
    "p256dh" :
"BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM=",
    "auth"   : "tBHItJI5svbpez7KI4CCXg=="
  }
}

So if a user subscribed in Chrome, they will probably have a Google-controlled push service API. All endpoints expect the same API contract.

Push messages must be encrypted. This ensures that the end user can decrypt the message, but the intermediate push service cannot.

Instructions inside the push message include:

  • Time to live. If it can't be delivered immediately, how long is the message kept before it is discarded?
  • Urgency. Highly urgent messages may wake up the user's device immediately. Low urgency messages may preserve battery life and wake up less frequently.
  • Push message has a "topic" which is unique, and will replace any previous message with the same topic.

Step 3: Push the event

Once you've sent the push message, the push service will hold it until it can deliver to the browser, or the TTL expires.

Delivery wakes up a service worker's "push" event even if the corresponding web page is closed, or the browser is not running.

Subscribing a user

Feature detection

if (!('serviceWorker' in navigator)) { // Service Worker isn't supported on this browser, disable or hide UI. return; }
if (!('PushManager' in window)) { // Push isn't supported on this browser, disable or hide UI. return; }

Register a service worker

Register our service worker. This tells the browser to let this javascript file run inside a service worker environment.

function registerServiceWorker() {
  return navigator.serviceWorker.register('service-worker.js')
  .then(function(registration) {
    console.log('Service worker successfully registered.');
    return registration;
  })
  .catch(function(err) {
    console.error('Unable to register service worker.', err);
  });
}

I'm not sure whether I can say registration = registerServiceWorker() and promises will magically make that work as if it were all synchronous, or if I'm supposed to change return registration to real code that uses it. We'll find out later.

Request permission

The permission API used to take a callback, but now returns a promise. So we should support either API.

function askPermission() {
  return new Promise(function(resolve, reject) {
    const permissionResult = Notification.requestPermission(function(result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  })
  .then(function(permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error('We weren\'t granted permission.');
    }
  });
}

Subscribing a user with PushManager

Once our service worker is registered and we've received permission, we can subscribe a user by calling registration.pushManager.subscribe().

function subscribeUserToPush() {
  return getSWRegistration()
  .then(function(registration) {
    const subscribeOptions = {
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(
        'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
      )
    };

    return registration.pushManager.subscribe(subscribeOptions);
  })
  .then(function(pushSubscription) {
    console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
    return pushSubscription;
  });
}

So that there takes the service worker registration, and, assuming we've been granted permission, subscribes the user to push notifications, and returns the push subscription object.

Options

userVisibleOnly

Passing userVisibleOnly: true essentially says that every push will also display a user-visible notification. You must pass true. Later versions of the spec may have some way to support "silent" notifications that the service worker can act upon but the user does not see.

Chrome does not support non-user-visible notifications.

applicationServerKey

applicationServerKey is the public key from your application's public/private key pair. You will generate this and pass it into the subscription request. The browser will pass it along to its own web service to generate the push subscription object that contains the endpoint and is associated with your public key. When your web service later sends a message, it will be signed with your private key, so the notification endpoint can validate that it came from an authorized sender.

applicationServerKey is not strictly required, but you should use it.

VAPID keys and application server keys are the same thing.

Generating keys

Generate keys here: https://web-push-codelab.appspot.com/

Permisions and subscribe()

Your browser will totally request permission if you call subscribe(). For trivial examples, you can skip the whole Notification.requestPermission().

What is a PushSubscription?

We do something like the following to subscribe and generate a push subscription:

function subscribeUserToPush() {
  return getSWRegistration()
  .then(function(registration) {
    const subscribeOptions = {
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(
        'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
      )
    };

    return registration.pushManager.subscribe(subscribeOptions);
  })
  .then(function(pushSubscription) {
    console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
    return pushSubscription;
  });
}

The resulting object looks like this:

{
  "endpoint": "https://some.pushservice.com/something-unique",
  "keys": {
    "p256dh":
"BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
    "auth":"FPssNDTKnInHVndSTdbKFw=="
  }
}

To send a message, we'll do an HTTP POST to endpoint, and use the keys to encrypt the message.

Send a Subscription to Your Server

Uh, figure that out yourself. Take JSON.stringify(pushSubscription) and put it in your database.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.