原文請查閱這裏,略有刪減,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。javascript
本系列持續更新中,Github 地址請查閱這裏。html
這是 JavaScript 工做原理的第九章。java
如今讓咱們把注意力轉移到網頁推送通知:咱們將會查看其構造,探索發送/接收通知背後的過程以及最後分享一下咱們在 SessionStack 是如何計劃利用這些功能來建立新的產品功能的。node
推送通知這一功能在移動端已經很是廣泛。不知爲什麼,網頁端的推送通知是千呼萬喚始出來,即便大多數開發者強烈地要求實現這一功能。git
網頁推送通知容許用戶選擇定時從網絡應用獲取及時信息。它旨在爲用戶從新獲取其感興趣,重要和及時的信息。github
推送服務是基於服務工做線程的,服務工做線程在以前的文章中有詳細闡述過。web
這個狀況下,之因此採用服務工做線程是由於它會在後臺運行,從而不會阻塞界面的渲染。對於推送通知來講,這是至關重要的,由於這意味着只有當用戶和推送通知自己進行交互操做纔會執行推送通知的相關代碼。數據庫
消息推送和通知是兩個不一樣的接口。promise
實現消息推送大概有如下三個步驟:瀏覽器
如今,讓咱們詳細闡述整個過程。
首先,須要檢測當前瀏覽器是否支持消息推送服務。能夠採用如下兩種簡單的檢查:
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
的結果字符串。
須要注意的是當用戶點擊 禁止
按鈕,網絡應用將不會再次詢問用戶受權直到用戶手動開啓更改受權狀態。該選項隱藏於設置面板中。
點擊地址欄最左邊的信息按鈕便可彈出受權的彈窗。
一旦服務工做線程註冊成功且得到受權,就能夠在註冊服務器線程的時候經過調用 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 對象參數,其中包含有必須或者可選的參數:
true
不然會出錯(這是歷史緣由形成的)。DOMString
字符串或者 ArrayBuffer
,消息推送服務器用來驗證應用服務器。消息推送服務器須要生成一對應用服務器密鑰對-即 VAPID 密鑰對,這對於消息推送服務器來講是惟一的。它們是由一對公鑰和私鑰所組成的。私鑰祕密存儲於推送服務器端,公鑰用來和客戶端進行交換通信用的。這些密鑰讓推送服務辨別訂閱用戶的應用服務器以及確保觸發推送消息到指定用戶的是同一個應用服務器。
你只須要一次性生成應用程序私有/公有密鑰對。能夠訪問 web-push-codelab.glitch.me/ 生成密鑰對。
當訂閱用戶的時候,瀏覽器向推送服務傳入 applicationServerKey
(公鑰),意即推送服務把應用服務器公鑰和用戶的 PushSubscription
綁定在一塊兒。
過程以下:
subscribe
,傳入服務器公鑰。subscribe()
promise 所返回的 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 標準協議,裏面定義瞭如何調用消息推送服務。
推送的消息必須得加密。這樣能夠防止消息推送服務窺視到發送的數據。這是相當重要的由於客戶端能夠決定使用哪一個消息推送服務(可能會使用一些不被信任和不安全的消息推送服務)。
消息推送參數:
每當發送消息到如上的推送服務,消息會處於待發送狀態直到發生如下幾種狀況:
當消息推送服務傳輸消息到瀏覽器,瀏覽器會接收到,解密,而後分派給服務工做線程 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 可使用這裏的庫來構建推送服務器。
作一個網頁消息推送所須要的條件即:
一張流程圖來表示吧:
今日頭條招人啦!發送簡歷到 likun.liyuk@bytedance.com ,便可走快速內推通道,長期有效!國際化PGC部門的JD以下:c.xiumi.us/board/v5/2H…,也可內推其餘部門!
本系列持續更新中,Github 地址請查閱這裏。