Skip to main content

Silent Push Notifications on Google Chrome

First, I would recommend you read (or skim) the Google Developers post on Push Notifications on Google Chrome. The following are a few excerpts that caught my eye:

You’ll notice that we show a notification even when there is an error. This is because if we don’t, Chrome will show it’s own generic notification.

See also:
When can I use push without showing notifications (i.e. silent background push)? 
There is no timeline for when this will be available yet, but there is an intent to implement background syncand while it’s not decided or spec’d, there is some discussion of enabling silent push with background sync.
And under the limitations section:
you have to show a notification when you receive a push message
At the time I first experimented with the Push API for Google Chrome, the first quote wasn't in the blog post, though the second two were. Being a naturally inquisitive creature, I wondered why you had to show a notifications, and more specifically, what were the consequences of not doing so. For a project I happened to be working on, I wanted to show push notifications when the site was in the background or closed, but obviously not when the user was browsing the website, as that would be annoying. So I experimented.



I set up the web project I was working on to receive push notifications using a service worker much like the examples in the post. One key difference; Instead of the following push event handler:

self.addEventListener('push', function(event) {  
  console.log('Received a push message', event);

  var title = 'Yay a message.';  
  var body = 'We have received a push message.';  
  var icon = '/images/icon-192x192.png';  
  var tag = 'simple-push-demo-notification-tag';

  event.waitUntil(  
    self.registration.showNotification(title, {  
      body: body,  
      icon: icon,  
      tag: tag  
    })  
  );  
});

I instead chose to try:

self.addEventListener('push', function(event) {  
  console.log('Received a push message', event);

  var title = 'Yay a message.';  
  var body = 'We have received a push message.';  
  var icon = '/images/icon-192x192.png';  
  var tag = 'simple-push-demo-notification-tag';

  //event.waitUntil(  
  //  self.registration.showNotification(title, {  
  //    body: body,  
  //    icon: icon,  
  //    tag: tag  
  //  })  
  //);  
});

I did this to test the consequences and see what would happen. As I more or less expected by this point, I saw some output in the console indicating I had received a push message, but I did see any notification pop up. Interesting...

I decided to test this further. I then modified the code to be the following:

self.addEventListener('push', function(event) {  
  console.log('Received a push message', event);

  var title = 'Yay a message.';  
  var body = 'We have received a push message.';  
  var icon = '/images/icon-192x192.png';  
  var tag = 'simple-push-demo-notification-tag';

  if(window.globalBoolean) {
    event.waitUntil(  
      self.registration.showNotification(title, {  
        body: body,  
        icon: icon,  
        tag: tag  
      })  
    ); 
  } 
});

In the JavaScript console of my webpage, I experimented with setting the value of window.globalBoolean to true or false. Much to my amazement, the code (which, mind you, is in a ServiceWorker.js file running in the background) picked up on it! It would show push notifications when I set the globalBoolean to true, and stop showing them once I set them to false. However, as a fascinating caveat, I found that once I refreshed the webpage, setting globalBoolean no longer had any effect on the service worker displaying notifications; it was simply stuck to whatever I set it to prior to refreshing. Weird!

By this point I was very invested. I decided to do a little research on how to communicate from my webpage to my service worker, and I discovered there's actually a pretty simple little protocol for doing so. I modified my service worker code one last time to:

self.addEventListener('push', function(event) {  
  console.log('Received a push message', event);

  var title = 'Yay a message.';  
  var body = 'We have received a push message.';  
  var icon = '/images/icon-192x192.png';  
  var tag = 'simple-push-demo-notification-tag';

  if(self.showNotifications) {
    event.waitUntil(  
      self.registration.showNotification(title, {  
        body: body,  
        icon: icon,  
        tag: tag  
      })  
    ); 
  } 
});

Additionally, I added the following to my service worker as well:

self.showNotifications = false; //default value

//listen for messages
self.addEventListener('message', function(event) {
  self.showNotifications = event.data === "true";
});

As you might imagine, this piece of code listens for a message event; when it receives a message, if the data contains the string "true", it sets self.showNotifications to true, otherwise it sets self.showNotifications to false. Not exactly the greatest code, but a good place to start experimenting.

