概述
Web Push是在PWA開(kāi)發(fā)中配合ServiceWorker實(shí)現(xiàn)消息推送的技術(shù)。大致流程分為以下幾步:
- 瀏覽器向推送服務(wù)器發(fā)起推送訂閱請(qǐng)求
- 瀏覽器拿到訂閱完成后推送服務(wù)器返回的訂閱信息,發(fā)送到應(yīng)用服務(wù)器進(jìn)行保存。
- 后面應(yīng)用服務(wù)器通過(guò)web push向推送服務(wù)發(fā)送消息,同時(shí)攜帶之前保存的訂閱信息,用于讓推送服務(wù)器知道要推送給誰(shuí)。
- 推送服務(wù)器接收到消息后,根據(jù)訂閱信息將消息推送給指定的瀏覽器,serviceWorker需要提前監(jiān)聽(tīng)push事件。
接下來(lái)看一下每一步的具體操作。
客戶(hù)端發(fā)起訂閱
首先前端在serviceWorker注冊(cè)完成之后可以獲取到一個(gè)serviceWorkerRegistration實(shí)例對(duì)象。使用這個(gè)對(duì)象的pushManager.subscribe方法進(jìn)行訂閱。
const registrtion = await navigator.serviceWorker.register('./sw.js')
const pushSubscription = await registrtion.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64ToUint8Array('BAvgL3epn4A5JN5tBhKsjpOUXnqYcbOGEitOw8KOAedS69NgcZ814NuP7IV5Ei5Wz2VBralB1WYkazdLBRMq3yw')
})
// 當(dāng)然也可以取消訂閱,先不關(guān)心
pushSubscription.unsubscribe().then(function () {
console.log('取消訂閱成功!')
})
userVisibleOnly參數(shù)為了保證推送對(duì)用戶(hù)可見(jiàn),設(shè)置為true即可;applicationServerKey服務(wù)器密鑰的生成需要借助一個(gè)npm包web-push來(lái)生成,可以通過(guò)代碼生成:
const webpush = require('web-push')
const vapidKeys = webpush.generateVAPIDKeys()
也可以全局安裝web-push,通過(guò)命令生成:
web-push generate-vapid-keys --json
以上可以生成一個(gè)這樣的一對(duì)公鑰和私鑰(不加--json也可以,生成的格式不用而已):
{
"publicKey":"BAvgL3epn4A5JN5tBhKsjpOUXnqYcbOGEitOw8KOAedS69NgcZ814NuP7IV5Ei5Wz2VBralB1WYkazdLBRMq3yw",
"privateKey":"SMIFmRPZHtLhIfxEqkbzanZg7NH1FlrFF8cf_K3BeWQ"
}
以上在應(yīng)用服務(wù)器生成,公鑰給到客戶(hù)端(就是訂閱是需要傳遞的),同時(shí)因?yàn)楣€是base64編碼的字符串,需要將其轉(zhuǎn)成Uint8Array格式才能作subscribe為參數(shù)傳入。私鑰是需要保存在應(yīng)用服務(wù)器的。
function base64ToUint8Array (base64String) {
let padding = '='.repeat((4 - base64String.length % 4) % 4)
let base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
let rawData = atob(base64)
let outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; i++) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
推送服務(wù)器返回的訂閱信息pushSubscription,是需要傳遞給應(yīng)用服務(wù)器的,格式如下:
{
"endpoint": "https://fcm.googleapis.com/xxxxx",
"keys": {
"p256dh": "xxxxxx",
"auth": "xxxxxx"
}
}
應(yīng)用服務(wù)器保存訂閱信息
拿到訂閱信息之后需要保存到我們自己的應(yīng)用服務(wù)器,可以通過(guò)pushSubscription.getKey('p256dh')和pushSubscription.getKey('auth')獲取密鑰和校驗(yàn)碼信息,由于通過(guò)getKey獲取到的是ArrayBuffer類(lèi)型,所以需要轉(zhuǎn)成base64字符串便于傳輸。應(yīng)用服務(wù)器可以提供一個(gè)接口,比如/api/saveSubscription,然后把數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)中。
fetch('/api/saveSubscription', {
method: 'post',
body: JSON.stringify({
endpoint: pushSubscription.endpoint,
keys: {
p256dh: uint8ArrayToBase64(pushSubscription.getKey('p256dh')),
auth: uint8ArrayToBase64(pushSubscription.getKey('auth')),
}
})
})
function uint8ArrayToBase64 (arr) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(arr)))
}
應(yīng)用服務(wù)器推送消息
我們的Node應(yīng)用服務(wù)端需要使用web-push庫(kù)實(shí)現(xiàn)向推送服務(wù)器發(fā)送消息。首先通過(guò)setVapidDetails進(jìn)行配置。然后需要配置在云服務(wù)申請(qǐng)到的GCM API Key,最后通過(guò)sendNotification發(fā)送,第一個(gè)參數(shù)是之前保存的pushSubscription對(duì)象,第二參數(shù)是要發(fā)送給瀏覽器前端的數(shù)據(jù)信息。
const webpush = require('web-push')
const vaipdKeys = {
publicKey: '', // 公鑰
privateKey: '' //私鑰
}
webpush.setVapidDetails(
'mailto:郵箱地址.com', // 注意前綴mailto:不能丟
vaipdKeys.publicKey,
vaipdKeys.privateKey
)
webpush.setGCMAPIKey('<Your GCM API Key Here>')
webpush.sendNotification(pushSubscription, JSON.stringfy({}))
瀏覽器監(jiān)聽(tīng)推送
在serviceWorker文件sw.js中,在注冊(cè)完成之后可以進(jìn)行監(jiān)聽(tīng)。這樣就可以收到推送消息了。
self.addEventListener('push', e => {
if(e.data) {
// 推送的消息在data屬性中,通過(guò)text()可以解析成字符串
console.log(e.data.text())
// 也可以通過(guò)json()解析成json等
}
})
注意:如果不翻一下,推送服務(wù)器在國(guó)內(nèi)是用不了的哦。
補(bǔ)充
Notification
配合使用的 Notification API,判斷是否支持并允許通知:
function requestNotificationPermission () {
if (!window.Notification) {
return Promise.reject('瀏覽器不支持桌面通知')
}
return Notification.requestPermission().then(function (permission) {
if (permission === 'granted') {
return Promise.resolve()
}
return Promise.reject('用戶(hù)已禁止通知權(quán)限')
})
}
整體流程圖
