PWA Learning and Practice series articles have been compiled into gitbook-PWA Learning Handbook, and the text has been synchronized to learning-PWA-ebook. Please indicate the author and source of reprint.

This is the fifth article in the PWA Learning & Practice series. All the code in this article can be found in the push branch of learning-PWA (after git clone, note the switch to push branch).

PWA, as one of the hottest technology concepts at present, has great significance for improving the security, performance and experience of Web applications, and is worth our understanding and learning. If you are interested in PWA, please pay attention to the PWA Learning and Practice series.

1. The introduction

In previous articles, I shared how to use manifest (and meta tags) to make your Web App more “native”; And how to use Service Worker to cache resources, accelerate the access speed of Web App, and provide partial offline function. In the next section, we’ll explore another important feature in PWA — Push & Notification. This capability allows us to push messages from the server to the user and direct the user to trigger the corresponding interaction.

In fact, notifications and notifications are two functions — the Push API and the Notification API. In order to better understand the related technologies, I will also introduce them in two parts: Push messages and Notification. In this article, we will first learn how to use the Push API to Push messages.

The Push API and Notification API are two separate technologies that can be used separately; However, the combination of the Push API and Notification API is a common pattern.

2. How do browsers implement server message Push

The whole process of Web Push is a bit more complicated than the previous ones. Therefore, before entering into specific technical details, we need to first understand the basic process and related concepts of the whole Push.

If you don’t know anything about Push, you might think that Push is our server interacting directly with the browser, using long connections, websockets, or other technologies to Push messages to the client. However, this is not the case with Web Push, which is actually a three-way interaction.

The three important “roles” in Push are:

  • Browser: our client
  • Push Service: A dedicated Push Service, which you can think of as a third party Service. Currently, Chrome and Firefox both have their own Push Service. In theory, any Push Service can be used as long as the browser supports it
  • Back-end services: In this case, our own back-end services

Here’s how these three interact in Web Push.

2.1. Message push process

The following figure, from the Draft Web Push protocol, shows the entire flow of Web Push:

+-------+ +--------------+ +-------------+ | UA | | Push Service | | Application | +-------+ +--------------+ | Server |  | | +-------------+ | Subscribe | | |--------------------->| | | Monitor | | |<====================>| | | | | | Distribute Push Resource | |-------------------------------------------->| | | | : : : | | Push Message | | Push Message |<---------------------| |<---------------------| | | | |Copy the code

The sequence diagram illustrates the steps of Web Push, which can be divided into two parts: subscribe and Push.

  • subscribeFirst, subscriptions:
    1. Ask Permission: This step is not in the flow shown above, it is actually a browser policy. The browser asks the user whether to allow notifications, and only after the user allows notifications can the user proceed.
    2. Subscribe: The browser (client) needs to Subscribe to the Push Service and gets a PushSubscription object
    3. Monitor: The subscription operation will communicate with the Push Service to generate the corresponding subscription information. The Push Service will maintain the corresponding information and keep contact with the client based on this.
    4. Distribute Push Resource: After a browser subscription is completed, it gets information about the subscription (exists inPushSubscriptionObject), we need to send this information to our server, where we can save it.

  • Push MessageAnd then push:
    1. Phase 1: When our server needs to Push a Message, it does not interact with the client directly, but informs the Push Service of relevant information through the Web Push protocol.
    2. Phase 2: Push Message Phase 2: After receiving the Message and passing the verification, the Push Service pushes the Message to the subscribed client based on the client information maintained by it.
    3. Finally, the client receives the message and completes the push process.

2.2. What is Push Service

In the Push process above, a lesser known role emerges: the Push Service. So what is Push Service?

A push service receives a network request, validates it and delivers a push message to the appropriate browser.

A Push Service can receive a network request, validate the request, and Push it to the appropriate browser client. Another very important feature of the Push Service is that it can help us save message queues when users are offline and then send messages to them when they are online.

Currently, different browser vendors use different Push services. Chrome, for example, uses Google’s own FCM (formerly KNOWN as GCM), as does Firefox. Do we need to write different code to accommodate services used by different browsers? The answer is no. Push Services follow the Web Push Protocol, which specifies the details of the request and its processing, ensuring that different Push services will also have a standard invocation.

