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

這是專門研究 JavaScript 及其構建組件系列文章的第 9 章。在識別和描述核心元素的過程當中,咱們還分享了咱們在構建一個輕量級 JavaScript 應用程序 SessionStack 時使用的一些經驗規則,該應用程序須要健壯、高性能,能夠幫助用戶實時查看和重現它們的 Web 應用程序缺陷。javascript

若是你錯過了前幾章,你能夠在這裏找到它們:html

  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 領域,儘管開發人員呼喚了好久。java

概述

Web 推送通知容許用戶在 Web 應用程序中選擇接收更新信息,這些旨在從新吸引用戶羣注意的更新信息一般是對用戶來講有趣、重要、實時的內容。android

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

在這種狀況下,使用 Service Worker 的緣由是它們在後臺工做。這對推送通知很是有用,由於這意味着只有當用戶與通知自己進行交互時纔會執行它們的代碼。git

推送和通知

推送和通知是兩種不一樣的 API。github

  • 推送 —— 它在服務器端將消息推送給 Service Worker 時被調用
  • 通知 —— 這是 Service Worker 或 web 應用程序中向用戶顯示信息腳本的操做。

推送

實現推送有三個步驟: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() 調用將向用戶顯示如下提示:

一旦被受權、關閉或阻止,咱們將獲得字符串格式的結果:‘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,你須要將它發送到你的服務器。在那裏(在服務器上),你將這個訂閱存到數據庫中,從今之後若是你要向該用戶推送消息就使用它。

發送推送消息

當你想向用戶發送推送消息時,你首先須要一個推送服務。你要(經過 API 調用)告訴推送服務要發送哪些數據,誰來接收數據以及其餘關於怎麼發送數據的標準。一般,此 API 調用是在你服務器上完成的。

推送服務

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

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

推送服務處理全部繁重的任務,好比,若是瀏覽器處於脫機狀態,推送服務會在發送相應消息以前對消息進行排隊,等待瀏覽器的再次聯機。

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

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

爲了得到 URL 來進行消息推送請求,你須要檢查 PushSubscription 對象中存儲的 endpoint 值。

推送服務 API

推送服務 API 提供了一種將消息發送給用戶的方式。API 基於 Web 推送協議,它是一種定義瞭如何對推送服務進行 API 調用的 IETF 標準。

你使用推送消息發送的數據必須被加密。這樣能夠防止推送服務查看發送的數據。這很重要,由於瀏覽器是能夠決定使用哪一種推送服務的(它可能使用了一些不受信任且不夠安全的服務器)。

對於每一個推送消息,你還能夠提供下列說明:

  • TTL —— 定義消息會在隊列中等多久,超過這個時間消息就會被刪除不作推送。。
  • 優先級 —— 定義消息的優先級,由於推送服務只發送高優先級的消息,以此來保護用戶設備的電池壽命。
  • Topic —— 給推送消息一個主題,新消息會替換等待中的帶相同主題的消息,這樣一旦設備處於活動狀態,用戶將不會收到過期的消息。

瀏覽器中的推送事件

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

  • 設備上線。
  • 消息因爲 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,這裏有一個免費的計劃。

資源


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

相關文章
相關標籤/搜索