loading...

I don't use whatsApp so i built messaging system with PWA for myself.

jswalker_in profile image jswalker.in ・4 min read

Alt Text

 [Ingredients Require]

      * [Node.js + Redis]
      * [Service Worker setting]
      * [Firebase API]
      * [IndexedDB]

[Node.js]

It helps the API wrapper to fulfill the registration and syncing process of user meta info.
I use Redis database to kept every user entry as one key like :

set trunk_user_hash JSON.stringify(_user_info_)

[Service Worker]

The service worker keeps alive(Till lifecycle of the worker in the background) to facilitate with the purpose.

The first one is to keep the cache of an array of files and the second one is to deal with the Firebase web-push Event Listener.

//List all files path that you want to cache in service worker for offline usage.

var filesToCache = ['./js/js-files.js','./css/css-files.js'];

//identify direct URL call not cached URL of service worker

var _direct_url_=['https://fcm.googleapis.com/fcm/send','/api/call'];

var staticCacheName="version_1";

    self.addEventListener('install', event => {
       event.waitUntil(
        caches.open(staticCacheName).then(cache => 
        cache.addAll(filesToCache)).then(self.skipWaiting()
       ));
    });      

    self.addEventListener('activate', function(event) {
       var cacheWhitelist = [staticCacheName];
        event.waitUntil(
          caches.keys().then(function(keyList) {
           return Promise.all(keyList.map(function(key) {
             if (cacheWhitelist.indexOf(key) === -1) {
              return caches.delete(key);
             }
           }));
         }));
     });   

     self.addEventListener('fetch', function(event) {
        var i=0,len=_direct_url_.length;flag = 0;
        for(i=0;i<len;i++){
          if(event.request.url.indexOf(_direct_url_[i])>-1){
             flag=1;
             break;
          }
        }
        if(flag==1){
           return;
        }else{
           event.respondWith(                 
              caches.match(event.request).then(function(response){
               if (response) {
                 return response;
               }
               return fetch(event.request);
              }).catch(function(error) {
            }));
         }
      });

