In this article, we see how to convert a Web Application or Website to a PWA with a Push Notification using Firebase Cloud Messaging.

In the modern world, most web applications are getting converted to a PWA (Progressive Web App) because it provides features like offline support, push notification, background sync. PWA features make our web application more like a native app and give a rich user experience.

For example, great companies like Twitter and Amazon have converted their Web App to PWA for more user engagement.

What is a PWA?

PWA = (Web App) + (some native app features)

PWA is your same Web App(HTML+CSS+JS). It works the same as your Web App on all browsers as it previously did. But it can have native features when your website loads on a modern browser. It makes your web app more powerful than before and it also makes it more scalable because we can able to prefetch and cache assets in the frontend, it reduces requests to your backend server.

How PWA is different from Web App

  • Installable: Your Web App can be installed like a native app
  • Progressive: Works same as your web app but with some native features
  • Native App Experience: User can use and navigate Web App like a native, once installed.
  • Easily Accessible: Unlike our web app, there is no need for our users to type web addresses each time they visit. Once installed, it can be opened with a single tap.
  • Application Caching: Before PWA, the only caching mechanism our web app implemented with is, by using HTTP cache which is only available to the browser. But with PWA we can cache things by using client-side code itself which is not available in a web app.
  • (App/Play) store publishing: PWA can be published in Google Play Store and IOS App Store.

Converting your application to PWA will only make it more powerful.

Why buisness should consider PWA

While most of our clients reach us and ask to develop the Web App solution first and then they ask for Android and iOS apps. All we are going to do is build the same functionality in web App to Android/IOS App by a separate team which takes more development cost and more time to market.

But some client has a limited budget or some client may think time to market is more important for their product.

Most of the client requirements can be satisfied by PWA features themselves. For them, we suggest PWA only and give them an idea to convert their PWA as an Android App using TWA if they want to deploy in Playstore.

If your requirement truly needs native application features which can’t be satisfied by PWA. Clients can go and develop both Applications as they wish. But even in that scenario. They can deploy the PWA in the play store until Android development is completed.

Example: Titan Eyeplus

Initially, they developed a PWA app and deployed it in the play store using TWA(Trusted Web Activity). Once they completed their Android application development. They deployed their real Android Application in the play store. They achieved both time-to-market using PWA and the Cost of the development.

PWA features

PWA gives our web applications native app-like features.

The main features are:

  • Installable: A web application installed like a native app.
  • Caching: Application caching is possible, which gives our application offline support.
  • Push Notification: Push Notification can be sent from our server to engage our users to our website.
  • Geofencing: The application can be notified by an event whenever the device location change.
  • Payment Request: Enable payment in your application with a great user experience like a native app.

And many more features to come in the future.

Other features are:

  • Shortcuts: Quickly accessible URLs added in the manifest file.
  • Web Share API: Let your application receive shared data from other applications.
  • Badge API: To show notification count in your installed PWA.
  • Periodic Background Sync API: saves your user’s data until it’s connected to the network.
  • Contact picker: Used to pick contacts from the user’s mobile.
  • File Picker: Used to access the file on local system/mobile

Advantage of PWA over Native Application

The native app performs better than PWA and has more features than PWA. But still, it has some advantages over the native app.

  • PWA runs on cross-platform like Android, IOS, Desktop.
  • It reduces your development cost.
  • Easy feature deployment as compared to a native app.
  • Easily discoverable because PWA(website) is SEO-friendly
  • Secure because it works only on HTTPS

Disadvantages of PWA over native app

  • Limited functionalities are available compared to a native app.
  • PWA features are not guaranteed to support all devices.
  • The branding of PWA is low because it’s not available in the app store or play store.

You can deploy your PWA as an Android app in the play store using android Trusted Web Activity(TWA). It will help your branding.

Things needed to convert Web App to PWA

For converting, any Web App or Website to PWA.

  • Service-Worker: the core of any PWA app for Caching, Pushes Notification, a proxy for our requests.
  • Manifest file: It has details about your web application. It used to download our application like a native app on the home screen.
  • App Logo: High-Quality image 512 x 512 px for your app icon. App logo needed for PWA on the home screen, splash screen, etc. So we have to create a set to 1:1 ratio images for our APP using any tools.
  • Responsive Design: The web app should be responsive to work on different screen sizes.

What is Service Worker:

A service worker(client-side script) is a proxy between your Web APP and the outer side, delivering push notifications for our Web App and supporting Caching.

Service Worker runs independently from the main javascript. So it doesn’t have access to DOM API. It can only access IndexedDB APIFetch APICache Storage API. But it can communicate with the main thread with a message.

Service provided by service worker:

  • Intercepting HTTP requests from your origin domain.
  • Receive Push Notification from your server.
  • Offline availability of our application

