In the first: Introducing Progressive Web Apps (Offline) – In Part 1 we looked at what a typical PWA should look like, and introduced sercer workers and App shells, In Part 2 of the second article on progressive Web Apps (Instant Loading), we cached dynamic data and implemented instant loading of data from locally saved data into pages, as well as introducing some of the Web’s databases.

In this article, the end of the series, we will implement:

  • Enable push notifications in your Web application
  • Make Web applications installable

Being pushed

The Push API enables Web applications to receive messages pushed from the server and notify users. This function requires service workers to work together, and a typical push notification process in a Web application looks like this:

  • The Web application pops up a pop-up window asking the user to subscribe to notifications.
  • Users subscribe to receive push notifications.
  • The service worker is responsible for managing and handling user subscriptions.
  • The user’s subscription ID is used when the server pushes messages, and each user can have a custom function based on their subscription ID.
  • In the service of the workerpushCan receive any incoming message.

Let’s get started.

Let’s start with a quick summary. How does push work in our Web app

  1. Users are given a switch to click a button to enable or disable push notifications.
  2. If the user is activated, the subscriber receives push notifications through the management of the service worker.
  3. Build an API to handle user deleting or saving subscription ids. This API will also be responsible for sending messages to all users who have enabled push messages.
  4. Set up a GitHub Webhook to send notifications immediately so that new submissions are pushed toresources-i-likewarehouse

Code in action

Create a new js/notification.js file in your project and reference it in index.html.

<script src="./js/notification.js"></script>
Copy the code

The js/notification.js code is as follows

(function (window) {
  'use strict';

  //Push notification button
  var fabPushElement = document.querySelector('.fab__push');
  var fabPushImgElement = document.querySelector('.fab__image');

  //To check `push notification` is supported or not
  function isPushSupported() {
    //To check `push notification` permission is denied by user
    if (Notification.permission === 'denied') {
      alert('User has blocked push notification.');
      return;
    }

    //Check `push notification` is supported or not
    if(! ('PushManager' in window)) {
      alert('Sorry, Push notification isn\'t supported in your browser.'); return; } //Get `push notification` subscription //If `serviceWorker` is registered and ready navigator.serviceWorker.ready .then(function (registration) { registration.pushManager.getSubscription() .then(function (subscription) { //If already access granted, enable push button status if (subscription) { changePushStatus(true); } else { changePushStatus(false); } }) .catch(function (error) { console.error('Error occurred while enabling push ', error); }); }); } // Ask User if he/she wants to subscribe to push notifications and then // .. subscribe and send push notification function subscribePush() { navigator.serviceWorker.ready.then(function(registration) { if (! registration.pushManager) { alert('Your browser doesn\'t support push notification.');
        return false;
      }

      //To subscribe `push notification` from push manager
      registration.pushManager.subscribe({
        userVisibleOnly: true //Always show notification when received
      })
      .then(function (subscription) {
        toast('Subscribed successfully.');
        console.info('Push notification subscribed.');
        console.log(subscription);
        //saveSubscriptionID(subscription);
        changePushStatus(true);
      })
      .catch(function (error) {
        changePushStatus(false);
        console.error('Push notification subscription error: ', error);
      });
    })
  }

  // Unsubscribe the user from push notifications
  function unsubscribePush() {
    navigator.serviceWorker.ready
    .then(function(registration) {
      //Get `push subscription`
      registration.pushManager.getSubscription()
      .then(function (subscription) {
        //If no `push subscription`, then return
        if(! subscription) { alert('Unable to unregister push notification.');
          return;
        }

        //Unsubscribe `push notification`
        subscription.unsubscribe()
          .then(function () {
            toast('Unsubscribed successfully.');
            console.info('Push notification unsubscribed.');
            console.log(subscription);
            //deleteSubscriptionID(subscription);
            changePushStatus(false);
          })
          .catch(function (error) {
            console.error(error);
          });
      })
      .catch(function (error) {
        console.error('Failed to unsubscribe push notification.');
      });
    })
  }

  //To change status
  function changePushStatus(status) {
    fabPushElement.dataset.checked = status;
    fabPushElement.checked = status;
    if (status) {
      fabPushElement.classList.add('active');
      fabPushImgElement.src = '.. /images/push-on.png';
    }
    else {
     fabPushElement.classList.remove('active');
     fabPushImgElement.src = '.. /images/push-off.png';
    }
  }

  //Click event for subscribe push
  fabPushElement.addEventListener('click'.function () {
    var isSubscribed = (fabPushElement.dataset.checked === 'true');
    if (isSubscribed) {
      unsubscribePush();
    }
    else{ subscribePush(); }}); isPushSupported(); //Checkfor push notification support
})(window);
Copy the code

