向 Web 应用添加推送通知

推送消息是一种简单有效的方式,可让您重新吸引用户。在此 Codelab 中,您将学习如何向 Web 应用添加推送通知。

学习内容

  • 如何订阅和取消订阅推送消息
  • 如何处理收到的推送消息
  • 如何显示通知
  • 如何响应通知点击

所需条件

  • Chrome 52 或更高版本
  • Web Server for Chrome 或您选择的自有网络服务器
  • 文本编辑器
  • 具备 HTML、CSS、JavaScript 和 Chrome 开发者工具方面的基础知识
  • 示例代码(请参阅“准备工作”)

下载示例代码

您可以通过以下两种方式获取此 Codelab 的示例代码:

  • 克隆 Git 代码库:
git clone https://github.com/GoogleChrome/push-notifications.git
  • 下载 ZIP 文件:

下载源代码

如果您将源代码下载为 ZIP 文件,解压缩后会得到一个根文件夹 push-notifications-master

安装并验证 Web 服务器

尽管您可以随意使用自己的网络服务器,但此 Codelab 旨在与 Web Server for Chrome 应用配合使用。如果您尚未安装该应用,可以通过 Chrome 网上应用店获取:

安装 Web Server for Chrome

安装 Web Server for Chrome 应用后,点击书签栏中的应用快捷方式:

在“应用”窗口中,点击“Web Server”图标:

接下来,您将看到以下对话框,该对话框可让您配置本地网络服务器:

点击选择文件夹按钮,然后选择您下载的 push-notifications 文件夹中的 app 文件夹。这样一来,您就可以通过对话框的 Web Server 网址(s)(网络服务器网址)部分中显示的网址处理正在进行的工作。

选项下,勾选自动显示 index.html 旁边的复选框,如下所示:

然后,将 Web Server: STARTED(网络服务器:已启动)切换开关向左滑动,然后再向右滑动,以停止并重启服务器。

点击 Web Server 网址,在网络浏览器中访问您的网站。您应该会看到如下所示的页面,不过您的版本可能会显示 127.0.0.1:8887 作为地址:

00-push-codelab.png

始终更新服务工作线程

在开发过程中,确保您的 Service Worker 始终是最新的并包含最新的更改非常有用。

如需在 Chrome 中设置此功能,请执行以下操作:

  1. 前往 Push Codelab 标签页。
  2. 打开开发者工具:在 Windows 和 Linux 上按 Ctrl-Shift-I,在 macOS 上按 Cmd-Option-I。
  3. 选择应用面板,点击 Service Worker 标签页,然后选中重新加载时更新复选框。选中此复选框后,每次重新加载网页时,系统都会强制更新 Service Worker。

已完成的代码

app 目录中,您会发现有一个名为 sw.js 的空文件。此文件将是您的服务工作线程。目前,您可以将其留空。您稍后会向其中添加代码。

首先,您需要将此文件注册为服务工作线程。

app/index.html 页面加载scripts/main.js。您可以在此 JavaScript 文件中注册服务工作线程。

将以下代码添加到 scripts/main.js

if ('serviceWorker' in navigator && 'PushManager' in window) {
  console.log('Service Worker and Push are supported');

  navigator.serviceWorker.register('sw.js')
  .then(function(swReg) {
    console.log('Service Worker is registered', swReg);

    swRegistration = swReg;
  })
  .catch(function(error) {
    console.error('Service Worker Error', error);
  });
} else {
  console.warn('Push messaging is not supported');
  pushButton.textContent = 'Push Not Supported';
}

此代码用于检查您的浏览器是否支持 Service Worker 和推送消息传递。如果支持,代码会注册您的 sw.js 文件。

试试看

通过刷新浏览器中的 Push Codelab 标签页来检查您的更改。

在 Chrome 开发者工具中查看控制台,看看是否有 Service Worker is registered message,如下所示:

获取应用服务器密钥

若要完成此 Codelab,您需要生成应用服务器密钥。您可以在配套网站 web-push-codelab.glitch.me 上执行此操作

您可以在此处生成公钥和私钥对。

push-codelab-04-companion.png

将您的公钥复制到 scripts/main.js 中,替换 <Your Public Key> 值:

const applicationServerPublicKey = '<Your Public Key>';

重要提示:您绝不应将私钥放在 Web 应用中!

已完成的代码

目前,Web 应用的启用 按钮处于停用状态,无法点击。这是因为,最佳实践是默认情况下停用推送按钮,并在您知道浏览器支持推送消息传递后启用该按钮,并且能够检查用户当前是否订阅了消息传递。

您需要在 scripts/main.js 中创建两个函数:

  • initializeUI,以检查用户当前是否已订阅
  • updateBtn,以启用按钮并根据用户是否订阅来更改文本

initializeUI 函数添加到 main.js,如下所示:

