JavaScript 工做原理之九-網頁消息推送通知機制

原文請查閱 這裏,略有刪減,本文采用 知識共享署名 4.0 國際許可協議共享,BY Troland

本系列持續更新中,Github 地址請查閱這裏javascript

這是 JavaScript 工做原理的第九章。html

如今讓咱們把注意力轉移到網頁推送通知:咱們將會查看其構造,探索發送/接收通知背後的過程以及最後分享一下咱們在 SessionStack 是如何計劃利用這些功能來建立新的產品功能的。java

推送通知這一功能在移動端已經很是廣泛。不知爲什麼,網頁端的推送通知是千呼萬喚始出來,即便大多數開發者強烈地要求實現這一功能。node

概述

網頁推送通知容許用戶選擇定時從網絡應用獲取及時信息。它旨在爲用戶從新獲取其感興趣,重要和及時的信息。git

推送服務是基於服務工做線程的,服務工做線程在以前的文章中有詳細闡述過。github

這個狀況下,之因此採用服務工做線程是由於它會在後臺運行,從而不會阻塞界面的渲染。對於推送通知來講,這是至關重要的,由於這意味着只有當用戶和推送通知自己進行交互操做纔會執行推送通知的相關代碼。web

消息推送和通知

消息推送和通知是兩個不一樣的接口。數據庫

  • 消息推送-消息推送服務器向服務工做線程推送消息時調用。
  • 消息通知-網絡應用中的服務工做線程或者腳本進行操做向用戶顯示消息通知。

消息推送

實現消息推送大概有如下三個步驟:promise

  • 界面-添加客戶端邏輯來讓用戶訂閱推送服務。在網絡應用界面中書寫 JavaScript 代碼邏輯來讓用戶註冊消息推送服務。
  • 發送消息-在服務器端實現接口調用來觸發向用戶設備推送消息。
  • 接收消息-一旦在瀏覽器端接收到推送消息則處理之。

如今,讓咱們詳細闡述整個過程。瀏覽器

兼容性檢測

首先,須要檢測當前瀏覽器是否支持消息推送服務。能夠採用如下兩種簡單的檢查:

  • 檢測 navigator 對象上的 serviceWorker 屬性
  • 檢測 window 對象上的 PushManager 屬性

都檢測代碼以下:

if (!('serviceWorker' in navigator)) {
   // 當前瀏覽器不支持服務器工做線程,禁用或者隱藏界面
  return; 
}

if (!('PushManager' in window)) {
  // 當前瀏覽器不支持推送服務,禁用或者隱藏界面 
  return; 
}

註冊服務工做線程

如今,消息推送功能是支持的。下一下即註冊服務工做線程。

從以前的文章中你應該很熟悉如何註冊服務工做線程。

請求受權

當註冊服務工做線程以後,接下來進行用戶訂閱的相關操做。這須要得到用戶的受權來向其推送消息。

得到受權的接口至關的簡單但有一個缺點即接口 接受的參數之前是一個回調函數如今是一個 Promise。由於沒法知曉當前瀏覽器支持的接口版本,因此須要進行兼容處理。

相似這樣:

