In this article, we will discuss about sending browser push notifications using JavaScript. The image given below is a sample browser push notification.
Browser push notifications

Picture credit: wingify

There are so many third party vendors who are providing this service with basic subscription costing around 25-30$/month

Some of them are: moengage, pushcrew, pushengage, izooto, more…

So if you are a JavaScript developer, you will be able to do this without any third party subscription. Let me explain it in detail.

What is a Service Worker?

It is a script that runs silently in the background of a browser which is  registered against an origin and path. A registered service worker doesn’t need the webpage to be opened in order to work. It runs in a separate thread which is non-blocking in manner and is designed to be asynchronous hence we cannot use APIs such as synchronous XHR and local storage inside a service worker.

Service workers are so powerful. If you are a black hat, you can use it to hijack network connections and reconstruct their response. For such security reasons, they are allowed to be run only over HTTPS. For development, you can use localhost which is considered as a secure origin.

Support

Service workers are currently  supported in Chrome (version 42 and above), Firefox (version 44 and above),and opera(version 27 and above) on desktop. On mobiles, it is available on Android (version 4 and above) default browser and on Android Chrome. For Edge,this technology is under development and for Safari it is under consideration.

To check for installed service workers on your browser for different domains;

For Chrome:
chrome://serviceworker-internals

Browser push notifications

For Firefox:
about:serviceworkers

Browser push notifications

And in your js script, you can use below expression to check for the support of service worker for any browser.

('serviceWorker' in navigator) //returns true if service worker is supported in the browser/.

 

Service Worker Life Cycle

Browser push notifications
Now lets start building the first Web push Notification application with payload.

  • Create a project in Google Developer Console and generate a GCM browser API key which you will be using in your node script later.
  • Project Structure:

Browser push notifications

  • Implementation:

First we will create a file called service-worker.js which will add event listeners to the service worker workspace up on registration.

"use strict";

var url = [];
var count = 0;

self.addEventListener('install', function(event) {
  event.waitUntil(self.skipWaiting()); //will install the service worker
});

self.addEventListener('activate', function(event) {
  event.waitUntil(self.clients.claim()); //will activate the serviceworker
});

// Register event listener for the 'notificationclick' event.
self.addEventListener('notificationclick', function(event) {
  event.notification.close();

  event.waitUntil(
    clients.matchAll({
      type: "window"
    })
    .then(function(clientList) {

      if (clients.openWindow) {
        var c = count;
        count++;
        return clients.openWindow(url[c]);
      }
    })
  );

});


self.addEventListener('push', function(event) {
  event.waitUntil(
    self.registration.pushManager.getSubscription()
    .then(function(subscription) {

      console.log("subscription", subscription);

      var payload = event.data ? JSON.parse(event.data.text()) : {
        title: 'Cronj IT Technoliges Pvt Ltd',
        body: 'CronJ combines the power of creativity and experience to employ a highly skilled team of Information Technology professionals who provide high-quality, high-value custom IT solutions to a variety of business enterprises.',
        icon: 'https://static.cronj.com/img/logos/cronj-logo.png',
        url: 'https://www.cronj.com'
      };


      url.push(payload.url);
      return self.registration.showNotification(payload.title, {
        body: payload.body,
        icon: payload.icon,
        tag: payload.url + payload.body + payload.icon + payload.title
      });


    })
  );
});

 

Now we will create index.js file where service worker registration is initiated.

var endpoint;
var key;
var authSecret;

// Register a Service Worker.
navigator.serviceWorker.register('service-worker.js')
  .then(function(registration) {
    // Use the PushManager to get the user's subscription to the push service.

    //service worker.ready will return the promise once the service worker is registered. This can help to get rid of
    //errors that occur while fetching subscription information before registration of the service worker

    return navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
      return serviceWorkerRegistration.pushManager.getSubscription()
        .then(function(subscription) {

          // If a subscription was found, return it.
          if (subscription) {
            return subscription;
          }

          // Otherwise, subscribe the user (userVisibleOnly allows to specify that we don't plan to
          // send browser push notifications that don't have a visible effect for the user).
          return serviceWorkerRegistration.pushManager.subscribe({
            userVisibleOnly: true
          });
        });

    });

  }).then(function(subscription) { //chaining the subscription promise object

    // Retrieve the user's public key.
    var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
    key = rawKey ?
      btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) :
      '';
    var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
    authSecret = rawAuthSecret ?
      btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) :
      '';

    endpoint = subscription.endpoint;

    // Send the subscription details to the server using the Fetch API.

    fetch('/register', {
      method: 'post',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify({
        endpoint: subscription.endpoint,
        key: key,
        authSecret: authSecret,
      }),
    });

  });

