Codelab:构建推送通知服务器

Kate Jeffreys
Kate Jeffreys
Kayce Basques
Kayce Basques

此 Codelab 将逐步展示如何构建推送通知服务器。完成本 Codelab 后,您将拥有一个具备以下特征的服务器:

  • 跟踪推送通知订阅情况(即,当客户端选择接收推送通知时,服务器会创建新的数据库记录;当客户端选择不接收推送通知时,服务器会删除现有的数据库记录)
  • 向单个客户端发送推送通知
  • 向所有订阅的客户端发送推送通知

此 Codelab 侧重于帮助您通过实践来学习,不会过多地介绍概念。如需了解推送通知的概念,请参阅推送通知的工作原理

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

浏览器兼容性

此 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 体验。

设置

设置身份验证

您需要先为服务器和客户端设置身份验证密钥,然后才能使推送通知正常运作。 如需了解原因,请参阅为 Web 推送协议请求签名

  1. 打开终端。
  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. 打开 public/index.js
  2. VAPID_PUBLIC_KEY_VALUE_HERE 替换为您的公钥值。

管理订阅

客户端会处理大部分订阅流程。服务器需要做的主要事情是保存新的推送通知订阅并删除旧的订阅。正是这些订阅让您能够在日后向客户端推送消息。 如需详细了解订阅流程,请参阅让客户端订阅推送通知

保存新的订阅信息

  1. 在应用标签页中,点击 Register service worker。在状态框中,您应该会看到类似如下的消息:
Service worker registered. Scope: https://example.com
  1. 在应用标签页中,点击订阅推送。您的浏览器或操作系统可能会询问您是否要允许该网站向您发送推送通知。点击允许(或浏览器/操作系统使用的任何等效短语)。在状态框中,您应该会看到类似如下的消息:
Service worker subscribed to push.  Endpoint: https://fcm.googleapis.com/fcm/send/…
  1. 打开终端以查看日志。您应该会看到 /add-subscription,后面跟着一些数据。/add-subscription 是客户端在想要订阅推送通知时向其发送 POST 请求的网址。以下数据是您需要保存的客户订阅信息。
  2. 打开 server.js
  3. 使用以下代码更新 /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. 再次查看日志。您应该会看到 /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. 点击通知我。您应该会收到推送通知。标题应为 Hello, Notifications!,正文应为 ID: <ID>,其中 <ID> 是一个随机数。
  3. 在其他浏览器或设备上打开您的应用,尝试让这些浏览器或设备订阅推送通知,然后点击通知所有用户按钮。您应该会在所有订阅的设备上收到相同的通知(即推送通知正文中的 ID 应该相同)。

后续步骤

  • 如需深入了解推送通知的工作原理,请参阅推送通知概览
  • 请参阅 Codelab:构建推送通知客户端,了解如何构建一个客户端,该客户端会请求通知权限、订阅设备以接收推送通知,并使用服务工作线程接收推送消息,然后将这些消息显示为通知。