function initializeUI() {
  // Set the initial subscription value
  swRegistration.pushManager.getSubscription()
  .then(function(subscription) {
    isSubscribed = !(subscription === null);

    if (isSubscribed) {
      console.log('User IS subscribed.');
    } else {
      console.log('User is NOT subscribed.');
    }

    updateBtn();
  });
}

新方法使用上一步中的 swRegistration,从中获取 pushManager 属性,并对该属性调用 getSubscription()

pushManagergetSubscription() 会返回一个 Promise,如果存在当前订阅,则该 Promise 会解析为当前订阅。否则,返回 null。这样一来,您就可以检查用户是否已订阅、设置 isSubscribed 的值,然后调用 updateBtn() 来更新按钮。

updateBtn() 函数添加到 main.js

function updateBtn() {
  if (isSubscribed) {
    pushButton.textContent = 'Disable Push Messaging';
  } else {
    pushButton.textContent = 'Enable Push Messaging';
  }

  pushButton.disabled = false;
}

此函数会启用按钮,并根据用户是否订阅来更改按钮文字。

最后要做的是,当您的服务工作线程在 main.js 中注册时调用 initializeUI()

navigator.serviceWorker.register('sw.js')
.then(function(swReg) {
  console.log('Service Worker is registered', swReg);

  swRegistration = swReg;
  initializeUI();
})

试试看

刷新 Push Codelab 标签页。您应该会看到启用推送消息按钮现在处于启用状态(您可以点击它),并且您应该会在控制台中看到 User is NOT subscribed

在完成此 Codelab 的其余部分时,您应该会看到按钮文字在每次订阅或取消订阅时发生变化。

已完成的代码

目前,您的启用推送消息按钮还不能发挥太大作用。让我们解决这个问题。

initializeUI() 函数中,为按钮添加点击监听器:

function initializeUI() {
  pushButton.addEventListener('click', function() {
    pushButton.disabled = true;
    if (isSubscribed) {
      // TODO: Unsubscribe user
    } else {
      subscribeUser();
    }
  });

  // Set the initial subscription value
  swRegistration.pushManager.getSubscription()
  .then(function(subscription) {
    isSubscribed = !(subscription === null);

    updateSubscriptionOnServer(subscription);

    if (isSubscribed) {
      console.log('User IS subscribed.');
    } else {
      console.log('User is NOT subscribed.');
    }

    updateBtn();
  });
}

当用户点击该按钮时,您会停用该按钮,以确保用户不会再次点击该按钮,因为订阅推送消息可能需要一些时间。

然后,如果用户当前未订阅,则调用 subscribeUser()。为此,您需要将以下代码粘贴到 scripts/main.js 中:

function subscribeUser() {
  const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
  swRegistration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: applicationServerKey
  })
  .then(function(subscription) {
    console.log('User is subscribed.');

    updateSubscriptionOnServer(subscription);

    isSubscribed = true;

    updateBtn();
  })
  .catch(function(error) {
    console.error('Failed to subscribe the user: ', error);
    updateBtn();
  });
}

我们来逐步了解此代码的作用以及如何为用户订阅推送消息。

首先,您需要获取应用服务器的公钥(以 Base64 网址 安全编码),并将其转换为 UInt8Array,因为这是 subscribe() 调用的预期输入。urlB64ToUint8Array() 函数位于 scripts/main.js 的顶部。

转换值后,您可以在服务工作线程的 pushManager 上调用 subscribe() 方法,并传入应用服务器的公钥和值 userVisibleOnly: true

const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: applicationServerKey
})

userVisibleOnly 参数可确保您在每次发送推送消息时都显示通知。目前,此值为必需值,且必须为 true。

调用 subscribe() 会返回一个 promise,该 promise 将在完成以下步骤后解析:

  1. 用户已授予显示通知的权限。
  2. 浏览器已向推送服务发送网络请求,以获取生成 PushSubscription 所需的数据。

如果这些步骤成功完成,subscribe() promise 将解析为 PushSubscription。如果用户未授予权限,或者在订阅用户时出现任何问题,Promise 将拒绝并显示错误。这样,您就可以在 Codelab 中获得以下 promise 链:

swRegistration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: applicationServerKey
})
.then(function(subscription) {
  console.log('User is subscribed.');

  updateSubscriptionOnServer(subscription);

  isSubscribed = true;

  updateBtn();

})
.catch(function(err) {
  console.log('Failed to subscribe the user: ', err);
  updateBtn();
});

这样一来,您要么获得订阅并视用户为订阅者,要么捕获错误并将其记录到控制台。在这两种情况下,您都会调用 updateBtn() 以确保按钮重新启用并显示相应的文本。

