Part-2:​​​​​​​ Web Push Notification

Setting up Push Notification on the client side 

 Welcome back to the 2nd post of the notification series. In this post, we will understand and will go through some client side coding part i.e Register Service worker, Service worker implementation, creating Subscription  Object and send it to the backend to save in the DB for future use.

First Let us understand some concept which is required to understand the code.so Let's begin with some cheer like HOZZZ the JOSH(Prem se bolo Jai Mata Di)Just Kidding.::)   

If you haven't yet gone through the previous blog then click here to read.

What is a Service Worker?

service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don't need a web page or user interaction. Today, they already include features like push notifications, Displaying Offline webpages and background sync.

Below is the Lifecycle of Service Worker.



For More Information Visit Here

Note:  We Need to be turned off the minifier  for successful execution of service worker.We can do by putting  /* MINIFIER : OFF */  at the start of our code and again turned on by  /* MINIFIER: ON */  at the end of our code.

Let's  Dive into Code Step-By-Step

1.Preparing The Service Worker

1. First we need to install the service worker and we can also cache the web pages by URL into the browser for offline support.

self.addEventListener('install',function(e){
        e.waitUntil(caches.open('pushNotification').then(function(cache){
            console.log("cahces "+JSON.stringify(cache));
            return cache.addAll([
                                 '/'
                               ]);
        }));
    });

In cache. addAll method we can cache all the web pages by URL I have cache only home URL.

Note: The cache API may not work in every browser.

2. Adding Event for fetch when will we will use to communicate with the server.

self.addEventListener('fetch',function(event){
        event.respondWith(caches.match(event.request).then(function(response){
            return response || fetch(event.request);
        }));
    }); 

3.Let's Add Listener for push

self.addEventListener('push',function(event){
        var serverData=event.data.json();
        if(serverData){
            var notifiBody=serverData.body;
            var imageIcon=serverData.imageUrl;
            var rUrl=serverData.redirectUrl;
            self.registration.showNotification(serverData.title,{
                body : notifiBody,
                icon : imageIcon,
                data: {
                  dateOfArrival: Date.now(),
                  primaryKey: 1,
                  redirectUrl : rUrl
                },
                timeout : 1000
            });
        }else{
            console.log("There is no data to be displayed.");
        }
    });

when we send the notification from the server this will define the data of the popup and we can organize also as per our requirement.

 4. Now we want to define the behavior on the click on the popup.

self.addEventListener('notificationclick', function(event) {
        var url = event.notification.data.redirectUrl;
        event.waitUntil(
            clients.matchAll({type: 'window'}).then( windowClients => {
                for (var i = 0; i < windowClients.length; i++) {
                    var client = windowClients[i];
                    if (client.url === url && 'focus' in client) {
                        return client.focus();
                    }
                }
                if (clients.openWindow) {
                    return clients.openWindow(url);
                }
            })
        );
    }); 

 

We have prepared service worker now it will able to catch the push message from the server asynchronously.

Note : self  define the instance of service worker itself.

Now we will register our Service Worker from into our code.

We also need to create manifest .json file in our for authentication for google chrome.

{
    "gcm_sender_id":"221518006527",
    "subject": "mailto:our own subject",
    "publicKey": "your own public key",
    "privateKey": "your own private key" 
}

Note: gcm_sender_id is must to the successful execution of service worker in chrome otherwise in the backend you will get an error called HTTP:403 unautherized or unregistered 

Click here To generate VAPIDS Keys.

Now we will register our service worker to the browser and enable push browser to ask for notification and sends a welcome notification to user. 

<script>
var swJsUrl='<%=request.getContextPath()%>';
var url='<%=resourceURL%>';
var requestPerm;
    /* MINIFIER: OFF */
    
    window.onload=function(){
        var model='<div id="myModal" class="modal fade" role="dialog">'+
          '<div class="modal-dialog">'+
            '<div class="modal-content">'+
              '<div class="modal-header">'+
                '<button type="button" class="close" data-dismiss="modal">&times;</button>'+
                '<h4 class="modal-title">Enable Push Notification</h4>'+
              '</div>'+
              '<div class="modal-body">'+
                '<h1 class="">Please Enable the notification to get the latest notification.</h1><br/>'+
                '<button class="btn-lg btn-default blueTheme" onclick="requestPermission()">Notify Me</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button type="button" class="btn-lg btn-default blueTheme" data-dismiss="modal">Not Now</button>'+
              '</div>'+
            '</div>'+
          '</div>'+
        '</div>';
        if(Notification.permission === 'default' ){
            var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
            if(fs){
                $("#notify").html(model);
                $('#myModal').modal('show');
            }else{
                $("#notify").html(model);
                $('#myModal').modal('show');
             }
        }
    };
    
    