To my front-end client, I added the following function:

function turnPushNotificationsOn() {
  if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
    // This wraps the message posting/response in a promise, which will resolve if the response doesn't
    // contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
    // controller.postMessage() and set up the onmessage handler independently of a promise, but this is
    // a convenient wrapper.
    return new Promise(function(resolve, reject) {
      var messageChannel = new MessageChannel();
      messageChannel.port1.onmessage = function(event) {
        if (event.data.error) {
          reject(event.data.error);
        } else {
          resolve(event.data);
        }
      };

      // This sends the message data as well as transferring messageChannel.port2 to the service worker.
      // The service worker can then use the transferred port to reply via postMessage(), which
      // will in turn trigger the onmessage handler on messageChannel.port1.
      // See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
      navigator.serviceWorker.controller.postMessage("true", [messageChannel.port2]);
    });
  }
}

I borrowed much of the above code from an example I found online for how to send messages to your service worker from your front end. I repurposed it specifically to send a "true" message to the service worker when turnPushNotificationsOn() was called.

Sure enough, I found that because I set self.showNotifications to false by default, notifications were arriving but not displaying. Once I called my new client function, self.showNotifications was set to true, and notifications would start displaying. This setting persisted until I turned it off again (using a function similar to turnPushNotificationsOn, but which I won't include here because duh), and the functions would even work across page refreshes. I had my solution!

Is this a bug? Maybe. Is it hacky? Yeah, kinda. But does it work? Up until Chrome 48, the answer is yes. There is no consequence to doing this as far as I've been able to tell on Chrome for Windows, Mac, or Android. In the example I showed, all I did was use this to control whether push notifications were shown or not. I needed this for a project because I did not want push notifications to display while the user was on the site. While not showing push notifications for active apps is a given behavior on mobile operating systems, the same cannot be said for the web (yet). Needless to say, if that were the case, I would not have had to come up with this solution. I was able to use the above solution in conjunction with some handy event listeners like "visibilitychange" and "beforeunload" to only turn push notifications on when the site was in the background or closed. 

Push Notifications on web are a very young technology, and I'm very interested in seeing where they go, even if their behavior isn't quite there yet. That said, I did very much enjoy piecing together my own solution, and I hope this is helpful for any of you working on similar projects.

Comments

Popular posts from this blog

Decentralized Internet Considered Harmful

Today I was reading a really great story on TechCrunch about how the future of the internet could be serverless . I was immediately hooked in by the idea, myself being a big fan of P2P technologies that eliminate server usage. I've often pondered how this type of network could be built in the past, mostly coming up blank, but was excited that a company called MaidSafe might have figured it out. MaidSafe is a fully decentralized platform on which application developers can build decentralized applications. The network is made up by individual users who contribute storage, computing power and bandwidth to form a world-wide autonomous system. Their solution is essentially to replace server storage with a P2P-like network, where chunks of data are stored over the computing devices of many individuals. They go on to make promises about the inherent security of such a network, how it would provide an alternative revenue stream for developers, yada yada yada. Though an attractive ide

Cognitive Architecture

The assignment: Sum up the entire first half of my Cognitive Architecture seminar into five principles, in one page or less, using language that could be understood by any semi-intelligent human being. What I did: Turned the assignment on its head and created a vivid presentation showcasing each of the principles I identified (with a total amount of text occupying about one page). The result?  

'Her': On Truly Artifically Intelligent Assistants

The future promises the perfection of artificially intelligent personal assistants. 'Her', the recent movie starring Joaquin Pheonix and ScarJo as his "OS" delivers a compelling vision of a future in which intelligent personal assistants can be interacted as easily as with the person right next to you. Without spoiling much of the plot, I will say the lead character falls in love with his OS, which is contained within a small phone-like gadget and an earpiece, and they have to struggle to work out what a human-computer relationship means. It's a surprisingly powerful story that truly left me with questions such as "What is life?" and "What makes humans human?" and left my friends with questions such as "Is this even possible?" In this future, the humans interact with an "OS" (operating system) that, while initially as intelligent as a human, has access to a much wider array of knowledge and learns much faster than we simp