[譯] JavaScript 是如何工作的:Web 推送通知的機制

LeviDing發表於2018-05-19

這是專門研究 JavaScript 及其構建元件系列文章的第 9 章。在識別和描述核心元素的過程中,我們還分享了我們在構建一個輕量級 JavaScript 應用程式 SessionStack 時使用的一些經驗規則,該應用程式需要健壯、高效能,可以幫助使用者實時檢視和重現它們的 Web 應用程式缺陷。

如果你錯過了前幾章,你可以在這裡找到它們:

  1. [譯] JavaScript 是如何工作的:對引擎、執行時、呼叫堆疊的概述
  2. [譯] JavaScript 是如何工作的:在 V8 引擎裡 5 個優化程式碼的技巧
  3. [譯] JavaScript 是如何工作的:記憶體管理 + 處理常見的4種記憶體洩漏
  4. [譯] JavaScript 是如何工作的: 事件迴圈和非同步程式設計的崛起 + 5個如何更好的使用 async/await 編碼的技巧
  5. [譯] JavaScript 是如何工作的:深入剖析 WebSockets 和擁有 SSE 技術 的 HTTP/2,以及如何在二者中做出正確的選擇
  6. [譯] JavaScript 是如何工作的:與 WebAssembly 一較高下 + 為何 WebAssembly 在某些情況下比 JavaScript 更為適用
  7. [譯] JavaScript 是如何工作的:Web Worker 的內部構造以及 5 種你應當使用它的場景
  8. [譯] JavaScript 是如何工作的:Web Worker 生命週期及用例

今天,我們來關注 Web 推送通知:我們將瞭解它們的構建元件,探索傳送/接收通知的流程,最後分享 SessionStack 是如何利用這些來構建新產品的特性。

推送通知在手機領域被廣泛使用。由於某種原因,它們很晚才進入 Web 領域,儘管開發人員呼喚了很久。

概述

Web 推送通知允許使用者在 Web 應用程式中選擇接收更新資訊,這些旨在重新吸引使用者群注意的更新資訊通常是對使用者來說有趣、重要、實時的內容。

推送基於我們在上一篇文章中詳細討論過的 Service Worker。

在這種情況下,使用 Service Worker 的原因是它們在後臺工作。這對推送通知非常有用,因為這意味著只有當使用者與通知本身進行互動時才會執行它們的程式碼。

推送和通知

推送和通知是兩種不同的 API。

  • 推送 —— 它在伺服器端將訊息推送給 Service Worker 時被呼叫
  • 通知 —— 這是 Service Worker 或 web 應用程式中向使用者顯示資訊指令碼的操作。

推送

實現推送有三個步驟:

  1. UI —— 新增必要的客戶端邏輯來讓使用者訂閱推送,這是你的 Web 應用程式 UI 需要的 JavaScript 邏輯,這樣使用者就能給自己註冊從而可以收到訊息推送。
  2. 傳送推送訊息 —— 在伺服器上實現 API 呼叫,該呼叫將觸發對使用者裝置的推送訊息。
  3. 接受推送訊息 —— 一旦推送訊息到達瀏覽器,就進行處理。

現在我們將更詳細地描述整個過程。

瀏覽器支援檢測

首先,我們需要檢查當前瀏覽器是否支援推送訊息。我們可以通過兩個簡單的方法檢查是否支援推送訊息:

  1. 檢查 navigator 物件上的 serviceWorker
  2. 檢查 window 物件上的 PushManager

兩種檢檢視起來都是這樣的:

if (!('serviceWorker' in navigator)) { 
  // Service Worker isn't supported on this browser, disable or hide UI. 
  return; 
}

if (!('PushManager' in window)) { 
  // Push isn't supported on this browser, disable or hide UI. 
  return; 
}
複製程式碼

註冊一個 Service Worker

此時,我們知道該功能是受支援的。下一步是註冊我們的 Service Worker。

如何註冊 Service Worker,你從我們以前的一篇文章中應該已經熟悉了

請求許可

在註冊了 Service Worker 之後,我們可以開始訂閱使用者。要做到這一點,我們需要得到他的許可才能給他傳送推送資訊。

