Message Boards

How to create a Progressive Web App (PWA) in Liferay DXP

thumbnail
saleem khan, modified 5 Years ago.

How to create a Progressive Web App (PWA) in Liferay DXP

Junior Member Posts: 71 Join Date: 11/16/13 Recent Posts
Hi Guys,

I am trying to do some POCs on this to create a PWA  in liferay. Please let me know if someone has implemented this and what is the right method to impletment this with liferay DXP.
Sudhanshu Kumar Shukla, modified 5 Years ago.

RE: How to create a Progressive Web App (PWA) in Liferay DXP

Junior Member Posts: 35 Join Date: 8/29/16 Recent Posts
Hi Saleem,

I have implement browser push notification in Liferay which is one of the features of PWA.
For PWA First you need to register a service worker and then according to that you need to cache all the
URLs which you want to be available offline.

Here is the sample code of service worker which I have used for push notification.
/* MINIFIER: OFF */
if('function' === typeof importScripts){
    importScripts('https://cdn.jsdelivr.net/npm/serviceworker-cache-polyfill@4.0.0/index.min.js');
    self.addEventListener('install',function(e){
        
        e.waitUntil(caches.open('pushNotification').then(function(cache){
            console.log("cahces "+JSON.stringify(cache));
            return cache.addAll([
                                 '/'    <=====here you can add more url to cache to work in offline i have just cached root as per my requirement
                               ]);
        }));
    });
    
    self.addEventListener('fetch',function(event){
        console.log("fetch");
        event.respondWith(caches.match(event.request).then(function(response){
            return response || fetch(event.request);
        }));
    });
    
    self.addEventListener('push',function(event){
        var serverData=event.data.json();
        if(serverData){
            var notifiBody=serverData.body;
            var imageIcon=serverData.imageUrl;
            var rUrl=serverData.redirectUrl;
            if(!imageIcon){
                imageIcon="https://www.google.com/imgres?imgurl=https://www.interntheory.com/uploads/company/companylogos/006190ff1333880b1d0ad36da4aa311f5621e16d/5cc73335527a6b5647b8dcf0d0563bd00200605fcom.png&imgrefurl=https://www.interntheory.com/company/edelweiss-broking-ltd-8cego84/&h=220&w=885&tbnid=2RZi1tNBal8D3M:&q=edelweiss&tbnh=30&tbnw=122&usg=AI4_-kQ9iI3Nfx1a1EHcq6rAoSaNHmEUcw&vet=1&docid=ocIy-ZgIuDyyDM&itg=1&sa=X&ved=2ahUKEwjgkp2rrangAhVJK48KHWDhAkkQ_B0wGnoECAQQEQ";
            }
            console.log("redirect url is : "+rUrl);
            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.");
        }
    });
    self.addEventListener('notificationclick', function(event) {
        var url = event.notification.data.redirectUrl;
        console.log("redirect url is : "+url);
        event.waitUntil(
            clients.matchAll({type: 'window'}).then( windowClients => {
                for (var i = 0; i < windowClients.length; i++) {
                    var client = windowClients[i]
/* MINIFIER: ON */
}
    });
        );
            })
                }
                    return clients.openWindow(url);
                if (clients.openWindow) {
                }
                    }
                        return client.focus();
                    if (client.url === url && 'focus' in client) {
;[/i]

Below is the code for registering the service worker from jsp

<portlet:resourceurl id="saveUserSubscription" var="resourceURL" />

<meta charset="utf-8">
<link href="<%= request.getContextPath()%>/manifest.json"> 

<script>
    Liferay.Loader.define._amd = Liferay.Loader.define.amd;
    Liferay.Loader.define.amd = false;
</script>
<script>
    Liferay.Loader.define.amd = Liferay.Loader.define._amd;
</script>
<div id="my-content-div">
    <div id="notify">
        
    </div>
</div>
<style>
        .blueTheme{
            color:#18489d;
        }
</style>
<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="yout auth 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]
</body>
</script>
    /* MINIFIER: ON */ 
    }    
        return outputArray;
        }
= rawData.charCodeAt(i);[/i]

If you want to connect with the backend and databases you can use the fetch method for that based upon custom requirement.

In chrome you need to authenticate with FCM or VAPIDS Keys but in firefox you won't. 


I have used manifier on and off because when Liferay minifies the js code then service worker is not working if you find any
 workaround for this then please let me know.


Below are the some useful links

https://developers.google.com/web/ilt/pwa/introduction-to-service-worker

https://developers.google.com/web/fundamentals/codelabs/offline/

May this will help.
 

thumbnail
saleem khan, modified 5 Years ago.

RE: How to create a Progressive Web App (PWA) in Liferay DXP

Junior Member Posts: 71 Join Date: 11/16/13 Recent Posts
Thanks Sudhanshu.


I have been going through the googles recources as you have mentioned and have managed to implement an POC. But What I want now is to how to cache everything and make it work fully offline and how good will it be in terms of performance. I am still working on it and whould post a blog on the same.

Thanks for sharing notification implementation as it was one of the item on this POC.
Ricardo Fretes, modified 4 Years ago.

RE: How to create a Progressive Web App (PWA) in Liferay DXP

New Member Post: 1 Join Date: 12/9/19 Recent Posts
Hi Saleem, Did you manage to finish it ? Have you created a blog post about ? 
thumbnail
saleem khan, modified 4 Years ago.

RE: How to create a Progressive Web App (PWA) in Liferay DXP

Junior Member Posts: 71 Join Date: 11/16/13 Recent Posts
Hi Recardo,Yes. I managed to do this and turns out it not that hard but unfortunately I didn’t have the time to write the blog on it.  Let me try doing it this weekend and share the link here 
Sudhanshu Kumar Shukla, modified 4 Years ago.

RE: How to create a Progressive Web App (PWA) in Liferay DXP

Junior Member Posts: 35 Join Date: 8/29/16 Recent Posts
Ricardo Fretes:

Hi Saleem, Did you manage to finish it ? Have you created a blog post about ? 

HI Ricardo 
Please refer the this my blog on this https://liferay.dev/blogs/-/blogs/pwa-part-1-web-push-notification.It has four parts one after one. kindly refer and let me know if you face any issue.