The above code does a lot of things. Rest assured, I will explain a wave of code function drops.

//Push notification button
  var fabPushElement = document.querySelector('.fab__push');
  var fabPushImgElement = document.querySelector('.fab__image');
Copy the code

The code above gets the nodes for push notification activation and deactivation buttons.

function isPushSupported() {
    //To check `push notification` permission is denied by user
    if (Notification.permission === 'denied') {
      alert('User has blocked push notification.');
      return;
    }

    //Check `push notification` is supported or not
    if(! ('PushManager' in window)) {
      alert('Sorry, Push notification isn\'t supported in your browser.'); return; } //Get `push notification` subscription //If `serviceWorker` is registered and ready navigator.serviceWorker.ready .then(function (registration) { registration.pushManager.getSubscription() .then(function (subscription) { //If already access granted, enable push button status if (subscription) { changePushStatus(true); } else { changePushStatus(false); } }) .catch(function (error) { console.error('Error occurred while enabling push ', error); }); }); }Copy the code

The code above checks to see if the browser supports push notifications. Now, the most important thing is that the service worker must be registered and ready before you try to subscribe to a user to receive push notifications. Therefore, the above code also checks if the service worker is ready and gets a subscription from the user.

 //To change status
  function changePushStatus(status) {
    fabPushElement.dataset.checked = status;
    fabPushElement.checked = status;
    if (status) {
      fabPushElement.classList.add('active');
      fabPushImgElement.src = '.. /images/push-on.png';
    }
    else {
     fabPushElement.classList.remove('active');
     fabPushImgElement.src = '.. /images/push-off.png'; }}Copy the code

User subscribe button style changes

The changePushStatus function stands for simply changing the color of the button to indicate whether the user is subscribed.

// Ask User if he/she wants to subscribe to push notifications and then/ /.. subscribe and send push notificationfunction subscribePush() {
    navigator.serviceWorker.ready.then(function(registration) {
      if(! registration.pushManager) { alert('Your browser doesn\'t support push notification.'); return false; } //To subscribe `push notification` from push manager registration.pushManager.subscribe({ userVisibleOnly: true //Always show notification when received }) .then(function (subscription) { toast('Subscribed successfully.'); console.info('Push notification subscribed.'); console.log(subscription); //saveSubscriptionID(subscription); changePushStatus(true); }) .catch(function (error) { changePushStatus(false); console.error('Push notification subscription error: ', error); }); })}Copy the code

The code above is responsible for popping up the request asking the user whether to allow or block push messages in the browser. If the user allows push messages, a toast is allowed prompt, then change the color of the button and save the subscription ID. If the Push browser does not support it, it notifies the user that it is not supported.

Note: The ability to save subscription ids is now commented out.

// Unsubscribe the user from push notifications
  function unsubscribePush() {
    navigator.serviceWorker.ready
    .then(function(registration) {
      //Get `push subscription`
      registration.pushManager.getSubscription()
      .then(function (subscription) {
        //If no `push subscription`, then return
        if(! subscription) { alert('Unable to unregister push notification.');
          return;
        }

        //Unsubscribe `push notification`
        subscription.unsubscribe()
          .then(function () {
            toast('Unsubscribed successfully.');
            console.info('Push notification unsubscribed.');
            //deleteSubscriptionID(subscription);
            changePushStatus(false);
          })
          .catch(function (error) {
            console.error(error);
          });
      })
      .catch(function (error) {
        console.error('Failed to unsubscribe push notification.'); }); })}Copy the code

The one above is responsible for the unsubscribe push message, pops up a toast prompt be careful, then changes the color of the button and removes the subscription ID.

Note: The ability to delete subscription ids is now commented out.


 //Click event for subscribe push
  fabPushElement.addEventListener('click'.function () {
    var isSubscribed = (fabPushElement.dataset.checked === 'true');
    if (isSubscribed) {
      unsubscribePush();
    }
    else{ subscribePush(); }});Copy the code

The above code is to add a button click event to implement the subscription and unsubscribe user switch.

Processing subscription ids

We can already see push subscriptions. Now, we need to be able to save each user’s subscription ID and delete those subscription ids when a user unsubscribes to a push notification.

Add the following code to your js/notification.js

function saveSubscriptionID(subscription) {
    var subscription_id = subscription.endpoint.split('gcm/send/') [1]; console.log("Subscription ID", subscription_id);

    fetch('http://localhost:3333/api/users', {
      method: 'post',
      headers: {
        'Accept': 'application/json'.'Content-Type': 'application/json'
      },
      body: JSON.stringify({ user_id : subscription_id })
    });
}

function deleteSubscriptionID(subscription) {
    var subscription_id = subscription.endpoint.split('gcm/send/') [1]; fetch('http://localhost:3333/api/user/' + subscription_id, {
      method: 'delete',
      headers: {
        'Accept': 'application/json'.'Content-Type': 'application/json'}}); }Copy the code

In the above code, we request an interface from the server to get the subscription ID and delete the subscription ID. The saveSubscriptionID function creates a new user and saves the subscription ID of the user. DeleteSubscriptionID removes the user and the subscription ID of the user

It looks weird. Why request to the server? Simple, because we need a database to store all subscription ids so that we can send notifications to all users.

API Service

This API Service handles the ability to save and delete subscription ids as well as the ability to push messages. The decomposition of this API. It will have three API routes:

  1. POST /api/usersCreate a new user and store its subscription ID
  2. DELETE /api/user/:user_idDelete and unsubscribe users
  3. POST /api/notifySend notifications to all subscribers

I am glad that I have the API Service source code, you can click the link to see, runtime to ensure that your Node and mongodb are installed. Clone XI and run Node server.js from the command line

Make sure you create the. Env file first, as shown below

Reminder: You can learn how to set up the API service through this good tutorial. In this tutorial I just implemented the Node.js version of the API service.

We will use Firebase Cloud Messaging as our Messaging service. So, now use Firebase to create a new project. When you’re done with your new Project, go to Project Settings > Cloud Messaging

Get your Server Key and copy and paste it into your.env file in FCM_API_KEY. Through our API Server we need to pass the Server Key to Firebase. Let’s take a look at our notification push controller code:

. notifyUsers:function(req, res){

    var sender = new gcm.Sender(secrets.fcm);

    // Prepare a message to be sent
    var message = new gcm.Message({
        notification: {
          title: "New commit on Github Repo: RIL",
          icon: "ic_launcher",
          body: "Click to see the latest commit'"}}); User.find({},function(err, users) {

      // user subscription ids to deliver message to
      var user_ids = _.map(users, 'user_id');

      console.log("User Ids", user_ids);

      // Actually send the message
      sender.send(message, { registrationTokens: user_ids }, function (err, response) {
        if (err) {
            console.error(err);
        } else {
          returnres.json(response); }}); }); },...Copy the code

Now return our js/notification.js and remove the saveSubscriptionID and deleteSubscriptionID comments we mentioned earlier, and your notification.js should look like this:

(function (window) {
  'use strict';

  //Push notification button
  var fabPushElement = document.querySelector('.fab__push');
  var fabPushImgElement = document.querySelector('.fab__image');

  //To check `push notification` is supported or not
  function isPushSupported() {
    //To check `push notification` permission is denied by user
    if (Notification.permission === 'denied') {
      alert('User has blocked push notification.');
      return;
    }

    //Check `push notification` is supported or not
    if(! ('PushManager' in window)) {
      alert('Sorry, Push notification isn\'t supported in your browser.'); return; } //Get `push notification` subscription //If `serviceWorker` is registered and ready navigator.serviceWorker.ready .then(function (registration) { registration.pushManager.getSubscription() .then(function (subscription) { //If already access granted, enable push button status if (subscription) { changePushStatus(true); } else { changePushStatus(false); } }) .catch(function (error) { console.error('Error occurred while enabling push ', error); }); }); } // Ask User if he/she wants to subscribe to push notifications and then // .. subscribe and send push notification function subscribePush() { navigator.serviceWorker.ready.then(function(registration) { if (! registration.pushManager) { alert('Your browser doesn\'t support push notification.');
        return false;
      }

      //To subscribe `push notification` from push manager
      registration.pushManager.subscribe({
        userVisibleOnly: true //Always show notification when received
      })
      .then(function (subscription) {
        toast('Subscribed successfully.');
        console.info('Push notification subscribed.');
        console.log(subscription);
        saveSubscriptionID(subscription);
        changePushStatus(true);
      })
      .catch(function (error) {
        changePushStatus(false);
        console.error('Push notification subscription error: ', error);
      });
    })
  }

  // Unsubscribe the user from push notifications
  function unsubscribePush() {
    navigator.serviceWorker.ready
    .then(function(registration) {
      //Get `push subscription`
      registration.pushManager.getSubscription()
      .then(function (subscription) {
        //If no `push subscription`, then return
        if(! subscription) { alert('Unable to unregister push notification.');
          return;
        }

        //Unsubscribe `push notification`
        subscription.unsubscribe()
          .then(function () {
            toast('Unsubscribed successfully.');
            console.info('Push notification unsubscribed.');
            console.log(subscription);
            deleteSubscriptionID(subscription);
            changePushStatus(false);
          })
          .catch(function (error) {
            console.error(error);
          });
      })
      .catch(function (error) {
        console.error('Failed to unsubscribe push notification.');
      });
    })
  }

  //To change status
  function changePushStatus(status) {
    fabPushElement.dataset.checked = status;
    fabPushElement.checked = status;
    if (status) {
      fabPushElement.classList.add('active');
      fabPushImgElement.src = '.. /images/push-on.png';
    }
    else {
     fabPushElement.classList.remove('active');
     fabPushImgElement.src = '.. /images/push-off.png';
    }
  }

  //Click event for subscribe push
  fabPushElement.addEventListener('click'.function () {
    var isSubscribed = (fabPushElement.dataset.checked === 'true');
    if (isSubscribed) {
      unsubscribePush();
    }
    else{ subscribePush(); }});function saveSubscriptionID(subscription) {
    var subscription_id = subscription.endpoint.split('gcm/send/') [1]; console.log("Subscription ID", subscription_id);

    fetch('http://localhost:3333/api/users', {
      method: 'post',
      headers: {
        'Accept': 'application/json'.'Content-Type': 'application/json'
      },
      body: JSON.stringify({ user_id : subscription_id })
    });
  }

  function deleteSubscriptionID(subscription) {
    var subscription_id = subscription.endpoint.split('gcm/send/') [1]; fetch('http://localhost:3333/api/user/' + subscription_id, {
      method: 'delete',
      headers: {
        'Accept': 'application/json'.'Content-Type': 'application/json'}}); } isPushSupported(); //Checkfor push notification support
})(window);
Copy the code

