Menu

Creating Scheduled Push Notifications

April 8th, 2020

Scheduled is the keyword there — that’s a fairly new thing! When a push notification is scheduled (i.e. “Take your pill” or “You’ve got a flight in 3 hours”) that means it can be shown to the user even if they’ve gone offline. That’s an improvement from the past where push notification required the user being online.

So how do scheduled push notifications work? There are four key parts we’re going to look at:

  • Registering a Service Worker
  • Adding and removing scheduled push notifications
  • Enhancing push notifications with action buttons
  • Handling push notifications in the Service Worker

First, a little background

Push notifications are a great way to inform site users that something important has happened and that they might want to open our (web) app again. With the Notifications API — in combination with the Push API and the HTTP Web Push Protocol — the web became an easy way to send a push notification from a server to an application and display it on a device.

Creating Scheduled Push Notifications

You may have already seen this sort of thing evolve. For example, how often do you see some sort of alert to accept notifications from a website? While browser vendors are already working on solutions to make that less annoying (both Firefox and Chrome have outlined plans), Chrome 80 just started an origin trial for the new Notification Trigger API, which lets us create notifications triggered by different events rather than a server push alone. For now, however, time-based triggers are the only supported events we have. But other events, like geolocation-based triggers, are already planned.

Scheduling an event in JavaScript is pretty easy, but there is one problem. In our push notification scenario, we can’t be sure the application is running at the exact moment we want to show the notification. This means that we can’t just schedule it on an application layer. Instead, we’ll need to do it on a Service Worker level. That’s where the new API comes into play.

The Notification Trigger API is in an early feedback phase. You need to enable the #enable-experimental-web-platform-features flag in Chrome or you should register your application for an origin trial.

Also, the Service Worker API requires a secure connection over HTTPS. So, if you try it out on your machine, you’ll need to ensure that it’s served over HTTPS.

Setting things up

I created a very basic setup. We have one application.js file, one index.html file, and one service-worker.js file, as well as a couple of image assets.

/project-folder
├── index.html
├── application.js
├── service-worker.js
└── assets
   ├─ badge.png
   └── icon.png

You can find the full example of a basic Notification Trigger API demo on GitHub.

Registering a Service Worker

First, we need to register a Service Worker. For now, it will do nothing but log that the registration was successful.

// service-worker.js
// listen to the install event
self.addEventListener('install', event => console.log('ServiceWorker installed'));
<!-- index.html -->
<script>
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js');
  }
</script>

Setting up the push notification

Inside our application, we need to ask for the user’s permission to show notifications. From there, we’ll get our Service Worker registration and register a new notification for this scope. So far, nothing new.

The cool part is the new showTrigger property. This lets us define the conditions for displaying a notification. For now, we want to add a new TimestampTrigger, which accepts a timestamp. And since everything happens directly on the device, it also works offline.

// application.js
document.querySelector('#notification-button').onclick = async () => {
  const reg = await navigator.serviceWorker.getRegistration();
  Notification.requestPermission().then(permission => {
    if (permission !== 'granted') {
      alert('you need to allow push notifications');
    } else {
      const timestamp = new Date().getTime() + 5 * 1000; // now plus 5000ms
      reg.showNotification(
        'Demo Push Notification',
        {
          tag: timestamp, // a unique ID
          body: 'Hello World', // content of the push notification
          showTrigger: new TimestampTrigger(timestamp), // set the time for the push notification
          data: {
            url: window.location.href, // pass the current url to the notification
          },
          badge: './assets/badge.png',
          icon: './assets/icon.png',
        }
      );
    }
  });
};

Handling the notification

Right now, the notification should show up at the specified timestamp. But now we need a way to interact with it, and that’s where we need the Service Worker notificationclick and notificationclose events.

Both events listen to the relevant interactions and both can use the full potential of the Service Worker. For example, we could open a new window:

// service-worker.js
self.addEventListener('notificationclick', event => {
  event.waitUntil(self.clients.openWindow('/'));
});

That’s a pretty straightforward example. But with the power of the Service Worker, we can do a lot more. Let’s check if the required window is already open and only open a new one if it isn’t.

// service-worker.js
self.addEventListener('notificationclick', event => {
  event.waitUntil(self.clients.matchAll().then(clients => {
    if (clients.length){ // check if at least one tab is already open
      clients[0].focus();
    } else {
      self.clients.openWindow('/');
    }
  }));
});

Notification actions

Another great way to facilitate interaction with users is to add predefined actions to the notifications. We could, for example, let them choose if they want to dismiss the notification or open the app.

// application.js
reg.showNotification(
  'Demo Push Notification',
  {
    tag: timestamp, // a unique ID
    body: 'Hello World', // content of the push notification
    showTrigger: new TimestampTrigger(timestamp), // set the time for the push notification
    data: {
      url: window.location.href, // pass the current url to the notification
    },
    badge: './assets/badge.png',
    icon: './assets/icon.png',
    actions: [
      {
        action: 'open',
        title: 'Open app’
      },
      {
        action: 'close',
        title: 'Close notification',
      }
    ]
  }
);

Now we use those notifications inside the Service Worker.

// service-worker.js
self.addEventListener('notificationclick', event => {
  if (event.action === 'close') {
    event.notification.close();
  } else {
    self.clients.openWindow('/');
  }
});

Cancelling push notifications

It’s also possible to cancel pending notifications. In this case, we need to get all pending notifications from the Service Worker and then close them before they are sent to the device.

// application.js
document.querySelector('#notification-cancel').onclick = async () => {
  const reg = await navigator.serviceWorker.getRegistration();
  const notifications = await reg.getNotifications({
    includeTriggered: true
  });
  notifications.forEach(notification => notification.close());
  alert(`${notifications.length} notification(s) cancelled`);
};

Communication

The last step is to set up the communication between the app and the Service Worker using the postMessage method on the Service Worker clients. Let’s say we want to notify the tab that’s already active that a push notification click happened.

// service-worker.js
self.addEventListener('notificationclick', event => {
  event.waitUntil(self.clients.matchAll().then(clients => {
    if(clients.length){ // check if at least one tab is already open
      clients[0].focus();
      clients[0].postMessage('Push notification clicked!');
    } else {
      self.clients.openWindow('/');
    }
  }));
});
// application.js
navigator.serviceWorker.addEventListener('message', event => console.log(event.data));

Summary

The Notification API is a very powerful feature to enhance the mobile experience of web applications. Thanks to the arrival of  the Notification Trigger API, it just got a very important improvement. The API is still under development, so now is the perfect time to play around with it and give feedback to the developers.

If you are working with Vue or React, I’d recommend you take a look at my own Progressive Web App demo. It includes a documented example using the Notification Trigger API for both frameworks that looks like this:

Creating Scheduled Push Notifications

The original post Creating Scheduled Push Notifications.