使用網頁推播時,其中一個痛點是觸發推播訊息非常「麻煩」。如要觸發推送訊息,應用程式必須依照 網路推送通訊協定向推送服務提出 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
清單,並執行程式碼來觸發推送訊息。
我們的示範內容含有「類似管理員」頁面,可讓您觸發推播。由於這是示範,因此是公開網頁。
我將逐步說明如何讓這個示範功能運作。這些是初階步驟,因此所有人都能跟著操作,包括任何 Node 新手。
在討論使用者訂閱時,我們已將 applicationServerKey
新增至 subscribe()
選項。我們需要這個私密金鑰來處理後端作業。
在示範中,這些值會以以下方式新增至 Node 應用程式 (我知道程式碼很無聊,但只是想讓您知道沒有任何神奇之處):
const vapidKeys = {
publicKey:
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};
接下來,我們需要為 Node 伺服器安裝 web-push
模組:
npm install web-push --save
接著,在 Node 指令碼中要求 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 通訊協定
- 處理推播事件
- 顯示通知
- 通知行為
- 常見的通知模式
- 推播通知常見問題
- 常見問題和回報錯誤