在本程式碼研究室中,您會建構推播通知伺服器。伺服器會管理推送訂閱項目清單,並傳送通知給這些群組。
用戶端程式碼已完成。在這個程式碼研究室中,您將處理伺服器端的功能。
重混範例應用程式,並在新分頁中查看
系統會自動封鎖內嵌的 Glitch 應用程式通知,因此您無法在這個頁面預覽應用程式。而是可以採取以下做法:
- 按一下「Remix to Edit」即可編輯專案。
- 如要預覽網站,請按下「查看應用程式」。然後按下
全螢幕
。
執行中的應用程式會在新的 Chrome 分頁中開啟。在嵌入式 Glitch 中,按一下「View Source」,再次顯示程式碼。
在您學習本程式碼研究室的過程中,請變更本頁面內嵌 Glitch 中的程式碼。為執行中的應用程式重新整理新分頁,就會看到變更。
瞭解入門應用程式及其程式碼
我們先查看應用程式的用戶端 UI,
在新的 Chrome 分頁中:
按下 Control+Shift+J 鍵 (或在 Mac 上為 Command+Option+J 鍵) 開啟開發人員工具。 再按一下「Console」(控制台) 分頁標籤即可。
請嘗試點選使用者介面中的按鈕 (請查看 Chrome Developer Console 的輸出內容)。
註冊 Service Worker 會針對您的 Glitch 專案網址範圍註冊 Service Worker。取消註冊 Service Worker 會移除 Service Worker。如已附加推送訂閱項目,系統就會停用推送訂閱項目。
訂閱推送會建立推送訂閱項目。只有在 Service Worker 註冊完成,且用戶端程式碼中有
VAPID_PUBLIC_KEY
常數時 (稍後會進一步說明),因此目前無法點選該常數。只要您有有效的推送訂閱項目,「通知目前的訂閱」要求伺服器就會傳送通知至其端點。
「通知所有訂閱項目」會指示伺服器傳送通知給資料庫中的所有訂閱端點。
請注意,其中某些端點可能處於停用狀態。在伺服器傳送通知給訂閱時,訂閱項目可能就消失了。
讓我們來看看伺服器端發生的問題。如要查看伺服器程式碼傳來的訊息,請在 Glitch 介面中查看 Node.js 記錄檔。
在 Glitch 應用程式中,按一下「Tools」->「Tools」記錄。
您可能會看到類似
Listening on port 3000
的訊息。如果您嘗試在實際應用程式的使用者介面中點選「通知目前的訂閱」或「通知所有訂閱項目」,還會看到下列訊息:
TODO: Implement sendNotifications() Endpoints to send to: []
現在來看看一些程式碼。
public/index.js
包含完成的用戶端程式碼。它會執行功能偵測、註冊和取消註冊 Service Worker,並控制使用者對推播通知的訂閱。同時將新增與已刪除的訂閱項目的相關資訊傳送至伺服器。由於您只需要處理伺服器功能,因此不會編輯這個檔案 (除填入
VAPID_PUBLIC_KEY
常數之外)。public/service-worker.js
是簡單的 Service Worker,可擷取推送事件並顯示通知。/views/index.html
包含應用程式 UI。.env
包含 Glitch 啟動時載入應用程式伺服器的環境變數。您將在.env
中填入用於傳送通知的驗證詳細資料。server.js
是您在本程式碼研究室中,會用到的大部分工作檔案。範例程式碼會建立簡易的 Express 網路伺服器。您有兩個 TODO 項目,並以
TODO:
在程式碼註解中標示。必要操作:在本程式碼研究室中,您將逐一檢查這些 TODO 項目。
產生並載入 VAPID 詳細資料
您的第一個 TODO 項目是產生 VAPID 詳細資料、將其新增至 Node.js 環境變數,然後以新的值更新用戶端和伺服器程式碼。
背景
使用者訂閱通知後,必須信任應用程式及其伺服器的身分。此外,使用者收到通知時,別忘了確認這是來自當初設定訂閱項目的應用程式。同時也必須相信其他人不能閱讀通知內容。
提高推播通知的安全性與私密性,稱為網路推送的自願應用程式伺服器識別 (VAPID)。VAPID 會使用公開金鑰密碼編譯技術來驗證應用程式、伺服器和訂閱端點的身分,並加密通知內容。
在這個應用程式中,您將使用網頁推送 npm 套件產生 VAPID 金鑰,並加密並傳送通知。
導入作業
在這個步驟中,請為應用程式產生一對 VAPID 金鑰,並將金鑰新增至環境變數。在伺服器中載入環境變數,然後在用戶端程式碼中將公開金鑰新增為常數。
使用
web-push
程式庫的generateVAPIDKeys
函式建立一組 VAPID 金鑰。在 server.js 中,移除以下這行程式碼周圍的註解:
server.js
// Generate VAPID keys (only do this once). /* * const vapidKeys = webpush.generateVAPIDKeys(); * console.log(vapidKeys); */ const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys);
Glitch 重新啟動您的應用程式後,會將產生的金鑰輸出至 Glitch 介面的 Node.js 記錄 (「而非」Chrome 控制台)。如要查看 VAPID 金鑰,請選取 工具 ->Glitch 介面中的記錄檔。
請務必從同一個金鑰組複製公開與私密金鑰!
每當您編輯程式碼,Glitch 就會重新啟動應用程式,因此產生的第一組金鑰可能會捲動未顯示,詳情請參閱更多輸出內容。
在 .env 中複製及貼上 VAPID 金鑰。請用雙引號 (
"..."
) 括住鍵。在
VAPID_SUBJECT
中,您可以輸入"mailto:test@test.test"
。.env
# process.env.SECRET VAPID_PUBLIC_KEY= VAPID_PRIVATE_KEY= VAPID_SUBJECT= VAPID_PUBLIC_KEY="BN3tWzHp3L3rBh03lGLlLlsq..." VAPID_PRIVATE_KEY="I_lM7JMIXRhOk6HN..." VAPID_SUBJECT="mailto:test@test.test"
在 server.js 中,請再次將這兩行程式碼註解排除,因為您只需要產生一次 VAPID 金鑰。
server.js
// Generate VAPID keys (only do this once). /* const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys); */ const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys);
在 server.js 中,從環境變數載入 VAPID 詳細資料。
server.js
const vapidDetails = { // TODO: Load VAPID details from environment variables. publicKey: process.env.VAPID_PUBLIC_KEY, privateKey: process.env.VAPID_PRIVATE_KEY, subject: process.env.VAPID_SUBJECT }
一併複製 public 金鑰並貼到用戶端程式碼中。
在 public/index.js 中,為複製到 .env 檔案的
VAPID_PUBLIC_KEY
輸入相同的值:public/index.js
// Copy from .env const VAPID_PUBLIC_KEY = ''; const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...'; ````
實作傳送通知的功能
背景
在這個應用程式中,您將使用網頁推送 npm 套件傳送通知。
呼叫 webpush.sendNotification()
時,這個套件會自動將通知加密,因此您不必擔心。
web-push 接受多個通知選項。例如,您可以附加標頭至郵件,並指定內容編碼。
在本程式碼研究室中,您只需要使用以下兩行程式碼來定義:
let options = {
TTL: 10000; // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails; // VAPID keys from .env
};
TTL
(存留時間) 選項可設定通知的到期時間。透過這種做法,伺服器可避免在使用者不再相關時傳送通知。
vapidDetails
選項包含您從環境變數載入的 VAPID 金鑰。
導入作業
在 server.js 中,按照以下方式修改 sendNotifications
函式:
server.js
function sendNotifications(database, endpoints) {
// TODO: Implement functionality to send notifications.
console.log('TODO: Implement sendNotifications()');
console.log('Endpoints to send to: ', endpoints);
let notification = JSON.stringify(createNotification());
let options = {
TTL: 10000, // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails // VAPID keys from .env
};
endpoints.map(endpoint => {
let subscription = database[endpoint];
webpush.sendNotification(subscription, notification, options);
});
}
由於 webpush.sendNotification()
會傳回承諾值,因此您可以輕鬆新增錯誤處理機制。
在 server.js 中,再次修改 sendNotifications
函式:
server.js
function sendNotifications(database, endpoints) {
let notification = JSON.stringify(createNotification());
let options = {
TTL: 10000; // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails; // VAPID keys from .env
};
endpoints.map(endpoint => {
let subscription = database[endpoint];
webpush.sendNotification(subscription, notification, options);
let id = endpoint.substr((endpoint.length - 8), endpoint.length);
webpush.sendNotification(subscription, notification, options)
.then(result => {
console.log(`Endpoint ID: ${id}`);
console.log(`Result: ${result.statusCode} `);
})
.catch(error => {
console.log(`Endpoint ID: ${id}`);
console.log(`Error: ${error.body} `);
});
});
}
處理新訂閱
背景
使用者訂閱推播通知後會發生以下情況:
使用者按一下 [訂閱推送]。
用戶端會使用
VAPID_PUBLIC_KEY
常數 (伺服器的公開 VAPID 金鑰) 產生不重複的伺服器專用subscription
物件。subscription
物件如下所示:{ "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...", "expirationTime": null, "keys": { "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...", "auth": "0IyyvUGNJ9RxJc83poo3bA" } }
用戶端會將
POST
要求傳送至/add-subscription
網址,包括內文中以字串化 JSON 顯示的訂閱項目。伺服器從 POST 要求主體擷取字串化的
subscription
,剖析回 JSON,然後將該字串新增至訂閱資料庫。資料庫會使用自有端點儲存訂閱項目做為索引鍵:
{
"https://fcm...1234": {
endpoint: "https://fcm...1234",
expirationTime: ...,
keys: { ... }
},
"https://fcm...abcd": {
endpoint: "https://fcm...abcd",
expirationTime: ...,
keys: { ... }
},
"https://fcm...zxcv": {
endpoint: "https://fcm...zxcv",
expirationTime: ...,
keys: { ... }
},
}
現在,新的訂閱可供伺服器傳送通知。
導入作業
新訂閱的要求則會傳至 /add-subscription
路徑,也就是 POST 網址。您會在 server.js 中看到虛設常式路徑處理常式:
server.js
app.post('/add-subscription', (request, response) => {
// TODO: implement handler for /add-subscription
console.log('TODO: Implement handler for /add-subscription');
console.log('Request body: ', request.body);
response.sendStatus(200);
});
在實作中,這個處理常式必須:
- 從要求的主體擷取新訂閱項目。
- 存取有效訂閱項目的資料庫。
- 將新訂閱項目加入有效訂閱項目清單。
如何處理新訂閱項目:
在 server.js 中,修改
/add-subscription
的路徑處理常式,如下所示:server.js
app.post('/add-subscription', (request, response) => {
// TODO: implement handler for /add-subscription
console.log('TODO: Implement handler for /add-subscription');
console.log('Request body: ', request.body);
let subscriptions = Object.assign({}, request.session.subscriptions);
subscriptions[request.body.endpoint] = request.body;
request.session.subscriptions = subscriptions;
response.sendStatus(200);
});
處理取消訂閱作業
背景
伺服器無法隨時得知訂閱項目何時進入閒置狀態,舉例來說,瀏覽器關閉 Service Worker 時,系統可能會清除訂閱項目。
不過,伺服器可以找出透過應用程式 UI 取消的訂閱項目。在這個步驟中,您將實作從資料庫中移除訂閱項目的功能。
如此一來,伺服器就能避免向不存在的端點傳送大量通知。使用簡單的測試應用程式其實並不重要,但大規模而言都很重要。
導入作業
取消訂閱的要求會導向 /remove-subscription
POST 網址。
server.js 中的虛設常式路徑處理常式如下所示:
server.js
app.post('/remove-subscription', (request, response) => {
// TODO: implement handler for /remove-subscription
console.log('TODO: Implement handler for /remove-subscription');
console.log('Request body: ', request.body);
response.sendStatus(200);
});
在實作中,這個處理常式必須:
- 從要求內文擷取已取消訂閱項目的端點。
- 存取有效訂閱項目的資料庫。
- 將已取消的訂閱項目從有效訂閱項目清單中移除。
用戶端的 POST 要求內容包含您需要移除的端點:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}
如何處理取消訂閱事宜:
在 server.js 中,修改
/remove-subscription
的路徑處理常式,如下所示:server.js
app.post('/remove-subscription', (request, response) => {
// TODO: implement handler for /remove-subscription
console.log('TODO: Implement handler for /remove-subscription');
console.log('Request body: ', request.body);
let subscriptions = Object.assign({}, request.session.subscriptions);
delete subscriptions[request.body.endpoint];
request.session.subscriptions = subscriptions;
response.sendStatus(200);
});