The service worker controls your application and can manipulate your requests, but it runs independently. So for that reason, the origin domain must be enabled with HTTPS to avoid a man-in-the-middle attack.

What is the Manifest file

A manifest file(manifest.json) has details about our PWA app to tell the browser.

  • name: Name of the application
  • short_name: Short Name for our application. If provided
  •  with both property name and short_name, the browser will take short_name.
  • description: Description to describe our application.
  • start_url: To specify the homepage of the application when our PWA launched.
  • icons: Set of images for PWA for Home screen, etc.
  • background_color: To set the background colour of the splash screen in our PWA application.
  • display: To customize our browser UI to show in our PWA app.
  • theme_color: Theme colour of PWA app.
  • scope: URL scope of our application to consider for PWA. Defaults to the location of the manifest file located.
  • shortcuts:  Quick links for our PWA application.

Convert Web App to PWA

For the demo purpose, I have created a Geekflare website folder structure with static files.

  • index.html – home page
  • articles/
    • index.html – articles page
  • authors/
    • index.html – authors page
  • tools/
    • index.html – tools page
  • deals/
    • index.html – deals page

If you already have any website or Web App then try to convert it to PWA by following the below steps.

Create Required images for PWA

Firstly, take your app logo and crop it in a 1:1 ratio size in 5 different sizes. I have used https://tools.crawlink.com/tools/pwa-icon-generator/ to get different image sizes quickly. So you can use it too.

Create a manifest file

Secondly, create a manifest.json file for your Web application with your app details. For the demo, I have created a manifest file for Geekflare Website.

{
	"name": "Geekflare",
	"short_name": "Geekflare",
	"description": "Geekflare produces high-quality technology & finance articles, makes tools, and APIs to help businesses and people grow.",
	"start_url": "/",
	"icons": [{
		"src": "assets/icon/icon-128x128.png",
		"sizes": "128x128",
		"type": "image/png"
	}, {
		"src": "assets/icon/icon-152x152.png",
		"sizes": "152x152",
		"type": "image/png"
	}, {
		"src": "assets/icon/icon-192x192.png",
		"sizes": "192x192",
		"type": "image/png"
	}, {
		"src": "assets/icon/icon-384x384.png",
		"sizes": "384x384",
		"type": "image/png"
	}, {
		"src": "assets/icon/icon-512x512.png",
		"sizes": "512x512",
		"type": "image/png"
	}],
	"background_color": "#EDF2F4",
	"display": "standalone",
	"theme_color": "#B20422",
	"scope": "/",
	"shortcuts": [{
			"name": "Articles",
			"short_name": "Articles",
			"description": "1595 articles on Security, Sysadmin, Digital Marketing, Cloud Computing, Development, and many other topics.",
			"url": "/articles",
			"icons": [{
				"src": "/assets/icon/icon-152x152.png",
				"sizes": "152x152"
			}]
		},
		{
			"name": "Authors",
			"short_name": "Authors",
			"description": "Geekflare - Authors",
			"url": "/authors",
			"icons": [{
				"src": "/assets/icon/icon-152x152.png",
				"sizes": "152x152"
			}]
		},
		{
			"name": "Tools",
			"short_name": "Tools",
			"description": "Geekflare - Tools",
			"url": "/tools",
			"icons": [{
				"src": "/assets/icon/icon-152x152.png",
				"sizes": "152x152"
			}]
		},
		{
			"name": "Deals",
			"short_name": "Deals",
			"description": "Geekflare - Deals",
			"url": "/deals",
			"icons": [{
				"src": "/assets/icon/icon-152x152.png",
				"sizes": "152x152"
			}]
		}
	]
}

Register Service-worker

create a script file register-service-worker.js and  service-worker.js  in the root folder.

The first one, register-service-worker.js is the javascript file that will run on the main thread which can access DOM API. But service-worker.js is a service worker script that runs independently from the main thread and its lifetime is also short. It runs whenever events call service workers and run until it finishes the process.

By checking the main thread javascript file you can check whether the service worker is registered in it. if not you can register the service worker script(service-worker.js).

paste the below snippet in register-service-worker.js:

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('/service-worker.js');
    });
}

Paste the below snippet in service-worker.js

self.addEventListener('install', (event) => { // event when service worker install
    console.log( 'install', event);
    self.skipWaiting();
});

self.addEventListener('activate', (event) => { // event when service worker activated
    console.log('activate', event);
    return self.clients.claim();
});

self.addEventListener('fetch', function(event) { // HTTP request interceptor
    event.respondWith(fetch(event.request)); // send all http request without any cache logic
    /*event.respondWith(
        caches.match(event.request).then(function(response) {
            return response || fetch(event. request);
        })
    );*/ // cache new request. if already in cache serves with the cache.
});

We didn’t concentrate on how to enable cache for offline support. We only talk about how to convert Web apps to PWA.

Add manifest file and script in the all head tag of your HTML page.

<link rel="manifest" href="/manifest.json">
<script src="/register-service-worker.js"></script>

Refresh the page after you added. Now you can install your application like below on mobile chrome.

PWA installation in android chrome

On the home screen, the app gets added.

pWA shortut in chrome

If you are using WordPress. Try using the existing PWA converter plugin. For vueJS or reactJS you can follow the above method or use the existing PWA npm modules to faster your development. Because PWA npm modules are already enabled with offline support caching, etc.

Enable Push Notification

Web push notifications are sent to the browser to make our users engage/interact with our application more often. We can enable it by using

  • Notification API: It is used to configure how our push notification should be shown to the user.
  • Push API: It is used to receive notification messages sent from our server to the browser.

The first step to enable push notification in our application is to check Notification API and get permission from the user to show a notification. For that copy and paste the snippet below in your register-service-worker.js.

if ('Notification' in window && Notification.permission != 'granted') {
    console.log('Ask user permission')
    Notification.requestPermission(status => {  
        console.log('Status:'+status)
        displayNotification('Notification Enabled');
    });
}


const displayNotification = notificationTitle => {
    console.log('display notification')
    if (Notification.permission == 'granted') {
        navigator.serviceWorker.getRegistration().then(reg => {
            console.log(reg)
            const options = {
                    body: 'Thanks for allowing push notification !',
                    icon:  '/assets/icons/icon-512x512.png',
                    vibrate: [100, 50, 100],
                    data: {
                      dateOfArrival: Date.now(),
                      primaryKey: 0
                    }
                  };
    
            reg.showNotification(notificationTitle, options);
        });
    }
};

If everything went correctly. You will receive a notification from the application.

pwa-notification-api-permission
pwa-notification-api-displaying-notification

‘Notification’ in window will tell us that Notification API is supported in that browser. Notification.permission will tell that the user has been permitted to show the notification. If the user permitted our application the value will be ‘granted’. if the user has rejected the value will be ‘blocked’.

Enable Firebase Cloud Messaging and Create Subscription

Now the real part begins. For pushing notifications from your server to the user we need a unique endpoint/subscription for each user. For that, we are going to use firebase cloud messaging.

As a first step, create a firebase account by visiting this link https://firebase.google.com/ and press get started.

  1. Create a new project with a name and press continue. I’m going to create it with the name Geekflare.
  2. In the next step, Google Analytics is enabled by default. You can toggle that we don’t need that now and press continue. You can enable it later in your firebase console if you needed.
  3. Once the project gets created, it will look like below.
Firebase Console

Then go to the project settings and click cloud messaging and generate keys.

firebase cloud messasing key generation

From the above steps, you have got 3 keys.

  • project server-key
  • Web push certificates private-key
  • Web push certificates public-key

Now paste the below snippet in register-service-worker.js:

const updateSubscriptionOnYourServer = subscription => {
    console.log('Write your ajax code here to save the user subscription in your DB', subscription);
    // write your own ajax request method using fetch, jquery, axios to save the subscription in your server for later use.
};

const subscribeUser = async () => {
    const swRegistration = await navigator.serviceWorker.getRegistration();
    const applicationServerPublicKey = 'BOcTIipY07N4Y63Y-9r7NMoJHofmCzn3Pu9g-LMsgIMGH4HVr42_LW9ia0lMr68TsTLKS3UcdkE3IcC52hJDYsY'; // paste your webpush certificate public key
    const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
    swRegistration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey
    })
    .then((subscription) => {
        console.log('User is subscribed newly:', subscription);
        updateSubscriptionOnServer(subscription);
    })
    .catch((err) => {
        if (Notification.permission === 'denied') {
          console.warn('Permission for notifications was denied')
        } else {
          console.error('Failed to subscribe the user: ', err)
        }
    });
};
const urlB64ToUint8Array = (base64String) => {
    const padding = '='.repeat((4 - base64String.length % 4) % 4)
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/')

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
};

const checkSubscription = async () => {
    const swRegistration = await navigator.serviceWorker.getRegistration();
    swRegistration.pushManager.getSubscription()
    .then(subscription => {
        if (!!subscription) {
            console.log('User IS Already subscribed.');
            updateSubscriptionOnYourServer(subscription);
        } else {
            console.log('User is NOT subscribed. Subscribe user newly');
            subscribeUser();
        }
    });
};

checkSubscription();

Paste the below snippet in service-worker.js.

self.addEventListener('push', (event) => {
  const json = JSON.parse(event.data.text())
  console.log('Push Data', event.data.text())
  self.registration.showNotification(json.header, json.options)
});

