Core Technology of Progressive Web Apps(PWA) - Push Notification Using Firebase Cloud Messaging

Keywords: curl JSON Attribute Google

Chrome is currently in use Firebase cloud messaging (FCM) As its push service. FCM recently adopted the Web Push protocol. FCM is a follow-up product of Google Cloud Messaging (GCM), which supports the same and more functions.

To use Firebase cloud messaging, you need to set up projects on Firebase (see the VAPID section to bypass this step). The general process is as follows:

1. In the Firebase console, choose to create a new project.
2. Provide the project name, and then click Create the project.
3. Click the Settings icon next to the project name in the navigation panel, and then select Project Settings.
4. Open the cloud messaging tab. You can find your server key and sender ID on this page. Save these values.
In order to route FCM mail to the correct service worker, you need to know the sender ID. This function is provided by adding the gcm_sender_id attribute to the manifest.json file of the application. For example:

{
  "name": "Push Notifications app",
  "gcm_sender_id": "370072803732"
}

To enable FCM to push load-free notifications to your Web client, the request must contain the following:

  • Subscription endpoint URL
  • Public server key. FCM uses it to check whether the requesting server is allowed to send messages to the receiving user.
    A production site or application usually sets up a service to allow the server to interact with the FCM.

We can use cURL to test push messages in our applications. We can send an empty message called tickle to the push service, and then the push service sends a message to the browser. If the notification shows, then we have done everything right, and our application is ready to push messages from the server.

The cURL command that sends the request to the FCM to send the push message:

curl "ENDPOINT_URL" --request POST --header "TTL: 60" --header "Content-Length: 0" \
--header "Authorization: key=SERVER_KEY"

For example:

curl "https://android.googleapis.com/gcm/send/fYFVeJQJ2CY:APA91bGrFGRmy-sY6NaF8a...gls7HZcwJL4 \ 
LFxjg0y0-ksEhKjpeFC5P" --request POST --header "TTL: 60" --header "Content-Length: 0" \
 --header "Authorization: key=AIzaSyD1JcZ8WM1vTtH6Y0tXq_Pnuw4jgj_92yg"

It's relatively easy to push messages to users. So far, however, the notifications we have sent have been empty. Chrome and Firefox support the ability to send data to service worker using push messages.
Let's first look at what changes the service worker needs to make to extract the data from the push message.

self.addEventListener('push', function(e) {
  var body;

  if (e.data) {
    body = e.data.text();
  } else {
    body = 'Push message no payload';
  }

  var options = {
    body: body,
    icon: 'images/notification-flat.png',
    vibrate: [100, 50, 100],
    data: {
      dateOfArrival: Date.now(),
      primaryKey: 1
    },
    actions: [
      {action: 'explore', title: 'Explore this new world',
        icon: 'images/checkmark.png'},
      {action: 'close', title: 'I don't want any of this',
        icon: 'images/xmark.png'},
    ]
  };
  e.waitUntil(
    self.registration.showNotification('Push Notification', options)
  );
});

When we receive a push notification with data, the data is directly available on the event object. These data can be of any type, json, blob, array or text.

Server push
Take nodejs as an example:
Install push service: npm install web-push

var webPush = require('web-push');

var pushSubscription = {"endpoint":"https://android.googleapis.com/gcm/send/f1LsxkKphfQ:APA91bFUx7ja4BK4JVrNgVjpg1cs9lGSGI6IMNL4mQ3Xe6mDGxvt_C_gItKYJI9CAx5i_Ss6cmDxdWZoLyhS2RJhkcv7LeE6hkiOsK6oBzbyifvKCdUYU7ADIRBiYNxIVpLIYeZ8kq_A",
"keys":{"p256dh":"BLc4xRzKlKORKWlbdgFaBrrPK3ydWAHo4M0gs0i1oEKgPpWC5cW8OCzVrOQRv-1npXRWk8udnW3oYhIO4475rds=", "auth":"5I2Bu2oKdyy9CwL8QVF0NQ=="}};

var payload = 'Here is a payload!';

var options = {
  gcmAPIKey: 'AIzaSyD1JcZ8WM1vTtH6Y0tXq_Pnuw4jgj_92yg',
  TTL: 60
};

webPush.sendNotification(
  pushSubscription,
  payload,
  options
);

This example passes the subscription object, valid content, and server key to the sendNotification method. It also passes a lifetime, which is a value in seconds, which describes how long the push service retains the push message (default is four weeks).

Identify your service through VAPID authentication
Web push protocols are designed to respect user privacy by maintaining user anonymity and not requiring a high degree of authentication between your application and push services. This poses some challenges:

  • Uncertified push services face greater risk of attack
  • Any application server with an endpoint can send messages to users
  • If there is a problem, the push service cannot contact the developer.
    The solution is to let publishers use the Voluntary Application Server Identification (VAPID) Web Push Protocol. This provides at least a stable identity for the application server, which can also include contact information, such as e-mail addresses.