function requestPermission(){
        if(Notification.permission === 'default' || Notification.permission === 'granted'){
            if(Notification.permission === 'granted'){
                Notification.requestPermission();
            }
            if('serviceWorker' in navigator){
                navigator.serviceWorker.register(swJsUrl+'/sw.js').then(function(registration){
                    var serviceWorker;
                    if (registration.installing) {
                        serviceWorker = registration.installing;
                    } else if (registration.waiting) {
                        serviceWorker = registration.waiting;
                    } else if (registration.active) {
                        serviceWorker = registration.active;
                    }
                    if (serviceWorker) {
                        if (serviceWorker.state == "activated") {
                            initalize(registration);
                        }
                        serviceWorker.addEventListener("statechange", function(e) {
                            if (e.target.state == "activated") {
                                initalize(registration);
                            }
                        });
                    }
                },{scope:'/'}).catch(function(err){
                    console.log(err);
                }); 
            }else{
                console.log("Service Worker is not supported in this browser.");
            }
        }
}
     function initalize(registration){
            if(!('showNotification' in ServiceWorkerRegistration.prototype)){
                return;
            }
            if(Notification.permission === 'denied'){
                var oldNotification=Notification;
                $('#myModal').modal('hide');
                return;
            }
            if(!('PushManager' in window)){
                return;
            }
            registration.pushManager.getSubscription().then(function(subscription){
                if(!subscription){
                    subscribe(registration);
                    return;
                }
                sendSubscriptionToServer(subscription);
            }).catch(function(err){
                console.warn("Error During getSubscription()",err);
            }); 
         
    } 
    
     function subscribe(registration){
        var applicationServerPublicKey="your own public key";
        var browserOptions={};
         if(typeof InstallTrigger !== 'undefined'){
            browserOptions={userVisibleOnly: true};
         }else{
               browserOptions={
                    userVisibleOnly: true,
                    applicationServerKey: urlB64ToUint8Array(applicationServerPublicKey),
                };   
         }
         registration.pushManager.subscribe(browserOptions).then(function(subscription){
                     $('#myModal').modal('hide');
                        return sendSubscriptionToServer(subscription);
                    }).catch(function(err){
                        if (Notification.permission === 'denied') {
                            console.warn('Permission for Notifications was denied');
                        } else {
                            console.log(JSON.stringify(err));
                          }
        });
    }
    
      function sendSubscriptionToServer(subscription){
        var key = subscription.getKey ? subscription.getKey('p256dh') : '';
        var auth = subscription.getKey ? subscription.getKey('auth') : '';
        var endpoint= subscription.endpoint;
        key= key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '';
        auth= auth ? btoa(String.fromCharCode.apply(null, new Uint8Array(auth))) : '';
        console.log("permission is : "+Notification.permission);
        if(Notification.permission === 'granted'){
            requestPerm='granted';
        }else if(Notification.permission === 'denied'){
            requestPerm='denied';
        }else{
            requestPerm='default';
        }
        
        url+='&<portlet:namespace/>browserUrl='+endpoint+'&<portlet:namespace/>p256dh='+key+'&<portlet:namespace/>auth='+auth+"&<portlet:namespace/>permission="+requestPerm;
        url=new Request(url,{'credentials': 'same-origin'});
        return fetch(url).then(function(resp){
            console.log(resp);
        }).catch(function(err){
            console.log(err);
        }); 
    }
      
     function urlB64ToUint8Array(base64String){
        var padding = "=".repeat((4 - base64String.length % 4) % 4);
        var base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/");
        var rawData = window.atob(base64);
        var outputArray = new Uint8Array(rawData.length);
        for (var i = 0; i < rawData.length; ++i) {
          outputArray[i] = rawData.charCodeAt(i);
        }
        return outputArray;
    }    
    /* MINIFIER: ON */ 
</script>

Here My implementation is to ask from a popup for notification weather user wants notification or first will check if the browser is supported notification API or not by ' serviceWorker ' in navigator. not then if users allow the notification then we will register the service worker through thennavigator .serviceWorker.register we will check whether show notification  is supported by this browser or not by  'showNotification' in ServiceWorkerRegistration.prototype

Then we will get the subscription object from the registration object by registration.pushManager.getSubscription()​  ,the main part of the application for chrome browser is registration.pushManager.subscribe( browserOptions ) in this optional we have to pass the otherwiseapplicationServerKey : urlB64ToUint8Array(applicationServerPublicKey) chrome will not accept the push message response saying that it is unauthorized due to a security issue.

Here we have main parameters to pass in the request which are the unique property for every browser to identify itself. 

 subscription.getKey ? subscription.getKey('p256dh') : '' encryption algo key
subscription.getKey ? subscription.getKey('auth') : '' auth key for browser
subscription.endpoint is the unique URL of the browser 

Below i have checked whether it is a private window or not 

if(Notification.permission === 'default' ){
    var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    if(fs){
          $("#notify").html(model);
          $('#myModal').modal('show');
     }else{
          $("#notify").html(model);
          $('#myModal').modal('show');
     }
}

From fetch send all the details to the server and it will wait until the request completes and revert a response or error. 

Note: In this everywhere we are using javascript new feature JavaScript  is  promise  which represent processes which are already happening, which can be chained with callback functions.For more Info Visit Here.

Now we are ready to go server-side programming in the next part.

If you have any doubt on above code or an explanation i will be happy to listen to you and if any correction required then kindly let me know.

Till then Enjoy coding and kindly share if you find it helpful.............................:::)))