向 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

安装并验证网络服务器

虽然您可以使用自己的网络服务器,但此 Codelab 非常适合与 Web 服务器(Chrome 版)应用搭配使用。如果您尚未安装此应用,可以从 Chrome 网上应用店下载:

安装 Chrome 版 Web 服务器

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

在“应用”窗口中,点击“网络服务器”图标:

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

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

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

然后将 Web Server: STARTED 切换开关滑至左侧,然后向右滑动,以停止并重启服务器。

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

00-push-codelab.png

始终更新 Service Worker

在开发过程中,有必要确保您的 Service Worker 始终是最新版本并包含最新更改。

要在 Chrome 中进行此设置,请执行以下操作:

  1. 转到 Push Codelab 标签页。
  2. 打开 DevTools:在 Windows 和 Linux 上按 Ctrl-Shift-I,在 macOS 上按 Cmd-Option-I。
  3. 选择 Application 面板,点击 Service Workers 标签页,然后选中 Update on Reload 复选框。启用此复选框后,每次重新加载页面时都会强制更新 Service Worker。

已完成的代码

app 目录中,请注意有一个名为 sw.js 的空文件。此文件将作为您的 Service Worker。目前,它可以留空。稍后您需要向其添加代码。

首先,您需要将此文件注册为 Service Worker。

您的 app/index.html 页面会加载 scripts/main.js。您可以在此 JavaScript 文件中注册 Service Worker。

将以下代码添加到 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 应用中!

已完成的代码

目前,网络应用的启用按钮处于停用状态,无法点击。这是因为在知道浏览器支持推送消息,而且您可以检查用户当前是否已订阅消息后,最好默认停用和启用按钮。

您需要在 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 可根据当前订阅进行解析(如果有)。否则返回 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 中注册 Service Worker 时,调用 initializeUI()

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

  swRegistration = swReg;
  initializeUI();
})

试试看

刷新 Push Codelab 标签页。您应该会看到 Enable Push Messaging 按钮现已启用(您可以点击该按钮),并且应该会在控制台中看到 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 的顶部。

转换值后,您可以对 Service Worker 的 pushManager 调用 subscribe() 方法,并传入应用服务器的公钥和值 userVisibleOnly: true

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

userVisibleOnly 参数用于保证每次发送推送消息时您都会显示通知。目前,此值是必填字段,且必须为 true。

调用 subscribe() 会返回在以下步骤后解析的 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 记录到控制台中。按钮文字会更改为停用推送消息,您可以用页面底部的 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 应用授予了权限,因此您需要点击网址栏中的圆圈 ,并将通知权限更改为使用全局默认设置(询问)

更改此设置后,请刷新页面并点击启用推送消息按钮,然后在权限对话框中选择屏蔽。该按钮将被停用,并显示推送消息已被拦截字样。

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

已完成的代码

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

当您触发推送消息时,浏览器会收到推送消息,确定推送的 Service Worker,唤醒该 Service Worker,并分派推送事件。因此,您需要监听此事件并显示通知。

将以下代码添加到您的 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));
});

我们来了解一下此代码。您正在通过添加事件监听器来监听 Service Worker 中的 push 事件:

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

(除非您之前玩过 Web Workers,否则 self 可能是新内容。在 Service Worker 文件中,self 引用 Service Worker 本身。)

收到推送消息后,系统将调用事件监听器,并且您可以通过对 Service Worker 的 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,使浏览器能够让 Service Worker 保持运行和运行状态,直到传入的 promise 得到解析。

为了使上述代码更容易理解,您可以像下面这样重新编写:

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

现在,您已经完成了推送事件,接下来我们来测试推送事件。

试试看

通过 Service Worker 中的推送事件处理,您可以触发虚构的推送事件来测试收到消息时会发生什么情况。

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

点击推送后,您应该会看到如下通知:

注意:如果此步骤不起作用,请尝试使用 DevTools Application 面板中的 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() 可确保浏览器在显示新窗口或标签页之前不会终止 Service Worker。

试试看

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

您已发现您的 Web 应用能够使用 DevTools 显示通知,并查看了如何通过点击关闭通知。下一步是发送实际的推送消息。

通常,这需要从网页向后端发送订阅。然后,后端会通过对订阅中的端点进行 API 调用来触发推送消息。

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

然后,将复制的网站粘贴到订阅收件人文本区域中的配套网站中:

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

点击发送推送消息按钮。

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

这应该可让您测试发送和接收数据,并最终操作通知。

配套应用只是一个使用 web-push 库发送消息的节点服务器。有必要查看 GitHub 上的 web-push-libs org,了解哪些库能够向您发送推送消息。这会处理触发推送消息的许多细节。

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

已完成的代码

唯一缺少的功能是退订用户的推送功能。为此,您需要对 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 进行解析的 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 应用中按启用推送消息停用推送消息,日志中会显示用户正在订阅和退订的消息。

恭喜您完成此 Codelab!

此 Codelab 介绍了如何上手向 Web 应用添加推送通知。如果您想详细了解网络通知的功能,请查看这些文档

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

深入阅读

相关博文