Let’s try activating the message push to see if a new user is created and stored in our API service database. Refresh the page and press the activate button, and see that there is an error on the console.

Don’t worry! The reason is that we didn’t create a manifest.json file in our application. Now the interesting thing is that the manifest.json file added to it will be faultless and adds a new feature to our application. With this manifest.json file, we can install our application on our screens. Viola!!!!!!

Now let’s create a manifest.json file with the following code


{
  "name": "PWA - Commits"."short_name": "PWA"."description": "Progressive Web Apps for Resources I like"."start_url": "./index.html? utm=homescreen"."display": "standalone"."orientation": "portrait"."background_color": "#f5f5f5"."theme_color": "#f5f5f5"."icons": [{"src": "./images/192x192.png"."type": "image/png"."sizes": "192x192"
    },
    {
      "src": "./images/168x168.png"."type": "image/png"."sizes": "168x168"
    },
    {
      "src": "./images/144x144.png"."type": "image/png"."sizes": "144x144"
    },
    {
      "src": "./images/96x96.png"."type": "image/png"."sizes": "96x96"
    },
    {
      "src": "./images/72x72.png"."type": "image/png"."sizes": "72x72"
    },
    {
      "src": "./images/48x48.png"."type": "image/png"."sizes": "48x48"}]."author": {
    "name": "Prosper Otemuyiwa"."website": "https://twitter.com/unicodeveloper"."github": "https://github.com/unicodeveloper"."source-repo": "https://github.com/unicodeveloper/pwa-commits"
  },
  "gcm_sender_id": "571712848651"
}
Copy the code