// Ask the server to send the client a notification (for testing purposes, in real
// applications the notification will be generated by some event on the server).
document.getElementById('doIt').addEventListener('click', function() {

  fetch('/sendNotification', {
    method: 'post',
    headers: {
      'Content-type': 'application/json'
    },
    body: JSON.stringify({
      endpoint: endpoint,
      key: key,
      authSecret: authSecret,
      title: document.getElementById("notificationTitle").value,
      body: document.getElementById("notificationBody").value,
      icon: document.getElementById("notificationIcon").value,
      link: document.getElementById("notificationLink").value
    }),
  });
});
 

your manifest.json

{
  "name": "Push Notifications",
  "short_name": "push Notifications",
  "start_url": "./index.html",
  "display": "standalone",
  "gcm_sender_id": "xxxxxxxxxxx",
  "gcm_user_visible_only": true
}

Include the script in your index.html file

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>Service Workers -Push Notifications</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="manifest" href="manifest.json">
</head>

<body>
    <span> Title</span>
    <input type="text" id="notificationTitle" />
    <br/>
    <span> body</span>
    <input type="text" id="notificationBody" />
    <br/>
    <span> Icon Url</span>
 <input type="url" id="notificationIcon" />
 <br/>
 <span> Link</span>
 <input type="url" id="notificationLink" />
 <br/>
 <button id="doIt">Send notification</button>
 <script src="./index.js"></script>
</body>

</html>

your server side script will be in server.js

“web-push”: “^2.2.0”

var express = require('express');
var app = express();
var webPush = require('web-push');
var bodyParser = require('body-parser')


app.set('port', 5000);
app.use(express.static(__dirname + '/'));

app.use(bodyParser.json())

webPush.setGCMAPIKey(process.env.GCM_API_KEY);

app.post('/register', function(req, res) {
  // A real world application would store the subscription info.
  res.sendStatus(201);
});

app.post('/sendNotification', function(req, res) {

  console.log(req.body)

  webPush.sendNotification(req.body.endpoint, {
      payload: JSON.stringify({
        'title': req.body.title,
        'icon': req.body.icon,
        'body': req.body.body,
        'url': req.body.link
      }),
      userPublicKey: req.body.key,
      userAuth: req.body.authSecret,
    })
    .then(function() {
      console.log("sent push")
      res.sendStatus(201);
    }, function(err) {
      console.log('webpusherr', err);

    });

});


app.listen(app.get('port'), function() {
  console.log('Node app is running on port', app.get('port'));
});

Note: Hope the code is self explanatory as we have put comments everywhere.

Running your project:

  • Install nodeJS
  • Install node modules
    DO >npm install  in the root folder which will install the node dependencies that are in the package.json file.
  • Now run below commad to start your server
     >node server
  • Now open http://localhost:5000 on your browser to see the below form.

Browser push notifications

  • Click “Allow” to let the browser allow notifications from the domain “http://localhost:5000
  • Browser push notificationsFill the form and send notification
  • Browser push notificationsYAY! GOT IT

Browser push notifications What if your production site doesn’t use HTTP’s.

You can go with a third party library where you need to buy a basic subscription that costs around 25 – 30$/month.
Let me explain what do they do to your HTTP site. When user wants to  activate browser push notifications on your http site, they will open a popup window in HTTP’s having their domain name and with a sub domain(usually your domain name) of your choice. So the moment a user registers, a service worker will be registered under this domain name.

An example would be
e.g. https://cronj.notificationcrew.com

where ‘notificationcrew’ is the third party vendors domain name. There is no workaround without a HTTP’s site. So if you are having some other HTTP’s site, you can create a sub domain having name same as  your HTTP domain name. Let’s say https://cronj.com is your HTTP site and you have some other HTTP’s site which is as https://subscribe.com . Your subdomain would be something like  https://cronj.subscribe.com