The specification lists several benefits of using VAPID:

  • Push services can use a consistent identity to establish desired behavior for application servers. An exception handler can then be triggered using a significant deviation from the established criteria.
  • In special cases, voluntary contact information can be used to contact application server operators.
  • Experience in deploying push services shows that software errors or exceptions can lead to a significant increase in the number of push messages. Contacting the operator of the application server has proven to be valuable.
  • Even if there is no available contact information, a reputable application server may take precedence over an unrecognized application server in selecting whether to discard a push message.
  • Using VAPID can also avoid specific FCM steps for sending push messages. You no longer need Firebase projects, gcm_sender_id or Authorization headers.

Using VAPID
The process is very simple:

Your application server creates a public/private key pair. The public key is your web app.
When the user chooses to receive the push, the public key is added to the subscribe () call options object.
When your server sends a push message, include the signed JSON Web Token with the public key.

Create a public/private key pair
The following are the relevant parts of the specification concerning VAPID public/private key format:

The application server should generate and maintain a signature key pair on the P-256 curve for elliptic curve digital signature (ECDSA).
With the help of web-push node library Complete this part:

function generateVAPIDKeys() {  
  const vapidKeys = webpush.generateVAPIDKeys();
  return {
    publicKey: vapidKeys.publicKey,  
    privateKey: vapidKeys.privateKey,  
  };  
}

Subscription public key
To subscribe to a Chrome user who uses the VAPID public key for push, pass the public key as Uint8Array using the subscribe () method's application ServerKey parameter.

const publicKey = new Uint8Array([0x40x370x770xfe,....]);
serviceWorkerRegistration.pushManager.subscribe(
  {
     userVisibleOnly: true,
     applicationServerKey: publicKey
  }
);

You will check the endpoints in the generated subscription object to see if it works properly; if the source is fcm.googleapis.com, it means it is running.

Note: Although this is an FCM URL, use the Web push protocol instead of the FCM protocol, so that your server-side code will apply to any push service.
Send push message

To send messages using VAPID, you can use two other HTTP headers for normal Web push protocol requests: Authorization header and JWT header.

  • Authorization header

The authorization header is a signature JSON Web Token (JWT) with "WebPush".

The format is as follows:
<JWTHeader>.<Payload>.<Signature>

JWT header:

{  
  "typ": "JWT",  //type
  "alg": "ES256"  //Signature algorithm name
}

Payload:(This JSON object is base64 url encoding)

{  
    "aud": "http://push-service.example.com",  //It can be obtained by const audience = new URL(subscription.endpoint).origin
    "exp": Math.floor((Date.now() / 1000) + (12 * 60 * 60)),  //Expiration date
    "sub": "mailto: my-email@some-url.com"  //It could be a contact url or mailto
}

Signature
This signature is encrypted by the private key generated by the encoding header and payload, which are dotted and then connected with the previously created VAPID. Signature Library
The signed JWT is used as the authorization header and WebPush is prefixed as follows:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Here are some points to be pointed out. First, the authorization Title literally contains the word WebPush, followed by a space, and then JWT. Also note the points that separate JWT header s, payload s, and signature s.

Crypto-Key
Like the authorization title, you must add your VAPID public key to the encrypted key header as a base64 url-encoded string prefixed with P256 ECDSA =.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

When you send notifications using encrypted data, you already use Crypto-Key header, so to add an application server key, just add a comma before adding the above content, as follows:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE,
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

There was a flaw in Chrome prior to version 52, which required semicolons instead of commas in Crypto-key header.

cURL sends push messages:

curl "https://updates.push.services.mozilla.com/wpush/v1/gAAAAABXmk....dyR" --request POST --header "TTL: 60" --header "Content-Length: 0" --header "Authorization: WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A" --header "Crypto-Key: p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo"

Noejs sends push messages:

var webPush = require('web-push');

var pushSubscription = {"endpoint":"https://fcm.googleapis.com/fcm/send/c0NI73v1E0Y:APA91bEN7z2weTCpJmcS-MFyfbgjtmlAWuV5YaaNw625_Rq2-f0ZrVLdRPXKGm7B3uwfygicoCeEoWQxCKIxlL3RWG2xkHs6C8-H_cxq-4Z-isAiZ3ixo84-2HeXB9eUvkfNO_t1jd5s","keys":{"p256dh":"BHxSHtYS0q3i0Tb3Ni6chC132ZDPd5uI4r-exy1KsevRqHJvOM5hNX-M83zgYjp-1kdirHv0Elhjw6Hivw1Be5M=","auth":"4a3vf9MjR9CtPSHLHcsLzQ=="}};

var vapidPublicKey = 'BAdXhdGDgXJeJadxabiFhmlTyF17HrCsfyIj3XEhg1j-RmT2wXU3lHiBqPSKSotvtfejZlAaPywJ9E-7AxXQBj4
';
var vapidPrivateKey = 'VCgMIYe2BnuNA4iCfR94hA6pLPT3u3ES1n1xOTrmyLw
';

var payload = 'Here is a payload!';

var options = {
  vapidDetails: {
    subject: 'mailto:example_email@example.com',
    publicKey: vapidPublicKey,
    privateKey: vapidPrivateKey
  },
  TTL: 60
};

webPush.sendNotification(
  pushSubscription,
  payload,
  options
);

Posted by maxsslade on Sun, 16 Dec 2018 14:24:04 -0800