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

设置

设置身份验证

您需要先为服务器和客户端设置身份验证密钥,然后才能使推送通知正常运行。如需了解原因,请参阅为 Web 推送协议请求签名。通常,您会将 Secret 存储在 .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 替换为您的公钥值。

注册 Service Worker

您的客户端需要 Service Worker 才能接收和显示通知。最好尽早注册 Service Worker。 如需了解更多背景信息,请参阅以通知形式接收和显示推送的消息。

  1. 将 // TODO add startup logic here 注释替换为以下代码:

    if ('serviceWorker' in navigator && 'PushManager' in window) {
      navigator.serviceWorker.register('./service-worker.js').then(serviceWorkerRegistration => {
        console.info('Service worker was registered.');
        console.info({serviceWorkerRegistration});
      }).catch(error => {
        console.error('An error occurred while registering the service worker.');
        console.error(error);
      });
      subscribeButton.disabled = false;
    } else {
      console.error('Browser does not support service workers or push messages.');
    }
    
    subscribeButton.addEventListener('click', subscribeButtonHandler);
    unsubscribeButton.addEventListener('click', unsubscribeButtonHandler);
    
  2. 在 Chrome 中,打开开发者工具控制台。

  3. 您应该会看到控制台中记录了 Service worker was registered. 消息。

请求推送通知权限

切勿在网页加载时请求发送推送通知的权限。 相反,您的界面应询问用户是否希望接收推送通知。 在他们明确确认(例如,通过点击按钮)后,您就可以开始正式流程,从浏览器获取推送通知权限。

  1. 在 public/index.js 中,将 subscribeButtonHandler() 中的 // TODO 注释替换为以下代码:

    // Prevent the user from clicking the subscribe button multiple times.
    subscribeButton.disabled = true;
    const result = await Notification.requestPermission();
    if (result === 'denied') {
      console.error('The user explicitly denied the permission request.');
      return;
    }
    if (result === 'granted') {
      console.info('The user accepted the permission request.');
    }
    
  2. 返回应用标签页,然后点击订阅推送。您的浏览器或操作系统会询问您是否允许该网站向您发送推送通知。

  3. 选择允许(或浏览器/操作系统使用的等效短语)。在控制台中,您应该会看到一条消息,指明请求是被接受还是被拒绝。

订阅推送通知

订阅流程涉及与由浏览器供应商控制的 Web 服务(称为推送服务)进行交互。获取推送通知订阅信息后,您需要将其发送到服务器,并让服务器将其长期存储在数据库中。

  1. 将以下突出显示的代码添加到 subscribeButtonHandler():

    subscribeButton.disabled = true;
    const result = await Notification.requestPermission();
    if (result === 'denied') {
      console.error('The user explicitly denied the permission request.');
      return;
    }
    if (result === 'granted') {
      console.info('The user accepted the permission request.');
    }
    const registration = await navigator.serviceWorker.getRegistration();
    const subscribed = await registration.pushManager.getSubscription();
    if (subscribed) {
      console.info('User is already subscribed.');
      notifyMeButton.disabled = false;
      unsubscribeButton.disabled = false;
      return;
    }
    const subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY)
    });
    notifyMeButton.disabled = false;
    fetch('/add-subscription', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(subscription)
    });
    

userVisibleOnly 选项必须为 true。未来或许可以推送消息而不显示用户可见的通知(静默推送),但出于隐私方面的考虑,浏览器目前不允许使用该功能。

applicationServerKey 值依赖于一个实用函数,该函数可将 base64 字符串转换为 Uint8Array。此值用于在您的服务器和推送服务之间进行身份验证。

退订推送通知

在用户订阅推送通知后,您的界面需要提供一种退订方式,以防用户改变主意,不想再接收推送通知。

  • 将 unsubscribeButtonHandler() 中的 // TODO 注释替换为以下代码:
const registration = await navigator.serviceWorker.getRegistration();
const subscription = await registration.pushManager.getSubscription();
fetch('/remove-subscription', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({endpoint: subscription.endpoint})
});
const unsubscribed = await subscription.unsubscribe();
if (unsubscribed) {
  console.info('Successfully unsubscribed from push notifications.');
  unsubscribeButton.disabled = true;
  subscribeButton.disabled = false;
  notifyMeButton.disabled = true;
}

接收推送消息并将其显示为通知

如前所述,您需要一个 Service Worker 来处理从服务器推送到客户端的消息的接收和显示。如需了解详情,请参阅接收推送的消息并将其显示为通知。

  1. 打开 public/service-worker.js,并将 Service Worker 的 push 事件处理脚本中的 // TODO 注释替换为以下代码:

    let data = event.data.json();
    const image = 'logo.png';
    const options = {
      body: data.options.body,
      icon: image
    }
    self.registration.showNotification(
      data.title,
      options
    );
    
  2. 返回到应用标签页。

  3. 点击通知我。您应该会收到一条推送通知。

  4. 在其他受支持的浏览器中打开应用标签页的网址。完成订阅工作流,然后点击通知所有人。您应该会在每个设备上收到相同的推送通知。

您可以通过多种方式自定义通知。如需了解详情,请参阅 ServiceWorkerRegistration.showNotification() 的参数。

在用户点击通知时打开网址

在实际应用中,您可能会使用通知来重新吸引用户,并提示他们访问您的网站。 为此,您需要对 Service Worker 进行更多配置。

  1. 将 Service Worker 的 notificationclick 事件处理脚本中的 // TODO 注释替换为以下代码:

    event.notification.close();
    event.waitUntil(self.clients.openWindow('https://web.dev'));
    
  2. 返回应用标签页,再向自己发送一条通知,然后点击该通知。您的浏览器应该会打开一个新标签页并加载 https://web.dev。

后续步骤