[Firebase Events]

    self.addEventListener('push', function(event) {
      var _json_ = JSON.parse(event.data.text());
      console.log(_json_.data.notification);
      console.log(_json_.data.notification.body);

     //Do your magic here
     //Additional code such as time delay,push into idb

      var body = _json_.data.notification;

      //Show Web-push on device
     event.waitUntil(self.registration.showNotification(
       "Title", 
        {icon: 'Icon-Image',
          badge: 'Small-badge-image',
          actions: [
            {action: 'action_1', title: 'show me'},
            {action: 'action_2', title: 'Dont'}
          ],
          vibrate: [3000], /*Three sercond of beep*/
          body: body.body,
          tag:"Unique or same tag for each notification",
          data: body
     }).then(() => 
       self.registration.getNotifications()).then(notifications=>{
          var msg_body={}; // Feed the payload

         self.clients.matchAll().then((clients) => {
           clients.map((client) => {
                   return client.postMessage(JSON.stringify(msg_body));
               })
         });

         // Push message in indexedDB for conversation history  
          push_msg(payload);
       });
   });

 self.addEventListener('notificationclick', function(event) {
   var eventURL = event.notification;
        event.notification.close();
        event.waitUntil(clients.matchAll({type: "window"     
          }).then(function(clientList) {
          for (var i = 0; i < clientList.length; i++) {
             var client = clientList[i];
               if (clients.openWindow){
         return clients.openWindow("Full-URL");         
           }
      }
    }));
  });

[Firebase]

Include web push standalone js files from firebase : (https://firebase.google.com/docs/web/setup)

On Document-Ready call following snippet to activate Firebase

var messaging = firebase.messaging();
firebase.initializeApp({'messagingSenderId': 'YOUR-SENDER-ID'});

 navigator.serviceWorker.register('./firebase-messaging-sw.js', {
        scope: './'
    }).then(function(registration) {
        console.log('ServiceWorker registerd');
        messaging.useServiceWorker(registration);
    }).then(() => {
        return messaging.requestPermission();
    }).then(() => {
        try{
           return messaging.getToken();
        }catch(err){
           console.log(err);
           return false;    
        }
    }).then((token) => {
        console.log(token); 
    // Store this token locally + into Redis along with user credential and meta data

}).catch((err) => {
        console.log('Messaging Service not working');
        console.log(err);
});

firebase.initializeApp({'messagingSenderId': 'YOUR-SENDER-ID'});

/*Ajax Call to send message to specific user*/

$.ajax({
                    type: 'POST',
                    url: "https://fcm.googleapis.com/fcm/send",
                    headers: {
                        "Authorization": "key=[FIREBASE-AUTH-KEY]",
                        "content-type": "application/json",
                    },
                    contentType: 'application/json',
                    dataType: 'json',                    
                    data: JSON.stringify({
                        body: "....",
                        to: [Receiver Token],
                        collapse_key: "new_messages",
                        data: {
                            "notification": {
                                msg_type: "msg",
                                sent_time:new Date(Date.now()+(new Date().getTimezoneOffset()*60000)).getTime()/1000|0,
                                timestamp:Date.now(),
                                ttl:30,
                                code:"Sender Code[Name or Codename]",
                                body: "MESSAGE BODY",
                                title: "Display Name of sender",
                                confirm: "URL FOR Confirm btn[Optional]",
                                decline: "URL FOR Decline btn[Optional]"
                            }
                        },
                        priority: "high"
                    }),
                    success: function(response) {
                      console.log(response);
                    },
                    fail:function(err){
                      console.log(err);
                    } 
             });

[IndexedDB]

Use IndexedDB APIs : If you want to store the messages for a while at client side.

Note:Use IndexedDB code into service worker file so you can manage it easily for transaction.

window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

var FM_DBOpenRequest="";
var db="";



function open_indexeddb(obj,callback){
  FM_DBOpenRequest = indexedDB.open("app-name",1);
  FM_DBOpenRequest.onsuccess = function(event) {
    db = FM_DBOpenRequest.result;
    indexeddbflag=1;
    callback(db);
  };

  FM_DBOpenRequest.onupgradeneeded = function(event) { 
    var db = event.target.result;

        var objectStore = db.createObjectStore('conversation', { keyPath: 'id' });
            objectStore.createIndex("sent_time", "sent_time", {
                unique: false
            });

              if(event.oldVersion<1){
                 /*Create or update object store*/
              }
              if(event.oldVersion<2){
                /*Create or update object store*/
              } 

  };

}

function push_msg(payload){
    /*Store data into object store by put method*/  

      var transaction = db.transaction(['conversation'], 'readwrite');
      var objectStore = transaction.objectStore('conversation');

      var msg_obj={}; /*Add data to push into idb*/
      var request = objectStore.put(msg_obj);
          request.onsuccess = function() {
          };
};

[Additional Tasks]

=================

Task
Keep Conversation at client side
Keep friend's token in-Sync locally.
Clear IDB an interval of 24hrs
Allow user to make web-call(WebRTC)

[Advantages]

===========
1.Small footprint and serve the purpose for friends and family.
2.No one will screening/selling your data.
3.Built to serve the purpose quickly with a low memory footprint.
4.My data My Rule.

[Limitations]

1.Firebase is free lunch for us, so sometimes the message got lost in the matrix.
2.Longer in-active users will not awake the device 's background service easily.
3.If the user is not frequent there will be a lot of delay messages.


This is a simple foundation work for any tiny messaging app. If you want to debug or to grasp more please inspect these files: {js/test.js,firebase-messaging-SW.js} from the console

Project Trunk : (https://trunk.jswalker.in)

Product of Jswalker.in
Thank You~~~[Create something that matters] EOF

Discussion

pic
Editor guide