在实际应用中,您会在函数 updateSubscriptionOnServer() 中将订阅数据发送到后端,但在本 Codelab 中,您只需在界面中显示订阅即可。将以下函数添加到 scripts/main.js 中:

function updateSubscriptionOnServer(subscription) {
  // TODO: Send subscription to application server

  const subscriptionJson = document.querySelector('.js-subscription-json');
  const subscriptionDetails =
    document.querySelector('.js-subscription-details');

  if (subscription) {
    subscriptionJson.textContent = JSON.stringify(subscription);
    subscriptionDetails.classList.remove('is-invisible');
  } else {
    subscriptionDetails.classList.add('is-invisible');
  }
}

试试看

前往 Push Codelab 标签页,刷新页面,然后点击该按钮。您应该会看到类似如下所示的权限提示:

如果您授予该权限,您应该会在控制台中看到 User is subscribed 日志。该按钮的文本将更改为 Disable Push Messaging(停用推送消息),并且您将能够在页面底部以 JSON 数据的形式查看订阅。

已完成的代码

您尚未处理的一件事是,如果用户屏蔽权限请求,会发生什么情况。这需要一些独特的考虑,因为如果用户屏蔽了权限,您的 Web 应用将无法再次显示权限提示,也无法让用户订阅。您至少需要停用该按钮,以便用户知道它无法使用。

处理此场景的明显位置是在 updateBtn() 函数中。您只需检查 Notification.permission 值,如下所示:

function updateBtn() {
  if (Notification.permission === 'denied') {
    pushButton.textContent = 'Push Messaging Blocked';
    pushButton.disabled = true;
    updateSubscriptionOnServer(null);
    return;
  }

  if (isSubscribed) {
    pushButton.textContent = 'Disable Push Messaging';
  } else {
    pushButton.textContent = 'Enable Push Messaging';
  }

  pushButton.disabled = false;
}

您知道,如果权限为 denied,则用户无法订阅,您也无能为力,因此最好永久停用该按钮。

试试看

由于您已在上一步中为 Web 应用授予权限,因此您需要点击网址栏中圆圈内的 i,并将通知权限更改为使用全局默认设置(询问)

更改此设置后,刷新页面,然后点击启用推送消息按钮,并在权限对话框中选择阻止。该按钮将处于停用状态,并显示“推送消息已屏蔽”文本。

进行此更改后,您现在可以订阅用户,并处理可能的权限场景。

已完成的代码

在学习如何从后端发送推送消息之前,您需要考虑订阅用户收到推送消息时实际会发生什么情况。

当您触发推送消息时,浏览器会收到该推送消息,确定该推送消息是针对哪个服务工作线程的,然后唤醒该服务工作线程并分派推送事件。您需要监听此事件,并显示一条通知作为结果。

将以下代码添加到您的 sw.js 文件中:

self.addEventListener('push', function(event) {
  console.log('[Service Worker] Push Received.');
  console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);

  const title = 'Push Codelab';
  const options = {
    body: 'Yay it works.',
    icon: 'images/icon.png',
    badge: 'images/badge.png'
  };

  event.waitUntil(self.registration.showNotification(title, options));
});

我们来逐步了解一下此代码。您可以通过添加事件监听器,在服务工作线程中监听 push 事件:

self.addEventListener('push', ... );

(除非您之前使用过 Web Worker,否则 self 可能是一个新概念。在服务工作器文件中,self 引用的是服务工作器本身。)

当收到推送消息时,系统会调用事件监听器,然后您可以通过调用服务工作线程的 registration 属性上的 showNotification() 来创建通知。showNotification() 需要 title;您还可以为其提供 options 对象来设置正文消息、图标和标记。(撰写本文时,徽章仅在 Android 上使用。)

const title = 'Push Codelab';
const options = {
  body: 'Yay it works.',
  icon: 'images/icon.png',
  badge: 'images/badge.png'
};
self.registration.showNotification(title, options);

push 事件处理中要介绍的最后一项是 event.waitUntil()。此方法接受一个 promise,以使浏览器能够保持您的服务工作线程处于活动状态并运行,直到传入的 promise 已得到解决。

为了让上面的代码更易于理解,您可以将其改写为如下形式:

const notificationPromise = self.registration.showNotification(title, options);
event.waitUntil(notificationPromise);

现在,您已逐步了解了推送事件,接下来我们来测试一下推送事件。

试试看

借助服务工作线程中的推送事件处理功能,您可以触发虚假推送事件,以测试在收到消息时会发生什么情况。

在 Web 应用中,订阅推送消息,并确保在控制台中看到 User IS subscribed。在开发者工具的应用面板中,点击 Service Workers 标签页下的 Push 按钮:

点击 Push 后,您应该会看到类似如下所示的通知:

注意:如果此步骤不起作用,请尝试使用开发者工具“应用”面板中的 Unregister 链接取消注册 Service Worker,等待 Service Worker 停止,然后重新加载页面。

