Codelab:构建推送通知服务器

Kate Jeffreys
Kate Jeffreys

此 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 即可修改项目。

设置身份验证

您需要先使用身份验证密钥设置服务器和客户端,然后才能使用推送通知。如需了解原因,请参阅对网页推送协议请求进行签名

  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(全屏)全屏
  1. 点击“应用”标签页中的注册服务工件。您应该会在状态框中看到类似如下的消息:
Service worker registered. Scope: https://desert-cactus-sunset.glitch.me/
  1. 在“应用”标签页中,点击订阅推送。您的浏览器或操作系统可能会询问您是否要允许该网站向您发送推送通知。点击允许(或浏览器/操作系统使用的任何等效字词)。您应该会在状态框中看到类似如下的消息:
Service worker subscribed to push.  Endpoint: https://fcm.googleapis.com/fcm/send/…
  1. 在 Glitch 界面中,点击查看源代码即可返回代码。
  2. 依次点击工具日志,打开 Glitch 日志。您应该会看到 /add-subscription 后跟一些数据。/add-subscription 是客户端在想要订阅推送通知时向其发送 POST 请求的网址。以下数据是您需要保存的客户订阅信息。
  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. 点击退订推送通知
  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. 点击取消订阅推送通知,然后再次点击订阅推送通知。 之所以需要这样做,是因为如前所述,每次您修改代码时,Glitch 都会重启项目,而项目配置为在启动时删除数据库。
  3. 点击通知我。您应该会收到推送通知。标题应为 Hello, Notifications!,正文应为 ID: <ID>,其中 <ID> 为随机数字。
  4. 在其他浏览器或设备上打开您的应用,然后尝试订阅推送通知,再点击通知所有人按钮。您应该会在所有已订阅的设备上收到相同的通知(即推送通知正文中的 ID 应相同)。

后续步骤

  • 如需从概念层面深入了解推送通知的运作方式,请参阅推送通知概览
  • 请参阅 Codelab:构建推送通知客户端,了解如何构建请求通知权限、订阅设备以接收推送通知,以及使用服务工件接收推送消息并将消息显示为通知的客户端。