獲取許可的 API 相對簡單,但缺點是 API 已經從接受回撥變為返回 Promise,這帶來了一個問題:我們無法判斷當前瀏覽器實現了哪個 API 版本,因此你必須實現和處理這兩個版本。

看起來是這樣的:

function requestPermission() {
  return new Promise(function(resolve, reject) {
    const permissionResult = Notification.requestPermission(function(result) {
      // Handling deprecated version with callback.
      resolve(result);
    });

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

Notification.requestPermission() 呼叫將向使用者顯示以下提示:

[譯] JavaScript 是如何工作的:Web 推送通知的機制

一旦被授權、關閉或阻止,我們將得到字串格式的結果:‘granted’‘default’‘denied’

記住,如果使用者單擊 Block 按鈕,你的 Web 應用程式將無法再次請求使用者的許可,直到他們通過更改許可權狀態手動 “unblock” 你的應用程式的限制。此選項隱藏在設定介面中。

使用者訂閱使用 PushManager

一旦我們註冊了 Service Worker 並獲得許可許可權,當你在註冊你的 Service Worker 時,我們就可以通過呼叫 registration.pushManager.subscribe() 來訂閱使用者。

整個片段可能如下所示(包括 Service Workder 註冊):

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-encoded DOMString 或者 ArrayBuffer 包含推送伺服器用來驗證應用伺服器的公鑰。

你的伺服器需要生成一對應用程式伺服器金鑰 —— 也稱為 VAPID 金鑰,對於你的伺服器來說,它們是唯一的。它們是一對公鑰和私鑰。私鑰被祕密儲存在你的終端,而公鑰則與客戶端交換。這些金鑰允許推送服務知道哪個應用伺服器訂閱了使用者,並確保它是觸發向該特定使用者推送訊息的相同伺服器。

你只需要為應用程式建立一次私鑰/公鑰對。做到這一點的方法是去完成這個 —— web-push-codelab.glitch.me/

瀏覽器在訂閱使用者時將 applicationServerKey(公鑰)傳遞給推送服務,這意味著推送服務可以將應用程式的公鑰繫結到使用者的 PushSubscription 中。

情況是這樣的:

  • 你的 web app 被載入時,你可以呼叫 subscribe() 來傳入你的 server 金鑰。
  • 瀏覽器向生成端點的推送服務發出請求,將此端點與該鍵關聯並將端點返回給瀏覽器。
  • 瀏覽器將此端點新增到 PushSubscription 物件中,該物件通過 subscribe() 的 promise 返回。

之後,無論你想何時傳送推送訊息,你都需要建立一個包含使用應用程式伺服器的專用金鑰簽名資訊的 Authorization header。當推送服務收到傳送推送訊息的請求時,它將通過查詢已經連線到該特定端點的公鑰來驗證頭(第二步)。

推送物件

PushSubscription 包含使用者裝置傳送推送訊息所需的所有資訊。就像這樣:

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

endpoint 是推送服務的 URL。要觸發推送訊息,請對此 URL 傳送 POST 請求。

這裡的 keys 物件的值是用來加密推送訊息帶過來的訊息資料。

一旦使用者被訂閱並且你有 PushSubscription,你需要將它傳送到你的伺服器。在那裡(在伺服器上),你將這個訂閱存到資料庫中,從今以後如果你要向該使用者推送訊息就使用它。

[譯] JavaScript 是如何工作的:Web 推送通知的機制

傳送推送訊息

當你想向使用者傳送推送訊息時,你首先需要一個推送服務。你要(通過 API 呼叫)告訴推送服務要傳送哪些資料,誰來接收資料以及其他關於怎麼傳送資料的標準。通常,此 API 呼叫是在你伺服器上完成的。

推送服務

推送服務是接收請求,驗證請求並將推送訊息傳遞給對應的瀏覽器。

注意推送服務不是由你管理的 —— 它是第三方服務。你的伺服器通過 API 與 推送服務進行通訊。推送服務的一個例子是 Google 的 FCM

推送服務處理所有繁重的任務,比如,如果瀏覽器處於離線狀態,推送服務會在傳送相應訊息之前對訊息進行排隊,等待瀏覽器的再次聯機。

每個瀏覽器都使用它們想要的任何推送服務,這是開發者無法控制的。

然而所有的推送服務都具有相同的 API,因此在實現過程中不會有很大難度。

為了獲得 URL 來進行訊息推送請求,你需要檢查 PushSubscription 物件中儲存的 endpoint 值。

推送服務 API

推送服務 API 提供了一種將訊息傳送給使用者的方式。API 基於 Web 推送協議,它是一種定義瞭如何對推送服務進行 API 呼叫的 IETF 標準。

你使用推送訊息傳送的資料必須被加密。這樣可以防止推送服務檢視傳送的資料。這很重要,因為瀏覽器是可以決定使用哪種推送服務的(它可能使用了一些不受信任且不夠安全的伺服器)。

對於每個推送訊息,你還可以提供下列說明:

  • TTL —— 定義訊息會在佇列中等多久,超過這個時間訊息就會被刪除不做推送。。
  • 優先順序 —— 定義訊息的優先順序,因為推送服務只傳送高優先順序的訊息,以此來保護使用者裝置的電池壽命。
  • Topic —— 給推送訊息一個主題,新訊息會替換等待中的帶相同主題的訊息,這樣一旦裝置處於活動狀態,使用者將不會收到過時的訊息。

[譯] JavaScript 是如何工作的:Web 推送通知的機制

瀏覽器中的推送事件

如上所述,將訊息傳送到推送服務後,訊息將處於掛機狀態,直到發生下列情況之一:

  • 裝置上線。
  • 訊息由於 TTL 而在佇列上過期。

當推送服務傳遞訊息時,瀏覽器會接收它,解密並在 Service Worker 中分發一個 push 事件。

這裡最好的是,即使是你的網頁沒有開啟,瀏覽器也可以執行你的 Service Worker。將會發生下面的事情:

  • 推送訊息到達解密它的瀏覽器
  • 瀏覽器喚醒 Service Worker
  • push 事件被分發給 Service Worker

設定推送事件監聽器的程式碼應該與用 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.');
  }
});
複製程式碼