One more point: We described the standard process for Push in the previous section, where the first step is for the browser to initiate a subscription, generating a PushSubscription pair. The Push Service generates a unique URL for each browser that initiates the subscription, so that when we Push a message to the URL, the Push Service knows which browser to notify. This URL information is also in the PushSubscription object, called endpoint.

So, if we know the value of the endpoint, does that mean we can push messages to the client? Not so. Here is a brief overview of security policies in Web Push.

2.3. How to ensure Push security

In Web Push, to ensure that clients only receive messages pushed by the server they subscribe to (other servers cannot Push messages even after reaching the endpoint), they need to digitally sign the Push information. The process is roughly as follows:

There is a public and private key pair in Web Push. The client holds the public key and the server holds the private key. When a client subscribes, it sends the public key to the Push Service, which maintains the public key with the corresponding endpoint. When the server wants to push messages, it will use the private key to digitally sign the sent data and generate an Authorization request header based on the digital signature. After the Push Service receives the request, it obtains the public key according to the endpoint and decrypts and verifies the digital signature. If the information matches, it indicates that the request is encrypted using the corresponding private key and that the request is from the server to which the browser subscribes. And vice versa.

How public and private keys are generated is explained in part 3.

3. How to use Push API to Push information to users

At this point, we have a basic understanding of the flow of Web Push. Let me show you how to use Web Push with some code.

This will continue to enhance our “book search” WebApp, based on the code on the SW-Cache branch.

To make the article and code clearer, the Web Push is divided into these sections:

  1. The browser initiates the subscription and sends the subscription information to the back end;
  2. Save the subscription information on the server for future push use;
  3. The server pushes a message and requests the Push Service.
  4. The browser receives the Push message and processes it.

Note: The Push Service Chrome relies on — FCM — is not accessible in China, so you need a “ladder” to run the code in demo, or you can test it in Firefox.

3.1. Browser (client) generates subscription information

First, we need to subscribe in the browser using the Subscribe method of PushManager.

We’ve seen how to register a Service Worker in Making Your WebApp Offline. After when we register the Service Worker will get a Registration object, by calling the Registration. The Registration object pushManager. The subscribe () method can initiate subscription.

In order to make the code clearer, this demo first removes the Service Worker registration method from the previous one:

// index.js
function registerServiceWorker(file) {
    return navigator.serviceWorker.register(file);
}
Copy the code

The subscribeUserToPush() method is then defined to initiate the subscription:

// index.js
function subscribeUserToPush(registration, publicKey) {
    var subscribeOptions = {
        userVisibleOnly: true.applicationServerKey: window.urlBase64ToUint8Array(publicKey)
    }; 
    return registration.pushManager.subscribe(subscribeOptions).then(function (pushSubscription) {
        console.log('Received PushSubscription: '.JSON.stringify(pushSubscription));
        return pushSubscription;
    });
}

Copy the code

Used here for registration. PushManager. The subscribe () method of the two configuration parameters: userVisibleOnly and applicationServerKey.

  • userVisibleOnlyIndicates whether the push needs to be explicitly presented to the user, that is, whether there is a message alert when the push is made. If there is no notification, it indicates a “silent” push. In Chrome, you must set it totrueOtherwise, the browser will report an error on the console:

  • applicationServerKeyIs a public key of a client,VAPIDDefines its specification, and therefore can also be called VAPID Keys. If you remember the security policies mentioned in 2.3, you are familiar with this public key. This parameter must be of the Unit8Array type. So we defined oneurlBase64ToUint8ArrayThe base64 public key string is converted to Unit8Array.subscribe()It’s also a Promise method, and in then we get information about the subscription — onePushSubscriptionObject. The following figure shows some of the information in this object. Notice theendpoint, the Push Service randomly generates a different value for each client.

After that, we send the PushSubscription information to the back end. Here we define a sendSubscriptionToServer() method, which is just a plain XHR request that posts subscription information to the interface, without listing the code to save space.

Finally, combine the set of methods. Of course, before using Web Push, you still need to check the feature ‘PushManager’ in window.