Now here you can gather browser endpoint data by opening a popup window on your HTTP site (https://cronj.com).The popup window will be served over HTTP’s which will hold all your service worker scripts. e.g: https://cronj.subscribe.com/notificationsubscribe.html will be opened in a popup that can have markup and service worker scripts included.
You need to “Allow” notifications on the browser default popup which means browser will allow notifications that are registered under this domain name.Service workers will now be registered in your browser and you will be saving the browser subscription info in  your application database by making an ajax call.
As now you have the browser endpoints data with you,your server can request GCM to fire a notification to these endpoints.

At this point,what if  the user wants to unsubscribe from the notifications.
You have notifications activated, but you cannot have access to the service worker subscription data from a HTTP domain. It is not recommended to open a popup on HTTP’s every time the user wants to unsubscribe/resubscribe to notifications.

Here Comes the browser fingerprinting,
Fingerprinting is a technique which is outlined in the research by Electronic Frontier Foundation, of anonymously identifying a web browser with accuracy of up to 94%. As they explained, “Browser is queried its agent string, screen resolution and color depth, installed plugins with supported mime types, timezone offset and other capabilities, such as local storage and session storage. Then these values are passed through a hashing function to produce a fingerprint that gives weak guarantees of uniqueness”.

Since the accuracy is 94%, we will use fingerprint + ip address for 100% accuracy.

We will store this unique identification (fingerprint + ip address) of the browser along with the push subscription data in the database. When user visits your HTTP site, you can generate the fingerprint again and check for the push subscription details in the database. You can even have a flag like DND(Do Not Disturb) which you could reset every time the user unsubscribe/re-subscribe from the HTTP domain.

Update:
Now you can clone from our public repository and use it.
https://bitbucket.org/cronj-dev/browser-push-notification

Looking for NodeJS Development Company, your search ends here!

26,908 total views, 18 views today

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmail
  1. Server gives error [Error: You must pass in a subscription with at least an endpoint.] and sendNotification is keeps on pending and at last show np response.

  2. Please explain about the functions:
    1. fetch(‘/register’, { }); in index.js
    2. fetch(‘/sendNotification’, {}); in index.js
    3. app.post(‘/register’, function(req, res) {}); in server.js
    4. app.post(‘/sendNotification’, function(req, res) {}); in server.js

    5. from where it will fetch the GCM_API_KEY:
    webPush.setGCMAPIKey(process.env.GCM_API_KEY);

    where to specify it,

    • HI harpreeth,
      Here is the explanation:
      1) ‘/register’ in index.js is a http post method call to the server.Here we will send the browser endpoint,user public key, user auth secret to the server. A real world application would save these details in the database so that you can send push notification to this browser anytime on demand.
      2) ‘/sendNotification’ is a http post method which is called on button click.Here we are just sending the user subscription data(browser endpoint,user public key, user auth secret) and payload data (Title, Description,Image url, Link url) in method body to the server.From the server we are parsing the body and firing notification.
      3) app.post(‘/register’, function(req, res) {}); in server.js is the express route we defined to listen to any http post method calls from either a client or server(In the above tutorial,we are calling it from client).
      4)same as point.3
      5)For testing purpose, you can use webPush.setGCMAPIKey(YOUR_API_KEY); directly and make it work.But for production environment or while pushing it to an online repository,it is not recommended to hardcode directly as others who have access can misuse it.For such reasons,we are using environment variables.Read below to know more about setting and getiing environment variables in nodejs.
      https://scotch.io/tutorials/how-to-use-environment-variables

  3. Very informative tutorial.
    I have a web application which is already in production. How can I add this push notification functionality to the application.? How to get the api key and add to my application?

    • Hi Stanly Thomas,
      Here is the link to source code at bitbucket. you can clone it and use it.you can modify the script as per your requirements.
      https://bitbucket.org/cronj-dev/browser-push-notification
      I highly recommend testing this service fully in staging first and then do the deployment to production. It is not recommended to update service worker.js file too often(in production). As service worker.js will be running in the browser background, there is a high chance that it might create dulpicate background scripts resulting duplicate notifications to users. To get rid of such situations again you have to do browser cache bursting, etc…
      To get the api key, you need to register your application in google developer console.
      https://console.developers.google.com/

  4. siripuram thirupathi

    Hi Nithish Reddy Jalumuri,

    can we have file to download the code and we can implement in our project

    Thanks in advance
    siripuram Thirupathi

  5. hi nitesh ,
    i got an error on
    1. fetch(‘/register’, { }); in index.js (response status 404 not found)
    2. fetch(‘/sendNotification’, {}); in index.js

    please , let me know how to resolve this error and what we have to add in place of ‘fetch’ code.

  6. hi Nithish Reddy Jalumuri ,
    i got an error on
    1. fetch(‘/register’, { }); in index.js (response status 404 not found)
    2. fetch(‘/sendNotification’, {}); in index.js

    please , let me know how to resolve this error and what we have to add in place of ‘fetch’ code.

    Reply

    • Hello Ali,
      you can include the service worker script in your home page which will register the service worker in the user’s browser when someone opens it.
      And you can write a php script that will make a request to GCM when a notification is need to be sent.
      Using curl you can do something like below:
      curl –header “Authorization: key=browserapikey” –header “Content-Type: application/json” https://android.googleapis.com/gcm/send -d “{\”registration_ids\”:[registrationid]}”

  7. Hi,
    I am getting below error, can you please help me

    title: ‘hello’,
    body: ‘hello’,
    icon: ‘https://static.cronj.com/img/logos/cronj-logo.png’,
    link: ‘https://www.cronj.com’ }
    webpusherr { [WebPushError: Received unexpected response code]
    name: ‘WebPushError’,
    message: ‘Received unexpected response code’,
    statusCode: 400,
    headers:
    { ‘content-type’: ‘text/html; charset=UTF-8’,
    date: ‘Sat, 22 Apr 2017 12:41:27 GMT’,
    expires: ‘Sat, 22 Apr 2017 12:41:27 GMT’,
    ‘cache-control’: ‘private, max-age=0’,
    ‘x-content-type-options’: ‘nosniff’,
    ‘x-frame-options’: ‘SAMEORIGIN’,
    ‘x-xss-protection’: ‘1; mode=block’,
    server: ‘GSE’,
    ‘alt-svc’: ‘quic=”:443″; ma=2592000; v=”37,36,35″‘,
    ‘accept-ranges’: ‘none’,
    vary: ‘Accept-Encoding’,
    connection: ‘close’ },
    body: ‘\n\nUnauthorizedRegistration\n\n\nUnauthorizedRegistration\nError 400\n\n\n’,

  8. Hello!
    Could you please explain what this part is?

    var payload = event.data ? JSON.parse(event.data.text()) : {
    title: ‘Cronj IT Technoliges Pvt Ltd’,
    body: ‘CronJ combines the power of creativity and experience to employ a highly skilled team of Information Technology professionals who provide high-quality, high-value custom IT solutions to a variety of business enterprises.’,
    icon: ‘https://static.cronj.com/img/logos/cronj-logo.png’,
    url: ‘https://www.cronj.com’
    };

    Should it be triggered when the user subscribes or what? The code seems to work fine (windows 8.1), the push notifications go trough, but I can’t see that message anywhere. If this is something else (but please explain anyway), how could I show a message when the user subscribes/unsubscribes? Thanks!

    • Hello Marshall,

      At receiver end (browser), i just created a default object to show when an empty push notification is sent from the server.Please note that a ternary operator has been used to show hardcoded object when there is no data received in the payload from the server.

      Have a look at index.js
      It has a ‘/register’ ajax call to server.In the serevr script you can instantly fetch browser endpoints and fire a notification after saving these endpoints to the database.

  9. I’m testing this on my AWS cloud server and everything works up to Click “Allow” to let the browser allow notifications from the my https domain. I receive the initial permissions notification and responded with allow.

    But when I Fill the form and send notification utilizing my https domain nothing happens? Any suggestions for what I might need to change would be greatly appreciated.

    Thanks

    Frank

  10. Hi Nithish Reddy Jalumuri,
    i got an error on
    1. fetch(‘/register’, { }); in index.js (response status 404 not found)
    2. fetch(‘/sendNotification’, {}); in index.js

    please , let me know how to resolve this error and what we have to add in place of ‘fetch’ code.

  11. \webpush_notification\node_modules\web-push\src\web-push-lib.js:35
    throw new Error(‘The GCM API Key should be a non-empty string or null.’);
    ^

    i am getting this type error.

      • In server.js file, make sure you set the GCM API key properly
        webPush.setGCMAPIKey(process.env.GCM_API_KEY);
        or you can check the env variable by doing console.log(process.env.GCM_API_KEY)

        For testing purpose, you can hardcode the GCM API key by passing the string directly as below:
        webPush.setGCMAPIKey(“YOUR_GCM_API_KEY”);

  12. I’m getting following error on click of send Notification :

    webpusherr Error: You must pass in a subscription with at least an endpoint.

  13. I am getting this error when trying to send a push notification:

    Error: You must pass in a subscription with at least an endpoint

    How would I fix this?

  14. For every one getting this issue “You must pass in a subscription with at least an endpoint.”

    Please replace :

    webPush.sendNotification(req.body.endpoint, {
    payload: JSON.stringify({
    ‘title’: req.body.title,
    ‘icon’: req.body.icon,
    ‘body’: req.body.body,
    ‘url’: req.body.link
    }),
    userPublicKey: req.body.key,
    userAuth: req.body.authSecret,
    })
    .then(function() {
    console.log(“sent push”)
    res.sendStatus(201);
    }, function(err) {
    console.log(‘webpusherr’, err);

    });

    With :

    var payload = JSON.stringify({
    ‘title’: req.body.title,
    ‘icon’: ”,
    ‘body’: req.body.body,
    ‘url’: ”
    });

    webPush.sendNotification({
    endpoint: req.body.endpoint,
    keys: {
    p256dh: req.body.key,
    auth: req.body.authSecret
    }
    }, payload).then(function() {
    console.log(“sent push”)
    res.sendStatus(201);
    }, function(err) {
    console.log(‘webpusherr’, err);

    });

    });

    NOTE: AFTER THIS I AM GETTING A DIFFERENT ERROR “UnauthorizedRegistration”, hope someone can help with the same

Leave a Reply

Your email address will not be published. Required fields are marked *