function requestPermission() {
  return new Promise(function(resolve, reject) {
    const permissionResult = Notification.requestPermission(function(result) {
      // 使用回調來處理廢棄的接口版本
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  })
  .then(function(permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error('Permission not granted.');
    }
  });
}

調用 Notification.requestPermission() 會向用戶彈出如下的提示框:

當得到,關閉以及禁止權限的時候,就能夠獲得 granteddefault 或者 denied 的結果字符串。

須要注意的是當用戶點擊 禁止 按鈕,網絡應用將不會再次詢問用戶受權直到用戶手動開啓更改受權狀態。該選項隱藏於設置面板中。

點擊地址欄最左邊的信息按鈕便可彈出受權的彈窗。

經過 PushManager 訂閱用戶

一旦服務工做線程註冊成功且得到受權,就能夠在註冊服務器線程的時候經過調用 registration.pushManager.subscribe() 來訂閱用戶。

整個代碼片段以下(包括註冊服務工做線程):

function subscribeUserToPush() {
  return navigator.serviceWorker.register('service-worker.js')
  .then(function(registration) {
    var subscribeOptions = {
      userVisibleOnly: true,
      applicationServerKey: btoa(
        'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
      )
    };

    return registration.pushManager.subscribe(subscribeOptions);
  })
  .then(function(pushSubscription) {
    console.log('PushSubscription: ', JSON.stringify(pushSubscription));
    return pushSubscription;
  });
}

registration.pushManager.subscribe(options) 中有一個 options 對象參數,其中包含有必須或者可選的參數:

  • userVisibleOnly:返回的推送訂閱是否僅對訂閱用戶可見。必須設置爲 true 不然會出錯(這是歷史緣由形成的)。
  • applicationServerKey:一個包含公鑰的 Base64 編碼的 DOMString 字符串或者 ArrayBuffer ,消息推送服務器用來驗證應用服務器。

消息推送服務器須要生成一對應用服務器密鑰對-即 VAPID 密鑰對,這對於消息推送服務器來講是惟一的。它們是由一對公鑰和私鑰所組成的。私鑰祕密存儲於推送服務器端,公鑰用來和客戶端進行交換通信用的。這些密鑰讓推送服務辨別訂閱用戶的應用服務器以及確保觸發推送消息到指定用戶的是同一個應用服務器。

你只須要一次性生成應用程序私有/公有密鑰對。能夠訪問 https://web-push-codelab.glit... 生成密鑰對。

當訂閱用戶的時候,瀏覽器向推送服務傳入 applicationServerKey (公鑰),意即推送服務把應用服務器公鑰和用戶的 PushSubscription 綁定在一塊兒。

過程以下:

  • 網絡應用加載完成而後,調用 subscribe ,傳入服務器公鑰。
  • 瀏覽器向消息推送服務發起請求生成一個端點信息並連同密鑰信息一塊兒返回給瀏覽器。
  • 瀏覽器把端信息添加到由 subscribe() promise 所返回的 PushSubscription 對象中。

以後,每當須要推送信息的時候,必須發送一個認證頭其中包含應用服務器私鑰簽名的信息。

每當推送服務接收到推送消息的請求,它會經過在傳輸頭中查找已經和指定端(第二步中)綁定的公鑰來進行驗證。

PushSubscription 對象

PushSubscription 包含了向用戶設備推送信息所必備的一切信息。大概包含以下信息:

{
  "endpoint": "https://domain.pushservice.com/some-id",
  "keys": {
    "p256dh":
"BIPUL12DLfytvTajnryr3PJdAgXS3HGMlLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WArAPIxr4gK0_dQds4yiI=",
    "auth":"FPssMOQPmLmXWmdSTdbKVw=="
  }
}

endpoint 便是推送服務地址。當須要推送消息時,向該地址發起 POST 請求。

keys 對象包含用來加密隨推送消息一塊兒發送的信息數據的值。

當用戶訂閱以後且返回了 PushSubscription 對象,你須要把它保存在推送服務器上。這樣就能夠把該訂閱相關數據保存在數據庫之中而後從今之後,就能夠根據數據庫中的存儲值來給指定的用戶發送消息。

消息推送

當須要發送消息到用戶的時候,首先須要有一個消息推送服務。你通知推送服務(經過接口調用)須要推送的數據,消息推送的目標用戶以及任意條件下如何發送消息。通常狀況下,這些接口調用是由消息推送服務器來完成的。

消息推送服務

消息推送服務是用來接收消息推送請求,驗證請求以及推送消息到指定的用戶瀏覽器端。

請注意這裏的消息推送服務並非由你來控制的-它是第三方服務。服務器只是經過接口來和消息推送服務進行通信。Google’s FCM 是消息推送服務之一。

消息推送服務會處理核心的事務。好比,當瀏覽器離線,推送服務在發送各自的消息以前會排隊消息且等待直到瀏覽器連網。

開發人員能夠選擇讓瀏覽器使用任意的消息推送服務。

然而,全部的消息推送服務都擁有同樣的接口,這樣就不會因爲接口不一而增長消息推送實現的難度。

能夠從 PushSubscription 對象的 endpoint 屬性值得到處理消息推送的請求 URL 地址。

消息推送接口

消息推送服務接口提供了向用戶發送消息的一種方法。該接口是一個被稱爲 Web Push Protocol 的 IETF 標準協議,裏面定義瞭如何調用消息推送服務。

推送的消息必須得加密。這樣能夠防止消息推送服務窺視到發送的數據。這是相當重要的由於客戶端能夠決定使用哪一個消息推送服務(可能會使用一些不被信任和不安全的消息推送服務)。

消息推送參數:

  • TTL-定義消息在被刪除且不可以傳輸以前在隊列中的保存時長。
  • Priority-定義了每條消息的優先級,這樣就可讓消息推送服務只推送高優先級的消息以方便用戶節省設備的電力。
  • Topic-爲推送消息設置主題名稱這樣就可使用相同的主題名稱來置換掉掛起的消息,因此一旦設備激活,用戶就不會收到過時的消息。

瀏覽器消息推送事件

每當發送消息到如上的推送服務,消息會處於待發送狀態直到發生如下幾種狀況:

  • 設備連網。
  • 隊列中的消息停留時長超過設置的 TTL。

當消息推送服務傳輸消息到瀏覽器,瀏覽器會接收到,解密,而後分派給服務工做線程 push 事件。

劃重點這裏即便沒有打開網頁,瀏覽器仍然能夠執行服務工做線程。會發生以下事件:

  • 瀏覽器解密接收的推送消息。
  • 瀏覽器喚醒服務工做線程。
  • 服務工做線程接收到 push 事件。

監聽推送事件和在 JavaScript 中寫的其它事件監聽很是相似。

self.addEventListener('push', function(event) {
  if (event.data) {
    console.log('This push event has data: ', event.data.text());
  } else {
    console.log('This push event has no data.');
  }
});

須要理解服務工做線程的一點即其運行時間是不可人爲控制的。只有瀏覽器能夠喚醒和結束它。

在服務工做線程中,event.waitUntil(promise) 告訴瀏覽器服務工做線程正在處理消息直到 promise 解析完成,若是想要完成消息的處理,那麼瀏覽器就不該該停止服務工做線程。

如下爲處理 push 事件的示例:

self.addEventListener('push', function(event) {
  var promise = self.registration.showNotification('Push notification!');

  event.waitUntil(promise);
});

調用 self.registration.showNotification() 向用戶彈出一個通知而且返回一個 promise,一旦通知顯示完成即解析完成。

能夠採用可視化的方法來設置符合本身需求的 showNotification(title, options) 方法。title 參數是字符串而 options 是一個相似以下的對象:

{
  "//": "視覺選項",
  "body": "<String>",
  "icon": "<URL String>",
  "image": "<URL String>",
  "badge": "<URL String>",
  "vibrate": "<Array of Integers>",
  "sound": "<URL String>",
  "dir": "<String of 'auto' | 'ltr' | 'rtl'>",

  "//": "行爲選項",
  "tag": "<String>",
  "data": "<Anything>",
  "requireInteraction": "<boolean>",
  "renotify": "<Boolean>",
  "silent": "<Boolean>",

  "//": "視覺和行爲選項",
  "actions": "<Array of Strings>",

  "//": "信息選項。沒有視覺效果",
  "timestamp": "<Long>"
}

能夠在這裏查看到每一個選項的更加詳細的內容。

每當想要和用戶分享緊急,重要及緊迫的信息的時候,消息推送服務是用來通知用戶的一個絕佳的方式。

參考資源

如下皆爲本身擴展的內容。

通知處理

服務工做線程能夠採用相似以下的代碼來進行處理:

self.addEventListener('notificationclick', function(event) {
  console.log('[Service Worker] Notification click Received.');
  
  event.notification.close();

  event.waitUntil(clients.openWindow('https://developers.google.com/web/'));
});

總結

nodejs 可使用這裏的庫來構建推送服務器。

作一個網頁消息推送所須要的條件即:

  • 消息推送服務器(調用消息推送服務及生成 VAPID 公鑰和私鑰對)。
  • 檢查瀏覽器端兼容性,獲取受權,使用消息推送服務器生成的公鑰並生成訂閱對象,保存該訂閱對象到推送服務器上面。
  • 消息推送服務(第三方服務)。

一張流程圖來表示吧:

相關文章
相關標籤/搜索