#Push API指南
推送API允許網絡應用程序接收由服務器推送的消息,即使該Web應用程序在瀏覽器中沒有打開或者不在設備上運行。
推送API允許網絡應用程序接收由服務器推送的消息,即使該Web應用程序在瀏覽器中沒有打開或者不在設備上運行。
使用推送API,您可以向用戶發送消息,從服務器推送到客戶端,即使用戶未在瀏覽站點。
這使您能夠提供通知和內容更新,從而使受眾更加參與。
這是巨大的,因為與本機應用程序相比,移動網絡的一個缺失的基礎是能夠接收通知的能力,以及離線支持。
是否受到支持?
推送API是瀏覽器API的最新添加,目前Chrome(桌面和移動平台),Firefox和Opera支持自2016年以來,Edge自17版(2018年初)開始支持。查看有關當前瀏覽器支持狀況的更多信息,請訪問https://caniuse.com/#feat=push-api
IE不支持它,Safari有自己的實現方式。
由於Chrome和Firefox支持它,大約60%的桌面瀏覽器用戶可以使用它,所以使用它是相對較安全的。
它是如何工作的
總覽
當用戶訪問您的Web應用程序時,您可以觸發一個面板要求許可權來發送更新。安裝了服務器工作者(Service Worker),並在後台運行,監聽推送事件(Push Event)。
推送和通知是一個獨立的概念和API,有時由於在iOS中使用“推送通知”詞彙而混淆。基本上,當使用推送API接收到推送事件時,調用通知API。
如果給予權限,您的服務器將通知發送到客戶端,服務工作者(Service Worker)接收到推送事件(Push Event)後將對此事件做出反應,觸發通知。
獲取用戶權限
使用推送API的第一步是獲取用戶允許從您接收數據。
許多網站在第一次頁面加載時執行此面板時錯誤,用戶還沒有確定您的內容是否好,他們將拒絕授權。請明智地運用。
有6個步驟:
- 檢查是否支持服務器工作者
- 檢查是否支持推送API
- 註冊服務器工作者
- 向用戶請求權限
- 訂閱用戶並獲取PushSubscription對象
- 將PushSubscription對象發送到您的服務器
檢查是否支持服務器工作者
1 | if (!('serviceWorker' in navigator)) { |
檢查是否支持推送API
1 | if (!('PushManager' in window)) { |
註冊服務器工作者
此代碼註冊位於域根目錄的worker.js
文件中的服務器工作者。
1 | window.addEventListener('load', () => { |
要了解有關服務器工作者的詳細信息,請查看服務器工作者指南。
向用戶請求權限
現在,服務器工作者已註冊,您可以請求權限。
這個API隨著時間的推移進行了修改,從接受回調函數作為參數,變為返回一個Promise(承諾),這打破了向前和向後的兼容性,而我們需要兩者都進行,因為我們不知道用戶的瀏覽器實現了哪種方法。
代碼如下,調用Notification.requestPermission()
。
1 | const askPermission = () => { |
permissionResult
值是一個字符串,可以有以下值:
granted
default
denied
此代碼導致瀏覽器顯示權限對話框:
如果用戶點擊了“阻止”,則您將無法再次請求用戶的權限,除非他們在瀏覽器的高級設置面板中手動解除對站點的阻止(很不可能發生)。
訂閱用戶並獲取PushSubscription對象
如果用戶給予我們權限,我們可以通過調用registration.pushManager.subscribe()
訂閱用戶。
1 | const APP_SERVER_KEY = 'XXX' |
APP_SERVER_KEY
是一個字符串,稱為Application Server Key或者VAPID key,它識別應用程序的公共金鑰,作為公共/私有金鑰對的一部分。
出於安全原因的驗證,在確保只有您(而不是其他人)可以向用戶發送推送消息時使用。
將PushSubscription對象發送到您的服務器
在之前的代碼片段中,我們獲取了pushSubscription
對象,其中包含了我們發送推送消息所需的所有信息。我們需要將此信息發送到我們的服務器,以便稍後發送通知。
首先我們創建對象的JSON表示:
1 | const subscription = JSON.stringify(pushSubscription) |
然後我們可以使用Fetch API將其發送給我們的服務器:
1 | const sendToServer = (subscription) => { |
服務器端如何工作
到目前為止,我們只談到了客戶端的部分:獲取用戶在未來接收通知的權限。
那麼服務器應該做什麼,以及如何與客戶端交互呢?
此服務器端示例使用Express.js (http://expressjs.com/)作為基本的HTTP框架,但您可以使用任何語言或框架編寫服務器端的Push API處理程序
註冊新的用戶端訂閱
當客戶端發送新的訂閱時,請記住我們在/api/subscription
HTTP POST端點上使用了PushSubscription對象的詳細信息,以JSON格式在正文中發送。
我們初始化Express.js:
1 | const express = require('express') |
此實用函數確保請求有效,具有正文和端點屬性,否則它將返回一個錯誤給客戶端:
1 | const isValidSaveRequest = (req, res) => { |
下一個實用函數將訂閱保存到數據庫中,返回一個承諾,當插入完成時解析(或失敗)。insertToDatabase
函數是一個占位符,我們不會在此詳細討論那些細節:
1 | const saveSubscriptionToDatabase = (subscription) => { |
我們在POST請求處理程序中使用這些函數。我們檢查請求是否有效,然後保存請求,然後將data.success: true
響應返回給客戶端,或者返回錯誤:
1 | app.post('/api/subscription', (req, res) => { |
發送推送消息
現在服務器在其列表中註冊了客戶端後,我們可以向其發送推送消息。讓我們通過創建一個示例代碼片段來查看它的工作原理,該片段會獲取所有訂閱,並馬上將Push消息推送給它們。
我們使用一個庫,因為Web Push協議非常複雜,庫使我們可以將很多低級代碼抽象出來,以確保我們能夠安全地工作並正確地處理任何邊緣情況。
此示例使用
web-push
Node.js庫(https://github.com/web-push-libs/web-push)來處理推送消息
首先我們初始化web-push
庫,並生成私鑰和公鑰的元組,並將其設置為VAPID的詳細信息:
1 | const webpush = require('web-push') |
然後我們設置一個triggerPush()
方法,負責向客戶端發送推送事件。它只需要調用webpush.sendNotification()
並捕獲任何錯誤。如果返回的錯誤HTTP狀態碼是410,表示已消失,我們將從數據庫中刪除該訂閱者。
1 | const triggerPush = (subscription, dataToSend) => { |
我們不實現從數據庫中獲取訂閱的功能,但我們將其作為存根:
1 | const getSubscriptionsFromDatabase = () => { |
代碼的核心是POST請求的回調函數,該請求發送到/api/push
端點:
1 | app.post('/api/push', (req, res) => { |
上面的代碼的作用是:它從數據庫中獲取所有訂閱,然後對它們進行迭代,並調用我們之前解釋的triggerPush()
函數。
完成訂閱後,我們返回一個成功的JSON響應,除非發生錯誤,我們返回一個500錯誤。
在現實世界中…
您不太可能設置自己的推送服務器,除非您有一個非常特殊的用例,或者只是想學習技術或者喜歡DIY。相反,您通常希望使用類似OneSignal(https://onesignal.com)這樣的平台,它能夠透明地處理各種平台的推送事件,包括Safari和iOS,而且還是免費的。
接收推送事件
當從服務器發送推送事件時,客戶端如何接收它呢?
它是一個正常的[JavaScript(/javascript/)]事件監聽器,在push
事件上運行在服務器工作者(Service Worker)內:
1 | self.addEventListener('push', (event) => { |
event.data
中包含PushMessageData
對象,它公開了方法,以您希望的格式檢索由服務器發送的推送數據:
- **arrayBuffer()**:作為
ArrayBuffer
對象 - **blob()**:作為Blob對象
- **json()**:解析為JSON
- **text()**:純文本
通常我們會使用event.data.json()
。
顯示通知
這裡我們稍微涉及到了通知API,但出於一個好的原因,因為Push API的一個主要用例是顯示通知。
在服務器工作者(Service Worker)中的push
事件監聽器內部,我們需要將通知顯示給用戶,並告訴事件在瀏覽器顯示通知之前等待。我們將事件的生命週期延長到瀏覽器顯示通知之前(直到Promise被解析),否則服務器工作者可能在處理過程中被停止。
1 | self.addEventListener('push', (event) => { |
有關通知的更多信息,請參見通知API指南。