Codelab: 构建推送通知服务器

本 Codelab 将逐步向您展示如何构建推送通知服务器。在本 Codelab 结束时,您将拥有一个服务器,它可以:

  • 跟踪推送通知订阅(即,当客户端选择加入推送通知时,服务器会创建一个新的数据库记录;当客户端选择退出时,服务器会删除现有的数据库记录)
  • 向单个客户端发送推送通知
  • 向所有已订阅的客户端发送推送通知

本 Codelab 侧重于帮助您在实践中学习,并且不涉及太多概念。请查看推送通知如何工作?,以了解推送通知的概念。

本 Codelab 的客户端代码已完成。在本 Codelab 中,您只需实现服务器端。要了解如何实现推送通知客户端,请查看 Codelab:构建推送通知客户端

请查看 push-notifications-server-codelab-complete源代码)以查看完整代码。

浏览器兼容性

已知此 Codelab 可在以下操作系统和浏览器组合下工作:

  • Windows:Chrome、Edge
  • macOS:Chrome、Firefox
  • Android:Chrome、Firefox

已知此 Codelab 在以下操作系统(或操作系统和浏览器组合)下工作:

  • macOS:Brave、Edge、Safari
  • iOS

应用堆栈

  • 服务器构建于 Express.js 之上。
  • web-push Node.js 库处理所有推送通知逻辑。
  • 订阅数据使用 lowdb 写入 JSON 文件。

您不必使用任何这些技术来实现推送通知。我们选择这些技术是因为它们提供了可靠的 Codelab 体验。

设置

获取代码的可编辑副本

在整个 Codelab 中,您在这些说明右侧看到的代码编辑器将称为 Glitch 界面

  1. 点击Remix to Edit(Remix 进行编辑)使项目可编辑。

设置身份验证

在推送通知开始工作之前,您需要使用身份验证密钥设置服务器和客户端。请参阅签署您的 Web 推送协议请求以了解原因。

  1. 点击 Tools(工具),然后点击 Terminal(终端),打开 Glitch 终端。
  2. 在终端中,运行 npx web-push generate-vapid-keys。复制私钥和公钥值。
  3. 打开 .env 并更新 VAPID_PUBLIC_KEYVAPID_PRIVATE_KEY。将 VAPID_SUBJECT 设置为 mailto:test@test.test。所有这些值都应包含在双引号中。更新后,您的 .env 文件应类似于这样:
VAPID_PUBLIC_KEY="BKiwTvD9HA…"
VAPID_PRIVATE_KEY="4mXG9jBUaU…"
VAPID_SUBJECT="mailto:test@test.test"
  1. 关闭 Glitch 终端。
  1. 打开 public/index.js
  2. VAPID_PUBLIC_KEY_VALUE_HERE 替换为您的公钥值。

管理订阅

您的客户端处理大部分订阅过程。您的服务器需要做的主要事情是保存新的推送通知订阅并删除旧的订阅。这些订阅使您能够在将来向客户端推送消息。请参阅将客户端订阅到推送通知,以了解有关订阅过程的更多背景信息。

保存新的订阅信息

  1. 要预览站点,请按View App(查看应用)。然后按Fullscreen(全屏) fullscreen
  1. 在应用标签页中,点击 Register service worker(注册 Service Worker)。在状态框中,您应该看到类似于以下内容的消息:
Service worker registered. Scope: https://desert-cactus-sunset.glitch.me/
  1. 在应用标签页中,点击 Subscribe to push(订阅推送)。您的浏览器或操作系统可能会询问您是否要允许网站向您发送推送通知。点击 Allow(允许)(或您的浏览器/操作系统使用的任何等效短语)。在状态框中,您应该看到类似于以下内容的消息:
Service worker subscribed to push.  Endpoint: https://fcm.googleapis.com/fcm/send/…
  1. 通过点击 Glitch 界面中的 View Source(查看源代码) 返回您的代码。
  2. 点击 Tools(工具),然后点击 Logs(日志),打开 Glitch 日志。您应该看到 /add-subscription,后跟一些数据。/add-subscription 是客户端在想要订阅推送通知时向其发送 POST 请求的 URL。后面的数据是您需要保存的客户端订阅信息。
  3. 打开 server.js
  4. 使用以下代码更新 /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);
});

删除旧的订阅信息

  1. 返回到应用标签页。
  2. 点击 Unsubscribe from push(取消订阅推送)
  3. 再次查看 Glitch 日志。您应该看到 /remove-subscription,后跟客户端的订阅信息。
  4. 使用以下代码更新 /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);
});

发送通知

发送推送消息中所述,您的服务器实际上并不直接向客户端发送推送消息。相反,它依赖于推送服务来执行此操作。您的服务器基本上只是通过向用户使用的浏览器供应商拥有的 Web 服务(推送服务)发出 Web 服务请求(Web 推送协议请求)来启动向客户端推送消息的过程。

  1. 使用以下代码更新 /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);
});
  1. 使用以下代码更新 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} `);
      });
  });
}
  1. 使用以下代码更新 /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);
  }
});
  1. 返回到应用标签页。
  2. 点击 Unsubscribe from push(取消订阅推送),然后再次点击 Subscribe to push(订阅推送)。这只是必要的,因为如前所述,Glitch 会在您每次编辑代码时重启项目,并且项目配置为在启动时删除数据库。
  3. 点击 Notify me(通知我)。您应该会收到推送通知。标题应为 Hello, Notifications!,正文应为 ID: <ID>,其中 <ID> 是一个随机数。
  4. 在其他浏览器或设备上打开您的应用,并尝试将它们订阅到推送通知,然后点击 Notify all(通知所有人) 按钮。您应该在所有已订阅的设备上收到相同的通知(即,推送通知正文中的 ID 应该相同)。

后续步骤

  • 阅读推送通知概述,以更深入地理解推送通知的工作原理。
  • 查看 Codelab:构建推送通知客户端,了解如何构建一个客户端,该客户端请求通知权限,订阅设备以接收推送通知,并使用 Service Worker 接收推送消息并将消息显示为通知。