本程式碼研究室會逐步說明如何建構推播通知伺服器。完成本程式碼研究室後,您將擁有下列伺服器:
- 追蹤推播通知訂閱項目 (也就是說,當用戶端選擇接收推播通知時,伺服器會建立新的資料庫記錄;當用戶端選擇不接收推播通知時,伺服器會刪除現有的資料庫記錄)
- 將推播通知傳送給單一用戶端
- 將推播通知傳送給所有已訂閱的用戶端
本程式碼研究室著重於透過實作來學習,不會深入探討概念。如要瞭解推播通知的概念,請參閱「推播通知的運作方式為何?」。
本程式碼研究室的用戶端程式碼已完成。在本程式碼研究室中,您只會實作伺服器。如要瞭解如何實作推播通知用戶端,請參閱「程式碼研究室:建構推播通知用戶端」。
瀏覽器相容性
據瞭解,本程式碼研究室適用於下列作業系統和瀏覽器組合:
- Windows:Chrome、Edge
- macOS:Chrome、Firefox
- Android:Chrome、Firefox
已知本程式碼研究室無法在下列作業系統 (或作業系統和瀏覽器組合) 中運作:
- macOS:Brave、Edge、Safari
- iOS
應用程式堆疊
- 伺服器是以 Express.js 為基礎建構。
- web-push Node.js 程式庫會處理所有推播通知邏輯。
- 訂閱資料會使用 lowdb 寫入 JSON 檔案。
您不必使用任何這些技術,就能實作推播通知。 我們選擇這些技術,是因為它們能提供可靠的程式碼研究室體驗。
設定
設定驗證方法
如要讓推播通知正常運作,請先使用驗證金鑰設定伺服器和用戶端。如要瞭解原因,請參閱「簽署網頁推送通訊協定要求」。
- 開啟終端機。
- 在終端機中執行 npx web-push generate-vapid-keys。複製私密金鑰和公開金鑰值。
- 開啟 .env並更新VAPID_PUBLIC_KEY和VAPID_PRIVATE_KEY。將VAPID_SUBJECT設為mailto:test@test.test。所有這些值都應以雙引號括住。更新後,.env檔案應如下所示:
VAPID_PUBLIC_KEY="BKiwTvD9HA…"
VAPID_PRIVATE_KEY="4mXG9jBUaU…"
VAPID_SUBJECT="mailto:test@test.test"
- 開啟 public/index.js。
- 將 VAPID_PUBLIC_KEY_VALUE_HERE替換為公開金鑰的值。
管理訂閱項目
客戶會處理大部分的訂閱程序。伺服器主要需要儲存新的推播通知訂閱項目,並刪除舊的訂閱項目。這些訂閱項目可讓您日後將訊息推送至用戶端。如要進一步瞭解訂閱程序,請參閱「為用戶端訂閱推播通知」。
儲存新的訂閱資訊
- 在應用程式分頁中,按一下「Register service worker」(註冊 Service Worker)。狀態方塊中應會顯示類似以下的訊息:
Service worker registered. Scope: https://example.com
- 在應用程式分頁中,按一下「Subscribe to push」(訂閱推播)。瀏覽器或作業系統可能會詢問你是否要允許網站傳送推播通知。按一下「允許」 (或瀏覽器/作業系統使用的同義詞)。狀態方塊中應會顯示類似以下的訊息:
Service worker subscribed to push.  Endpoint: https://fcm.googleapis.com/fcm/send/…
- 開啟終端機即可查看記錄。您應該會看到 /add-subscription,後面接著一些資料。/add-subscription是用戶端在想訂閱推播通知時,傳送 POST 要求的網址。以下資料是您需要儲存的客戶訂閱資訊。
- 開啟 server.js。
- 使用下列程式碼更新 /add-subscription路由處理常式邏輯:
app.post('/add-subscription', (request, response) => {
  console.log('/add-subscription');
  console.log(request.body);
  console.log(`Subscribing ${request.body.endpoint}`);
  db.get('subscriptions')
    .push(request.body)
    .write();
  response.sendStatus(200);
});
刪除舊的訂閱資訊
- 返回應用程式分頁。
- 按一下「取消訂閱推播通知」。
- 再次查看記錄。您應該會看到 /remove-subscription,後面接著客戶的訂閱資訊。
- 使用下列程式碼更新 /remove-subscription路由處理常式邏輯:
app.post('/remove-subscription', (request, response) => {
  console.log('/remove-subscription');
  console.log(request.body);
  console.log(`Unsubscribing ${request.body.endpoint}`);
  db.get('subscriptions')
    .remove({endpoint: request.body.endpoint})
    .write();
  response.sendStatus(200);
});
傳送通知
如「傳送即時訊息」一文所述,伺服器不會直接將即時訊息傳送給用戶端,而是依賴推送服務來執行這項操作。基本上,您的伺服器只要向使用者所用瀏覽器供應商擁有的網路服務 (即推送服務) 發出網路服務要求 (網路推送通訊協定要求),即可啟動將訊息推送至用戶端的程序。
- 使用下列程式碼更新 /notify-me路由處理常式邏輯:
app.post('/notify-me', (request, response) => {
  console.log('/notify-me');
  console.log(request.body);
  console.log(`Notifying ${request.body.endpoint}`);
  const subscription = 
      db.get('subscriptions').find({endpoint: request.body.endpoint}).value();
  sendNotifications([subscription]);
  response.sendStatus(200);
});
- 使用下列程式碼更新 sendNotifications()函式:
function sendNotifications(subscriptions) {
  // TODO
  // Create the notification content.
  const notification = JSON.stringify({
    title: "Hello, Notifications!",
    options: {
      body: `ID: ${Math.floor(Math.random() * 100)}`
    }
  });
  // Customize how the push service should attempt to deliver the push message.
  // And provide authentication information.
  const options = {
    TTL: 10000,
    vapidDetails: vapidDetails
  };
  // Send a push message to each client specified in the subscriptions array.
  subscriptions.forEach(subscription => {
    const endpoint = subscription.endpoint;
    const 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} `);
      });
  });
}
- 使用下列程式碼更新 /notify-all路由處理常式邏輯:
app.post('/notify-all', (request, response) => {
  console.log('/notify-all');
  response.sendStatus(200);
  console.log('Notifying all subscribers');
  const subscriptions =
      db.get('subscriptions').cloneDeep().value();
  if (subscriptions.length > 0) {
    sendNotifications(subscriptions);
    response.sendStatus(200);
  } else {
    response.sendStatus(409);
  }
});
- 返回應用程式分頁。
- 按一下「通知我」。你應該會收到推播通知。標題應為 Hello, Notifications!,內文應為ID: <ID>,其中<ID>是隨機數字。
- 在其他瀏覽器或裝置上開啟應用程式,嘗試讓使用者訂閱推播通知,然後按一下「通知所有人」按鈕。所有已訂閱的裝置應會收到相同通知 (即推播通知主體中的 ID 應相同)。
後續步驟
- 如要深入瞭解推播通知的運作方式,請參閱「推播通知總覽」。
- 請參閱程式碼研究室:建構推播通知用戶端,瞭解如何建構用戶端,要求通知權限、訂閱裝置以接收推播通知,以及使用服務工作站接收推播訊息,並將訊息顯示為通知。
 
 
        
        