使用網頁推播時,其中一個痛點是觸發推播訊息非常「麻煩」。如要觸發推送訊息,應用程式必須依照 網路推送通訊協定,向推送服務提出 POST 要求。如要在所有瀏覽器使用推送功能,您必須使用 VAPID (又稱為應用程式伺服器金鑰),基本上必須設定標頭,並利用值證明應用程式可向使用者傳送訊息。如要透過推播訊息傳送資料,資料必須加密,並加入特定標頭,讓瀏覽器能夠正確解密訊息。
觸發推播的主要問題是,如果發生問題,就很難診斷問題。隨著時間的推移和更多瀏覽器的支援,這個問題正在改善,但仍不容易解決。因此,強烈建議您使用程式庫來處理推播訊息的加密、格式設定和觸發功能。
如果您真的想瞭解程式庫的運作方式,我們會在下一節說明。我們現在將討論如何管理訂閱項目,以及使用現有的網路推送程式庫發出推送要求。
在本節中,我們將使用 web-push Node 程式庫。其他語言會有些許差異,但不會太大。我們會採用 Node,因為它是 JavaScript,應該是最適合讀者的選擇。
我們將逐步完成以下步驟:
- 將訂閱項目傳送至後端並儲存。
- 擷取已儲存的訂閱項目,並觸發推送訊息。
儲存訂閱項目
從資料庫儲存及查詢 PushSubscription
的方式會因您選擇的伺服器端語言和資料庫而異,但查看示例可能會有所幫助。
在示範網頁中,我們會透過簡單的 POST 要求,將 PushSubscription
傳送至後端:
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.');
}
});
}
示範中的 Express 伺服器有一個 /api/save-subscription/
端點相符的要求事件監聽器:
app.post('/api/save-subscription/', function (req, res) {
在這個路徑中,我們會驗證訂閱,只是確保要求合法且未充飽電:
const isValidSaveRequest = (req, res) => {
// Check the request body has at least an endpoint.
if (!req.body || !req.body.endpoint) {
// Not a valid subscription.
res.status(400);
res.setHeader('Content-Type', 'application/json');
res.send(
JSON.stringify({
error: {
id: 'no-endpoint',
message: 'Subscription must have an endpoint.',
},
}),
);
return false;
}
return true;
};
如果訂閱項目有效,就必須儲存訂閱項目,並傳回適當的 JSON 回應:
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.',
},
}),
);
});
本示範使用 nedb 儲存訂閱項目,這是簡單的檔案型資料庫,但您可以使用自己選擇的任何資料庫。我們只使用這項功能,因為它不需要任何設定。如要用於實際工作環境,建議您使用更可靠的工具。(我傾向繼續使用良好的舊版 MySQL)。
function saveSubscriptionToDatabase(subscription) {
return new Promise(function (resolve, reject) {
db.insert(subscription, function (err, newDoc) {
if (err) {
reject(err);
return;
}
resolve(newDoc._id);
});
});
}
傳送推送訊息
在傳送推播訊息時,我們最終需要一些事件來觸發傳送訊息給使用者的程序。常見做法是建立管理頁面,讓您設定及觸發推播訊息。不過,您可以建立本機執行程式,或使用任何其他方法存取 PushSubscription
清單,並執行程式碼來觸發推送訊息。
我們的示範內容含有「類似管理員」頁面,可讓您觸發推播。由於這是示範,因此是公開網頁。
我將逐步說明如何讓這個示範功能運作。我們將逐步引導完成這些步驟,讓所有人都能跟上腳步,包括新手節點的使用者。
在討論使用者訂閱時,我們已將 applicationServerKey
新增至 subscribe()
選項。位於後端,需要使用這個私密金鑰
在示範中,這些值會以以下方式新增至 Node 應用程式 (我知道程式碼很無聊,但只是想讓您知道沒有任何神奇之處):
const vapidKeys = {
publicKey:
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};
接下來,我們需要為節點伺服器安裝 web-push
模組:
npm install web-push --save
接著,在節點指令碼中,我們需要使用類似下方的 web-push
模組:
const webpush = require('web-push');
我們現在可以開始使用 web-push
模組。首先,我們需要將應用程式伺服器金鑰告知 web-push
模組。(請記住,這些金鑰也稱為 VAPID 金鑰,因為這就是規格名稱)。
const vapidKeys = {
publicKey:
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};
webpush.setVapidDetails(
'mailto:web-push-book@gauntface.com',
vapidKeys.publicKey,
vapidKeys.privateKey,
);
請注意,我們也加入了「mailto:」字串。這個字串必須是網址或 mailto 電子郵件地址。這項資訊實際上會傳送至網頁推播服務,做為觸發推播的要求。這麼做的原因是,如果網路推送服務需要與傳送者聯絡,他們有一些資訊可以讓他們開啟。
這樣,web-push
模組已可供使用,下一步就是觸發推送訊息。
此示範內容會使用假示管理控制台來觸發推送訊息。
按一下「觸發推送訊息」按鈕,系統就會向 /api/trigger-push-msg/
發出 POST 要求,這是後端傳送推送訊息的信號,因此我們會在 Express 中為這個端點建立路由:
app.post('/api/trigger-push-msg/', function (req, res) {
收到這項要求後,我們會從資料庫擷取訂閱項目,並針對每個訂閱項目觸發推播訊息。
return getSubscriptionsFromDatabase().then(function (subscriptions) {
let promiseChain = Promise.resolve();
for (let i = 0; i < subscriptions.length; i++) {
const subscription = subscriptions[i];
promiseChain = promiseChain.then(() => {
return triggerPushMsg(subscription, dataToSend);
});
}
return promiseChain;
});
接著,函式 triggerPushMsg()
就能使用 Web-Push 程式庫,將訊息傳送至提供的訂閱項目。
const triggerPushMsg = function (subscription, dataToSend) {
return webpush.sendNotification(subscription, dataToSend).catch((err) => {
if (err.statusCode === 404 || err.statusCode === 410) {
console.log('Subscription has expired or is no longer valid: ', err);
return deleteSubscriptionFromDatabase(subscription._id);
} else {
throw err;
}
});
};
對 webpush.sendNotification()
的呼叫會傳回承諾。如果訊息已成功傳送,承諾就會解析,我們不需要採取任何行動。如果承諾遭到拒絕,您就需要檢查錯誤,因為系統會告知您 PushSubscription
是否仍有效。
如要判斷推送服務發出的錯誤類型,建議您查看狀態碼。不同推播服務的錯誤訊息各有不同,有些比其他訊息更實用。
在本範例中,程式碼會檢查狀態碼 404
和 410
,這兩個狀態碼為「找不到」和「不存在」的 HTTP 狀態碼。如果我們收到其中一種訊息,表示訂閱項目已過期或不再有效。在這些情況下,我們需要從資料庫中移除訂閱項目。
如果發生其他錯誤,我們只會執行 throw err
,這將允許 triggerPushMsg()
傳回的承諾。
在下一節中,我們會進一步探討其他狀態碼,並詳細說明網路推送通訊協定。
迴圈處理訂閱項目後,我們需要傳回 JSON 回應。
.then(() => {
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-send-messages',
message: `We were unable to send messages to all subscriptions : ` +
`'${err.message}'`
}
}));
});
我們已介紹主要的導入步驟:
- 建立 API,將網頁上的訂閱項目傳送至後端,以便將訂閱項目儲存到資料庫。
- 建立 API 以觸發推播訊息傳送作業 (在本例中,這是從假裝的管理員面板呼叫的 API)。
- 從後端擷取所有訂閱項目,並使用其中一個 web-push 程式庫,向每個訂閱項目傳送訊息。
無論您的後端為何 (Node、PHP、Python、...),實作推送作業的步驟都相同。
接下來,這些網路推播程式庫的作用究竟是什麼?
後續步驟
- 網路推播通知總覽
- 推播通知的運作方式
- 訂閱使用者
- 權限使用者體驗
- 使用網頁推送程式庫傳送訊息
- Web Push 通訊協定
- 處理推送事件
- 顯示通知
- 通知行為
- 常見的通知模式
- 推播通知常見問題
- 常見問題和回報錯誤