Now let’s take a quick look at what key means in manifest.json.

  • Name: Indicates the name of the application, as it is usually displayed on the screen for the user to see.
  • Short_name: indicates the abbreviation of the Web application name.
  • Description: Represents a general description of the Web application.
  • Start_url: Is the URL loaded when the user starts the Web application.
  • Display: Defines the default display mode for the Web application. There are different patternsfullscreen.standalone.minimal-ui
  • Orientation:
  • Background_color: Indicates the background color of the Web application.
  • Theme_color: Represents the default theme color of the application. It colors the status bar on Android.
  • ICONS: ICONS on the main screen
  • Author: Some information about the author
  • Gcm_sender_id: used to identify applicationsFirebasethesender_id, obtained below. The following figure

Reference this manifest.json file in your index. HTML and latest. HTML.


 <link rel="manifest" href="./manifest.json">
Copy the code

Now, clear the cache, refresh the app, and hit the message push button

Then you see the subscription ID printed in the console, look at the down database,

Yaaay!! It’s working

You can see the user’s subscription ID in the database, which means that our request was successful

Tip: RoboMongo is a graphical interface for managing mongodb databases.

You can try unsubscribing to see how it removes users from the API service database.

Send and receive push messages

In our service API, we make a POST request to/API /notify route, and then the backend receives the front-end request and pushes it to Firebase Cloud Messaging service. Now, that’s not enough, so we also need a way to listen to and accept this notification in the browser.