// index.js
if ('serviceWorker' in navigator && 'PushManager' in window) {
    var publicKey = 'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A';
    // Register the service worker
    registerServiceWorker('./sw.js').then(function (registration) {
        console.log('Service Worker registered successfully ');
        // Enable the notification push subscription function of the client
        return subscribeUserToPush(registration, publicKey);
    }).then(function (subscription) {
        var body = {subscription: subscription};
        // Simply generate an identity for each client to facilitate later push
        body.uniqueid = new Date().getTime();
        console.log('uniqueid', body.uniqueid);
        // Stores the generated client subscription information on its own server
        return sendSubscriptionToServer(JSON.stringify(body));
    }).then(function (res) {
        console.log(res);
    }).catch(function (err) {
        console.log(err);
    });
}
Copy the code

Note that a unique IDuniqueid is generated for each client for our later push, and the timestamp is used to generate a simple uniqueID.

In addition, since userVisibleOnly is true, user authorization is required to enable notification, so we will see the following prompt box, select “Allow”. You can manage notifications in Settings.

3.2. The server stores client Subscription information

To store the subscription information posted by the browser, the server needs to add an interface /subscription and middleware KOA-Body to handle the body

// app.js
const koaBody = require('koa-body');
/** * Submit the subscription information and save */
router.post('/subscription', koaBody(), async ctx => {
    let body = ctx.request.body;
    await util.saveRecord(body);
    ctx.response.body = {
        status: 0
    };
});
Copy the code

Once the subscription message is received, it needs to be saved on the server. You can save it in any way: mysql, redis, mongodb… Here, for convenience, I use NEdb for simple storage. Nedb does not need to be deployed and installed. Data can be stored in memory or persisted. Nedb API is similar to mongodb.

Here util.saverecord () does this: First, it checks for the subscription information and updates only the uniqueID if it does; Otherwise, it is stored directly.

At this point, we have stored the client’s subscription information. Now, you can wait for future push to use.

3.3. Use subscription information to push information

In practice, we usually provide a push configuration background for operation or product students. You can select the corresponding client, fill in the push information, and initiate push. For simplicity, INSTEAD of writing a push configuration background, I provided only a POST interface /push to submit push messages. Later we can completely develop the corresponding push background to call this interface.

// app.js
/** * Push API, which can be called in the admin background * in this example, you can post a request directly to see the effect */
router.post('/push', koaBody(), async ctx => {
    let {uniqueid, payload} = ctx.request.body;
    let list = uniqueid ? await util.find({uniqueid}) : await util.findAll();
    let status = list.length > 0 ? 0 : - 1;

    for (let i = 0; i < list.length; i++) {
        let subscription = list[i].subscription;
        pushMessage(subscription, JSON.stringify(payload));
    }

    ctx.response.body = {
        status
    };
});
Copy the code

Take a look at the /push interface.

  1. First of all, depending on the parameters of post, we can passuniqueidTo query a subscription:util.find({uniqueid}); You can also query all subscriptions from the database:util.findAll().
  2. Then throughpushMessage()Method sends a request to the Push Service. As we learned in Section 2, this request needs to comply with the Web Push protocol. However, the operations related to request encapsulation and encryption of Web Push protocol are very complicated. Therefore, Web Push provides a set of libraries for developers of various languages:Web Push LibarayNodeJS, PHP, Python, Java, etc. Leaving these complicated and tedious operations to them can make us do more with less effort.
  3. The result is simply returned based on whether there is a subscription.

Install node version web-push

npm install web-push --save
Copy the code

The public and private keys mentioned earlier can also be generated by Web-push

Using Web-push is very simple, starting with setting VAPID Keys:

// app.js
const webpush = require('web-push');
/** * VAPID value * here can be replaced with the actual value in your business */
const vapidKeys = {
    publicKey: 'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A'.privateKey: 'TVe_nJlciDOn130gFyFYP8UiGxxWd3QdH6C5axXpSgM'
};

// Set the VAPID value of the web-push
webpush.setVapidDetails(
    'mailto:[email protected]',
    vapidKeys.publicKey,
    vapidKeys.privateKey
);
Copy the code

After that, you can send a request to the Push Service using the webpush.sendNotification() method.

Finally, let’s look at the details of the pushMessage() method:

// app.js
/** * Push info to push service * @param {*} subscription * @param {*} data */
function pushMessage(subscription, data = {}) {
    webpush.sendNotification(subscription, data, options).then(data= > {
        console.log('Push service data :'.JSON.stringify(data));
        return;
    }).catch(err= > {
        // Check the status code, 440 and 410 indicate invalid
        if (err.statusCode === 410 || err.statusCode === 404) {
            return util.remove(subscription);
        }
        else {
            console.log(subscription);
            console.log(err); }})}Copy the code

Webpush. SendNotification encapsulates the processing details of the request for us. The status codes 401 and 404 indicate that the subscription is invalid and can be removed from the database.

3.4. The Service Worker listens for Push messages

After calling webpush.sendNotification(), we have sent the message to the Push Service; The Push Service pushes our message to the browser.

To get the push message in the browser, just listen for the push event in the Service Worker:

// sw.js
self.addEventListener('push'.function (e) {
    var data = e.data;
    if (e.data) {
        data = data.json();
        console.log(The data for 'push 'is:, data);
        self.registration.showNotification(data.text);        
    } 
    else {
        console.log('Push doesn't have any data'); }});Copy the code

4. Effect display

We use Both Firefox and Chrome to access the WebApp and push messages to both clients. We can use the uniqueID printed in the console to make /push requests in Postman for testing.

As you can see, we pushed the “Welcome to PWA” message to Firefox and Chrome, respectively. The output in the console comes from listening for the push event in the Service Worker. The browser alert that pops up comes from the userVisibleOnly: True property configured at subscription time mentioned earlier. In future articles, I’ll continue to walk you through the use of the Notification API.

As mentioned earlier, the Push Service can maintain Push messages for you when your device is offline. The push is received when the browser device is reconnected to the Internet. The following shows how the device will receive a push when it is connected again:

5. Compatibility with all evils

It’s time to check compatibility again. More importantly, the Safari team has not explicitly stated plans to support the Push API.

Of course, a bigger problem than compatibility is that the FCM service Chrome relies on is not accessible in The country, whereas Firefox’s service is available in the country. That’s why you have this setting in your code:

const options = {
    // Proxy: 'http://localhost:1087' // To use FCM (Chrome), you need to configure the proxy
};
Copy the code

The above code is actually used to configure the Web-push proxy. One thing to note here is that web-push currently installed from NPM does not support setting proxy options. There is a special issue on Github about this and recently (two weeks ago) a corresponding PR was added. Therefore, if you need web-push support for proxies, the easy way is to tweak the Web-push code accordingly based on the master.

Although the domestic Push function cannot be used on Chrome due to the blocking of Google services, as an important technology point, Web Push is still worth our understanding and learning.

6. Write at the end

All of the code examples in this article can be found at learn-pwa/push. Note that after Git Clone, switch to the push branch. Switch to another branch to see different versions:

  • Basic branch: Basic project Demo, a general book search application (website);
  • Manifest branch: Based on basic branch, add manifest and other functions;
  • Sw-cache branch: based on the MANIFEST branch, add caching and offline functions;
  • Push branch: based on sw-cache branch, add the message push function of the server.
  • Master branch: The latest code for the application.

If you like or want to learn more about PWA, please feel free to follow me and follow the PWA Learning & Practice series. I will summarize the questions and technical points I have encountered in the process of learning PWA, and practice with everyone through actual codes.

In the next article, we will slow down – to do a good job, you must first sharpen your tools. Before going on to learn more about PWA-related technologies, let’s take a look at some PWA debugging tips for Chrome. After that, we’ll come back to another feature that is often combined with the Push API — the message Notification API.

PWA Learning and Practice series

  • Start your PWA learning journey in 2018
  • Learn to Use Manifest to Make your WebApp More “Native”
  • Make your WebApp available offline from today
  • Article 4: TroubleShooting: TroubleShooting FireBase Login Authentication Failures
  • Article 5: Keep In touch with Your Users: Web Push Functionality
  • How to Debug? Debug your PWA in Chrome
  • Enhanced Interaction: Use the Notification API for reminders
  • Chapter 8: Background Data Synchronization using Service Worker
  • Chapter nine: Problems and solutions in PWA practice
  • Resource Hint – Improving page loading performance and Experience
  • Part 11: Learning offline Strategies from workbox

The resources

  • Generic Event Delivery Using HTTP Pus (draft-ietf-webpush-protocol-12)
  • Brief introduction to FCM
  • How Push Works