原文地址:Subscribing a Userhtml
譯文地址:訂閱一個用戶git
譯者:劉鵬github
第一步是從用戶那裏獲取發送消息的權限,而後才能着手於 PushSubscription
。數據庫
實現這一步的 Javascript API 是至關直接,因此讓咱們來一步一步看一下這個邏輯流程。npm
首先,咱們須要檢查用戶當前的瀏覽器是否支持推送消息。能夠經過下面兩個簡單的方法來檢測。json
if (!('serviceWorker' in navigator)) {
// 此瀏覽器不支持 Service Worker,禁用或隱藏 UI
return;
}
if (!('PushManager' in window)) {
// 此瀏覽器不支持推送,禁用或隱藏 UI
return;
}
複製代碼
雖然越來越多的瀏覽器對 service worker 和推送消息進行了支持,但對這二者同時進行特徵檢測而且進行漸進加強老是一個好主意。api
經過特徵檢測,咱們已經知道 service worker 和推送二者都已經支持了。下一步就是去註冊咱們的 service worker。promise
當註冊 service worker 的時候,至關於告訴瀏覽器咱們的 service worker 文件在哪裏。這個文件依然是 JavaScript,可是瀏覽器會給它訪問系統 service worker API 的權限,包括推送。 更確切的說,瀏覽器是在 service worker 環境運行這個文件。瀏覽器
經過調用 navigator.serviceWorker.register()
便可註冊一個 service worker,同時將咱們文件的路徑做爲參數傳入。以下面所示:
function registerServiceWorker() {
return navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
console.log('Service worker successfully registered.');
return registration;
})
.catch(function(err) {
console.error('Unable to register service worker.', err);
});
}
複製代碼
上面的代碼告訴瀏覽器,咱們有一個 service worker 文件,以及存放它的地址。在這個示例中,service worker 文件地址是 /service-worker.js
。在調用完 register()
以後,後臺瀏覽器會進行下面幾個步驟:
register()
以後返回的 promise 對象將會調用 resolve 方法。若是有任何錯誤發生,promise 對象會調用 reject 方法。若是
register()
reject 了,請在 Chrome 的開發者工具當中再檢查一遍你的 JavaScript 代碼中的拼寫錯誤或邏輯錯誤。
若是 register()
確實 resolve 了, 它會返回一個 ServiceWorkerRegistration 的方法。咱們將使用它來訪問推送管理 API。
咱們註冊了 service worker,爲訂閱用戶作好了準備,下一步就是從用戶那裏獲取給他們發送消息的權限。
獲取權限的 API 相對簡單,可是不太好的是這個 API 最近由原來的回調方式變爲返回一個 Promise 對象。這個變更形成了咱們不能分辨當前瀏覽器究竟實現了哪個,因此必須同時實現並處理二者。
function askPermission() {
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('We weren\'t granted permission.');
}
});
}
複製代碼
在上面的代碼當中,最重要的代碼片斷就是調用 Notification.requestPermission()
。這個方法會顯示一個提示給用戶:
一旦這個權限被贊成 / 容許,關閉(也就是點擊彈層上的叉)或者被攔截,咱們將獲取結果字符串:'granted'、'default' 或者 'denied'。
在上面的示例代碼中,若是權限被許可了,調用 askPermission()
返回的 promise 對象會 resolve,不然的話咱們會拋出一個錯誤讓 promise 對象拒絕。
還有一個邊界狀況咱們必定要處理,那就是用戶是否點擊了 Block 的按鈕。若是這個發生了,咱們將不再可以跟用戶請求受權。他們必須手動地 unblock 咱們的應用,改變咱們應用的權限狀態,這個入口隱藏在瀏覽器的設置面板。 你須要仔細想一想以什麼方法以及在什麼時間向用戶詢問受權,由於若是他們點擊 block,再想讓他們更改這個決定並非那麼容易。
好消息是,只要用戶知道爲何須要這個權限,大多數用戶都很樂意受權給咱們。
後續,咱們將看看一些流行的站點是怎麼請求受權的。
一旦咱們註冊了 service worker 而且獲取了權限,咱們就能調用 registration.pushManager.subscribe()
訂閱一個用戶。
function subscribeUserToPush() {
return navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
return pushSubscription;
});
}
複製代碼
當調用 subscribe()
方法的時候,咱們傳入一個 options 的對象,此對象包含必傳、可選參數。
讓咱們來看一下咱們能傳入的全部參數。
當推送一開始被添加到瀏覽器的時候,關於開發者是否可以發送消息給用戶而且不顯示通知,這一點是不肯定的。這個通常被稱爲靜默推送,這是由於用戶不知道後臺正在發生什麼。
這是考慮到開發者可能會作一些讓人討厭的事情,好比說持續不斷地追蹤用戶的位置,而不讓用戶知道。
爲了不這個場景以及讓規範編寫者有時間來考慮如何更好地支持這個特性,他們添加了 userVisibleOnly 選項,而且和瀏覽器達成了一個象徵性的協議,給此選項傳入一個 true 值,這樣的話每次收到一個推送,Web 應用都會展現出一個通知 (也就是說沒有靜默推送)。
因此說當前,你必須傳入一個爲 true 的值。若是你沒有傳入一個 userVisibleOnly 的鍵值或者傳入的是 false 值,你會獲得以下的錯誤:
Chrome 當前僅支持可以產生讓用戶可見消息的推送 API 的訂閱。你能夠調用
pushManager.subscribe({useVisibleOnly: true})
。查看 goo.gl/yqv4Q4 獲取更多詳情。
當前看起來,在 Chrome 當中,徹底的靜默推送永遠不會實現。規範編寫者正在探索一個預算 API 的概念,它會基於用戶對 Web App 的使用而給開發者們必定量的靜默推送消息次數。
在以前的章節當中,咱們提到了 application server keys,推送服務使用它來鑑別訂閱用戶的服務應用,而且確保是一樣的應用發送消息給那個訂閱用戶。
Application server keys 是一對公私鑰的鍵值對,對於你的應用來講是獨一無二的。私鑰應該對你的應用保密,而公鑰能夠自由地分享。
傳入到 subscribe()
方法的 applicationServerKey 選項應該是公鑰。當訂閱一個用戶的時候,瀏覽器會將這個值傳遞給推送服務,這意味着推送服務能夠將你應用的公鑰和用戶的 PushSubscription
綁定起來。
下面的圖描述了這些步驟:
subscribe()
,傳入你的 application server key 中的公鑰。subscribe()
返回的 Promise 對象 PushSubscription 當中。當你後續想要發送一個推送消息,須要建立一個 Authorization 的 header 頭,這個 header 頭將包含使用應用服務器的私鑰簽名以後的信息。 當推送服務接收到一個要求發送推送消息的請求,它經過查詢關聯到該請求的 endpoint 值的公鑰,來驗證該請求中籤名過的 Authorization 的 header 頭。若是簽名是合法的,推送服務知道它必定來自於擁有匹配的私鑰的應用服務器。總的來講,它是用來防止其餘人僞造身份發送消息給應用用戶的一個安全措施。
從技術上來講,applicationSecretKey
是一個可選項。然而,在 Chrome 瀏覽器上最容易的實現方案是須要它的,其餘瀏覽器在之後也可能須要它。在 Firefox 中當前是可選項。
在 VAPID spec 中定義了 application server key 的規範。記住 application server key 和 VAPID keys 是同一個概念。
你能夠經過訪問 web-push-codelab.glitch.me 建立 application server keys 的公私鑰。或者也可使用 web-push command line 經過下面幾步來生成密鑰。
$ npm install -g web-push
$ web-push generate-vapid-keys
複製代碼
你只須要爲你的應用生成一次密鑰,而且確保你把私鑰保管好(是的,我剛纔提到過)。
在調用 subscribe()
時有一個反作用。就是你在調用它的時候,若是 Web 應用沒有得到彈出通知的許可,瀏覽器會爲你請求許可。 若是你的 UI 和這個流程是匹配的,這會頗有用。可是若是你須要更多的控制(我認爲絕大多數開發者都是這樣想的),請使用咱們以前用過的 Notification.requestPermission()
。
PushSubscription
咱們調用 subscribe()
,傳入一些選項,而後得到一個 promise 對象,這個對象 resolve 返回的就是 PushSubscription
。相應的代碼以下:
function subscribeUserToPush() {
return navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
return pushSubscription;
});
}
複製代碼
這個 PushSubscription
對象包含發送推送消息給目標用戶所須要的所有信息。若是使用 JSON.stringify()
來打印,你能夠看到以下所示:
{
"endpoint": "https://some.pushservice.com/something-unique",
"keys": {
"p256dh":
"BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
"auth":"FPssNDTKnInHVndSTdbKFw=="
}
}
複製代碼
endpoint
值就是推送服務的 URL。若是要觸發一個推送消息的話,能夠向這個 URL 發送一個 POST 請求。
keys
對象用於加密推送消息數據。
一旦有了一個推送訂閱,你想要把它發送到你的服務器。怎麼來作徹底由你,可是一個小提示就是使用 JSON.stringify()
來從訂閱對象當中獲取全部的必需數據。 固然,你也能夠手動拼湊成相同的結果:
const subscriptionObject = {
endpoint: pushSubscription.endpoint,
keys: {
p256dh: pushSubscription.getKeys('p256dh'),
auth: pushSubscription.getKeys('auth')
}
};
// 上面和下面的輸出是同樣的
const subscriptionObjectToo = JSON.stringify(pushSubscription);
複製代碼
在 Web 頁面當中,像下面同樣完成一個訂閱的發送:
function sendSubscriptionToBackEnd(subscription) {
return fetch('/api/save-subscription/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
複製代碼
Node 服務接收到這個請求以後,保存數據到數據庫當中供之後使用。
app.post('/api/save-subscription/', function (req, res) {
if (!isValidSaveRequest(req, res)) {
return;
}
return saveSubscriptionToDatabase(req.body)
.then(function(subscriptionId) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({ data: { success: true } }));
})
.catch(function(err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message: 'The subscription was received but we were unable to save it to our database.'
}
}));
});
});
複製代碼
咱們的服務器有了 PushSubscription
的詳細信息,就能夠在任何想要的時候給用戶發送一條消息了。
當前人們常常問的問題以下:
我能夠更換瀏覽器使用的推送服務嗎?
不行。推送服務是由瀏覽器選擇的。正如咱們看到的,當咱們調用 subscribe()
時,瀏覽器會產生一個發送給推送服務的網絡請求,來獲取組成 PushSubscription 的細節信息。
每一個瀏覽器都使用不一樣的推送服務,那它們會有不一樣的 API 嗎?
全部的推送服務擁有相同的 API。
這個相同的 API 被稱爲 Web 推送協議,它描述了你的應用須要怎樣的網絡請求來觸發一個推送消息。
若是用戶在桌面版進行了訂閱,那他們是否是同時在他們的手機版也訂閱了?
不幸的是,並無。一個用戶必須在他想要接收消息的全部瀏覽器都進行註冊推送。值得注意的是,用戶也須要在每個設備上都進行受權。