Then the Service Worker pops up and listens for a push event in sw.js with the following code:



self.addEventListener('push'.function(event) {

  console.info('Event: Push');

  var title = 'New commit on Github Repo: RIL';

  var body = {
    'body': 'Click to see the latest commit'.'tag': 'pwa'.'icon': './images/48x48.png'
  };

  event.waitUntil(
    self.registration.showNotification(title, body)
  );
});

Copy the code

This code is added to sw.js. Clear the cache and reload your application, we now use postman to launch http://localhost:3333/api/notify requests

When a notification is sent, our browser welcomes a notification like this:

After receiving the notification, when the user clicks on the notification, we can decide what to do. Then add the following code to sw.js

self.addEventListener('notificationclick'.function(event) {

  var url = './latest.html';

  event.notification.close(); //Close the notification

  // Open the app and navigate to latest.html after clicking the notification
  event.waitUntil(
    clients.openWindow(url)
  );

});
Copy the code

Here, the above code listens for events that are triggered when the user clicks on the notification. Event.notification.close () is click to close notification. Then, will open a new browser window or TAB to localhost: 8080 / latest. HTML addresses.

Tip: event.waitUntil() is called before our new window opens to ensure that the browser does not terminate our server worker.

Automatic push message

Before we are through the Postman manually initiate a request to push message, in fact, we are think, for once you have submitted to https://github.com/unicodeveloper/resources-i-like/, we automatically receive receive a notification message. So how do we automate this process?

Have you heard of Webhooks??

Have!!!!!!

GitHub Webhooks

Tip: Use your own repository address, because when you see this, you commit yourself

To the warehouse you choose is https://github.com/unicodeveloper/resources-i-like/, I am here to Settings > Webhooks:

Click Add Webhook to add a hook. When the user commits a commit, the pusHS event is triggered. This hook will notify our notify API. Then it sends a browser push without a hitch. Open ~ ~ ~ ~

Look at the figure above, wait, wait, how did it get https://ea71f5aa.ngrok.io/api/notifyz this address?? You’re actually developing locally and you need to use the ngrok tool to forward the Intranet. I see!

Set the Ngrok

Very simple, we can’t use localhost, GitHub needs a URL that exists on the network, I use ngrok to expose the local server to the Internet.

After installing ngrok, on the command line, type code like this

./ngrok http 3333
Copy the code

get

Tip: Ngrok outputs HTTP and HTTPS addresses, both of which map to local services.

Now, as soon as you add Webhook, GitHub immediately submits a test POST request to determine if the setup is correct.

Test a COMMIT

We’ve got everything done, now we’re going to commit a commit, and once you do that, a message push is sent to our browser. The following figure

Host PWA

A PWA request service is over HTTPS. Firebase Hosting is a great option for deploying our application server with HTTPS support.

Our address on the app line: ril-pwa.firebaseapp.com/ The address on the server API line: rilapi.herokuapp.com/api

Add an app to the home screen

Open the browser on your device, especially Chrome, and add it like this:

You see the application icon on the desktop. Then this tutorial also sums up birds.

~~~~~ end, scattered flowers scattered flowers scattered flowers scattered flowers ~~~~


The attached:

Click the link to view

The original address

Part 1: Introducing progressive Web Apps (Offline) – Part 1

Part 2: Introducing progressive Web Apps (Instant Loading) – Part 2

Project PWA code

Project API code code

Personal Blog Address


If there is any mistake or mistake in translation, please kindly give me your advice. I will be very grateful