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

Troland發表於2019-02-16

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

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

這是 JavaScript 工作原理的第九章。

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

推送通知這一功能在移動端已經非常普遍。不知為何,網頁端的推送通知是千呼萬喚始出來,即使大多數開發者強烈地要求實現這一功能。

概述

網頁推送通知允許使用者選擇定時從網路應用獲取及時資訊。它旨在為使用者重新獲取其感興趣,重要和及時的資訊。

推送服務是基於服務工作執行緒的,服務工作執行緒在之前的文章中有詳細闡述過。

這個情況下,之所以採用服務工作執行緒是因為它會在後臺執行,從而不會阻塞介面的渲染。對於推送通知來說,這是相當重要的,因為這意味著只有當使用者和推送通知本身進行互動操作才會執行推送通知的相關程式碼。

訊息推送和通知

訊息推送和通知是兩個不同的介面。

  • 訊息推送-訊息推送伺服器向服務工作執行緒推送訊息時呼叫。
  • 訊息通知-網路應用中的服務工作執行緒或者指令碼進行操作向使用者顯示訊息通知。

訊息推送

實現訊息推送大概有以下三個步驟:

  • 介面-新增客戶端邏輯來讓使用者訂閱推送服務。在網路應用介面中書寫 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() 會向使用者彈出以下的提示框:

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

當獲得,關閉以及禁止許可權的時候,就可以得到 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 金鑰對,這對於訊息推送伺服器來說是唯一的。它們是由一對公鑰和私鑰所組成的。私鑰祕密儲存於推送伺服器端,公鑰用來和客戶端進行交換通訊用的。這些金鑰讓推送服務辨別訂閱使用者的應用伺服器以及確保觸發推送訊息到指定使用者的是同一個應用伺服器。

你只需要一次性生成應用程式私有/公有金鑰對。可以訪問 web-push-codelab.glitch.me/ 生成金鑰對。

當訂閱使用者的時候,瀏覽器向推送服務傳入 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 物件,你需要把它儲存在推送伺服器上。這樣就可以把該訂閱相關資料儲存在資料庫之中然後從今以後,就可以根據資料庫中的儲存值來給指定的使用者傳送訊息。

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

訊息推送

當需要傳送訊息到使用者的時候,首先需要有一個訊息推送服務。你通知推送服務(通過介面呼叫)需要推送的資料,訊息推送的目標使用者以及任意條件下如何傳送訊息。一般情況下,這些介面呼叫是由訊息推送伺服器來完成的。

訊息推送服務

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

請注意這裡的訊息推送服務並不是由你來控制的-它是第三方服務。伺服器只是通過介面來和訊息推送服務進行通訊。Google’s FCM 是訊息推送服務之一。

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

開發人員可以選擇讓瀏覽器使用任意的訊息推送服務。

然而,所有的訊息推送服務都擁有一樣的介面,這樣就不會由於介面不一而增加訊息推送實現的難度。

可以從 PushSubscription 物件的 endpoint 屬性值獲得處理訊息推送的請求 URL 地址。

訊息推送介面

訊息推送服務介面提供了向使用者傳送訊息的一種方法。該介面是一個被稱為 Web Push Protocol 的 IETF 標準協議,裡面定義瞭如何呼叫訊息推送服務。

推送的訊息必須得加密。這樣可以防止訊息推送服務窺視到傳送的資料。這是至關重要的因為客戶端可以決定使用哪個訊息推送服務(可能會使用一些不被信任和不安全的訊息推送服務)。

訊息推送引數:

  • TTL-定義訊息在被刪除且不能夠傳輸之前在佇列中的儲存時長。
  • Priority-定義了每條訊息的優先順序,這樣就可以讓訊息推送服務只推送高優先順序的訊息以方便使用者節省裝置的電力。
  • Topic-為推送訊息設定主題名稱這樣就可以使用相同的主題名稱來置換掉掛起的訊息,所以一旦裝置啟用,使用者就不會收到過期的訊息。

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

瀏覽器訊息推送事件

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

  • 裝置連網。
  • 佇列中的訊息停留時長超過設定的 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 公鑰和私鑰對)。
  • 檢查瀏覽器端相容性,獲取授權,使用訊息推送伺服器生成的公鑰並生成訂閱物件,儲存該訂閱物件到推送伺服器上面。
  • 訊息推送服務(第三方服務)。

一張流程圖來表示吧:

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

打個廣告 ^.^

今日頭條招人啦!傳送簡歷到 likun.liyuk@bytedance.com ,即可走快速內推通道,長期有效!國際化PGC部門的JD如下:c.xiumi.us/board/v5/2H…,也可內推其他部門!

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

相關文章