需要了解 Service Worker 的一點是,你沒有 Service Worker 程式碼執行時長的控制權。瀏覽器決定何時將其喚醒以及何時終止它。

在 Service Workers 中,event.waitUntil(promise) 通知瀏覽器工作正在進行,直到 promise 確定為止,如果它想要完成該工作,它不應該終止 sercice worker。

這裡是處理 push 事件的例子:

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

  event.waitUntil(promise);
});
複製程式碼

呼叫 self.registration.showNotification() 會向使用者傳送一個通知,並返回一個 promise,只要訊息展示了該 promise 就會觸發 resolve。

showNotification(title, options) 方法可以在視覺上進行調整以適應你的需求。title 引數是一個 string,而 options 是一個看起來像這樣的物件:

{
  "//": "Visual 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'>",

  "//": "Behavioural Options",
  "tag": "<String>",
  "data": "<Anything>",
  "requireInteraction": "<boolean>",
  "renotify": "<Boolean>",
  "silent": "<Boolean>",

  "//": "Both Visual & Behavioural Options",
  "actions": "<Array of Strings>",

  "//": "Information Option. No visual affect.",
  "timestamp": "<Long>"
}
複製程式碼

你可以在這裡閱讀到每個選項內容的更多細節 — developer.mozilla.org/en-US/docs/…

推送通知是一種可以在有緊急、重要和時間敏感的資訊需要與使用者進行分享的情況下,吸引使用者注意的絕好方式。

例如,我們在 SessionStack 計劃利用推送通知來提醒使用者,讓他們知道自己的產品中何時發生崩潰、問題或異常。這會讓使用者立即知道出現了問題。然後他們可以利用我們的庫所收集的資料(如 DOM 修改、使用者互動、網路請求、未處理異常和除錯資訊),以視訊的形式重現問題並檢視最終發生在使用者身上的一切事情。

這個特性不僅可以幫助客戶理解和重現任何問題,而且還可以在發生問題的第一時間通知客戶。

如果你想嘗試 SessionStack,這裡有一個免費的計劃。

[譯] JavaScript 是如何工作的:Web 推送通知的機制

資源


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章