已完成的代码

如果您点击其中一个通知,会发现没有任何反应。您可以在 Service Worker 中监听 notificationclick 事件,以处理通知点击。

首先,在 sw.js 中添加 notificationclick 监听器:

self.addEventListener('notificationclick', function(event) {
  console.log('[Service Worker] Notification click received.');

  event.notification.close();

  event.waitUntil(
    clients.openWindow('https://developers.google.com/web')
  );
});

当用户点击通知时,系统会调用 notificationclick 事件监听器。

该代码首先关闭了被点击的通知:

event.notification.close();

然后,系统会打开一个新窗口或标签页,加载网址 https://developers.google.com/web。您可以随意更改此设置。

event.waitUntil(
    clients.openWindow('https://developers.google.com/web/')
  );

event.waitUntil() 可确保浏览器不会在新窗口或标签页显示之前终止服务工作线程。

试试看

再次尝试在 DevTools 中触发推送消息,然后点击该通知。现在,您会看到通知关闭,并打开一个新标签页。

您已看到,您的 Web 应用能够使用开发者工具显示通知,并且您已了解如何通过点击关闭通知。下一步是发送实际的推送消息。

正常情况下,这需要从网页向后端发送订阅。然后,后端会通过向订阅中的端点发出 API 调用来触发推送消息。

这不属于此 Codelab 的讨论范围,但您可以使用配套网站 (web-push-codelab.glitch.me) 来触发实际的推送消息。将订阅内容粘贴到网页底部:

然后,将此内容粘贴到配套网站的订阅发送至文本区域中:

要发送的文本下方,添加您要随推送消息发送的任何字符串。

点击发送推送消息按钮。

然后,您应该会收到一条推送消息。您使用的文本将记录到控制台。

这样,您就可以测试数据的发送和接收,并相应地操纵通知。

配套应用只是一个使用 web-push 库发送消息的节点服务器。建议您查看 GitHub 上的 web-push-libs 组织,了解有哪些库可用于为您发送推送消息。这会处理触发推送消息的许多细节。

您可以点击此处查看配套网站的所有代码

已完成的代码

唯一缺少的功能是让用户退订推送。为此,您需要在 PushSubscription 上调用 unsubscribe()

回到 scripts/main.js 文件中,将 initializeUI() 中的 pushButton 点击监听器更改为以下内容:

pushButton.addEventListener('click', function() {
  pushButton.disabled = true;
  if (isSubscribed) {
    unsubscribeUser();
  } else {
    subscribeUser();
  }
});

请注意,您现在要调用一个新函数 unsubscribeUser()。在此函数中,您将获取当前订阅并对其调用 unsubscribe()。将以下代码添加到 scripts/main.js

function unsubscribeUser() {
  swRegistration.pushManager.getSubscription()
  .then(function(subscription) {
    if (subscription) {
      return subscription.unsubscribe();
    }
  })
  .catch(function(error) {
    console.log('Error unsubscribing', error);
  })
  .then(function() {
    updateSubscriptionOnServer(null);

    console.log('User is unsubscribed.');
    isSubscribed = false;

    updateBtn();
  });
}

我们来了解一下这个函数。

首先,您可以通过调用 getSubscription() 获取当前订阅:

swRegistration.pushManager.getSubscription()

如果存在 PushSubscription,则返回一个解析为 PushSubscription 的 promise;否则,返回 null。如果有订阅,您会对其调用 unsubscribe(),这会使 PushSubscription 无效。

swRegistration.pushManager.getSubscription()
.then(function(subscription) {
  if (subscription) {
    // TODO: Tell application server to delete subscription
    return subscription.unsubscribe();
  }
})
.catch(function(error) {
  console.log('Error unsubscribing', error);
})

由于调用 unsubscribe() 可能需要一些时间才能完成,因此它会返回一个 promise。您返回该 promise,以便链中的下一个 then() 等待 unsubscribe() 完成。您还可以添加一个 catch 处理程序,以防调用 unsubscribe() 导致错误。之后,您可以更新界面。

.then(function() {
  updateSubscriptionOnServer(null);

  console.log('User is unsubscribed.');
  isSubscribed = false;

  updateBtn();
})

试试看

您应该能够在 Web 应用中按 Enable Push Messaging(启用推送消息)或 Disable Push Messaging(停用推送消息),并且日志会显示用户订阅和取消订阅的情况。

恭喜您完成此 Codelab!

此 Codelab 已向您展示了如何开始向 Web 应用添加推送通知。如果您想详细了解 Web 通知的功能,请查看这些文档

如果您想在网站上部署推送通知,可能需要添加对使用 GCM 的旧版浏览器或不符合标准的浏览器的支持。点击此处可了解详情

深入阅读

相关博文