Now all set in the front-end. By using the subscription you can send push notifications to your user whenever you want until they were not denied the push services.

Push from the node.js Backend

You can use the web-push npm module to make it easier.

Example snippet to send push notification from nodeJS server.

const webPush = require('web-push');
    // pushSubscription is nothing but subscription that you sent from your front-end to save it in DB
    const pushSubscription = {"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABh2…E0mTFsHtUqaye8UCoLBq8sHCgo2IC7UaafhjGmVCG_SCdhZ9Z88uGj-uwMcg","keys":{"auth":"qX6AMD5JWbu41cFWE3Lk8w","p256dh":"BLxHw0IMtBMzOHnXgPxxMgSYXxwzJPxpgR8KmAbMMe1-eOudcIcUTVw0QvrC5gWOhZs-yzDa4yKooqSnM3rnx7Y"}};
    //your web certificates public-key
    const vapidPublicKey = 'BOcTIipY07N4Y63Y-9r7NMoJHofmCzn3Pu9g-LMsgIMGH4HVr42_LW9ia0lMr68TsTLKS3UcdkE3IcC52hJDYsY';
    //your web certificates private-key
    const vapidPrivateKey = 'web-certificate private key';

    var payload = JSON.stringify({
      "options": {
        "body": "PWA push notification testing fom backend",
        "badge": "/assets/icon/icon-152x152.png",
        "icon": "/assets/icon/icon-152x152.png",
        "vibrate": [100, 50, 100],
        "data": {
          "id": "458",
        },
        "actions": [{
          "action": "view",
          "title": "View"
        }, {
          "action": "close",
          "title": "Close"
        }]
      },
      "header": "Notification from Geekflare-PWA Demo"
    });

    var options = {
      vapidDetails: {
        subject: 'mailto:your-actual-mail@gmail.com',
        publicKey: vapidPublicKey,
        privateKey: vapidPrivateKey
      },
      TTL: 60
    };

    webPush.sendNotification(
      pushSubscription,
      payload,
      options
    ).then(data => {
      return res.json({status : true, message : 'Notification sent'});
    }).catch(err => {
      return res.json({status : false, message : err });
    });

The above code will send a push notification to the subscription. The push event in the service-worker will get triggered.

Push from the PHP Backend

For PHP backend you can use the web-push-php composer package. Check the example code to send push notifications below.

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

require __DIR__.'/../vendor/autoload.php';
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

// subscription stored in DB
$subsrciptionJson = '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABh2…E0mTFsHtUqaye8UCoLBq8sHCgo2IC7UaafhjGmVCG_SCdhZ9Z88uGj-uwMcg","keys":{"auth":"qX6AMD5JWbu41cFWE3Lk8w","p256dh":"BLxHw0IMtBMzOHnXgPxxMgSYXxwzJPxpgR8KmAbMMe1-eOudcIcUTVw0QvrC5gWOhZs-yzDa4yKooqSnM3rnx7Y"}}';
$payloadData = array (
'options' =>  array (
                'body' => 'PWA push notification testing fom backend',
                'badge' => '/assets/icon/icon-152x152.png',
                'icon' => '/assets/icon/icon-152x152.png',
                'vibrate' => 
                array (
                  0 => 100,
                  1 => 50,
                  2 => 100,
                ),
                'data' => 
                array (
                  'id' => '458',
                ),
                'actions' => 
                array (
                  0 => 
                  array (
                    'action' => 'view',
                    'title' => 'View',
                  ),
                  1 => 
                  array (
                    'action' => 'close',
                    'title' => 'Close',
                  ),
                ),
),
'header' => 'Notification from Geekflare-PWA Demo',
);

// auth
$auth = [
    'GCM' => 'your project private-key', // deprecated and optional, it's here only for compatibility reasons
    'VAPID' => [
        'subject' => 'mailto:your-actual-mail@gmail.com', // can be a mailto: or your website address
        'publicKey' => 'BOcTIipY07N4Y63Y-9r7NMoJHofmCzn3Pu9g-LMsgIMGH4HVr42_LW9ia0lMr68TsTLKS3UcdkE3IcC52hJDYsY', // (recommended) uncompressed public key P-256 encoded in Base64-URL
        'privateKey' => 'your web-certificate private-key', // (recommended) in fact the secret multiplier of the private key encoded in Base64-URL
    ],
];

$webPush = new WebPush($auth);

$subsrciptionData = json_decode($subsrciptionJson,true);


// webpush 6.0
$webPush->sendOneNotification(
  Subscription::create($subsrciptionData),
  json_encode($payloadData) // optional (defaults null)
);

Conclusion

I hope this gives you an idea about converting web applications to PWA. You can check the source code of this article here and demo it here. I have tested the push notification by sending it from the backend with the help of example code too.