個人總結:
1.介紹了網頁消息推送通知機制
全文地址:https://github.com/Troland/how-javascript-works
這是 JavaScript 工作原理的第九章。
現在讓我們把注意力轉移到網頁推送通知:我們將會查看其構造,探索發送/接收通知背后的過程以及最后分享一下我們在 SessionStack 是如何計划利用這些功能來創建新的產品功能的。
推送通知這一功能在移動端已經非常普遍。不知為何,網頁端的推送通知是千呼萬喚始出來,即使大多數開發者強烈地要求實現這一功能。
概述
網頁推送通知允許用戶選擇定時從網絡應用獲取及時信息。它旨在為用戶重新獲取其感興趣,重要和及時的信息。
推送服務是基於服務工作線程的,服務工作線程(service web worker)在之前的文章中有詳細闡述過。
這個情況下,之所以采用服務工作線程是因為它會在后台運行,從而不會阻塞界面的渲染。對於推送通知來說,這是相當重要的,因為這意味着只有當用戶和推送通知本身進行交互操作才會執行推送通知的相關代碼。
消息推送和通知
消息推送和通知是兩個不同的接口。
消息推送
實現消息推送大概有以下三個步驟:
- 界面-添加客戶端邏輯來讓用戶訂閱推送服務。在網絡應用界面中書寫 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()
會向用戶彈出以下的提示框:
當獲得,關閉以及禁止權限的時候,就可以得到 granted
,default
或者 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.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
對象,你需要把它保存在推送服務器上。這樣就可以把該訂閱相關數據保存在數據庫之中然后從今以后,就可以根據數據庫中的存儲值來給指定的用戶發送消息。
消息推送
當需要發送消息到用戶的時候,首先需要有一個消息推送服務。你通知推送服務(通過接口調用)需要推送的數據,消息推送的目標用戶以及任意條件下如何發送消息。一般情況下,這些接口調用是由消息推送服務器來完成的。
消息推送服務
消息推送服務是用來接收消息推送請求,驗證請求以及推送消息到指定的用戶瀏覽器端。
請注意這里的消息推送服務並不是由你來控制的-它是第三方服務。服務器只是通過接口來和消息推送服務進行通訊。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 公鑰和私鑰對)。
- 檢查瀏覽器端兼容性,獲取授權,使用消息推送服務器生成的公鑰並生成訂閱對象,保存該訂閱對象到推送服務器上面。
- 消息推送服務(第三方服務)。
一張流程圖來表示吧: