向 Web 应用添加推送通知

1. 概览

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

学习内容

  • 如何为用户订阅或退订推送消息
  • 如何处理传入的推送消息
  • 如何显示通知
  • 如何响应通知点击

所需条件

  • Chrome 52 或更高版本
  • Chrome 版 Web 服务器,或您自己选择的 Web 服务器
  • 文本编辑器
  • 具备 HTML、CSS、JavaScript 和 Chrome 开发者工具的基础知识
  • 示例代码(请参阅“设置”部分)。

2. 进行设置

下载示例代码

您可以通过以下两种方式获取此 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 应用后,点击书签栏上的应用快捷方式:

946bcaaad66e5c8e

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

9f3c21b2cf6cbfb5.png

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

73543edeb27c3d6f

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

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

5ac11bca86ce7369

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

d42f87972f9fec24.png

点击 Web 服务器 网址 以在 Web 浏览器中访问您的网站。您应该会看到如下页面,尽管您的版本显示的地址可能是 127.0.0.1:8887:

00-push-codelab.png

始终更新 Service Worker

在开发期间,确保 Service Worker 始终保持最新并拥有最新的更改很有帮助。

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

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

e7d384fb77885b99.png

3. 注册 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 文件。

试试看

刷新浏览器中的推送 Codelab 标签页,以检查您的更改。

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

5d7ad383d6f235d5

获取应用服务器密钥

为了处理此 Codelab,您需要生成应用服务器密钥。您可以在以下合作伙伴网站上完成此操作:web-push-codelab.glitch.me

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

push-codelab-04-companion.png

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

const applicationServerPublicKey = '<Your Public Key>';

重要提示:切勿将私钥放在您的 Web 应用中!

4. 初始化状态

已完成的代码

目前,该 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();
})

试试看

刷新推送 Codelab 标签页。您应该会看到 Enable Push Messaging 按钮已启用(您可以点击该按钮),并且应该会在控制台中看到 User is NOT subscribed

a1553f4a0483d227.png

随着您继续学习此 Codelab 的其余部分,无论何时订阅或退订,您应该都会看到按钮文本发生变化。

5. 订阅用户

已完成的代码

目前,Enable Push Messaging 按钮没有什么作用。让我们解决这个问题。

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');
  }
}

试试看

转到推送 Codelab 标签页,刷新页面,然后点击该按钮。您应该会看到如下所示的权限提示:

fcc61267a0194e81.png

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

5c5505f2ead037c

6. 处理权限请求被拒

已完成的代码

您尚未处理的一个问题是,如果用户阻止权限请求会发生什么情况。这需要特别注意,因为如果用户阻止权限,您的 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,然后将通知权限更改为使用全局默认设置(询问)

54495592074f10ae

更改此设置后,请刷新页面,点击 Enable Push Messaging 按钮,然后在权限对话框中选择 Block。该按钮将被停用,并显示文本 Push Messaging 已阻止

d4cf22468f6defda.png

进行此更改后,您现在可以订阅用户了,同时已考虑到可能的权限场景。

7. 处理推送事件

已完成的代码

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

当您触发推送消息时,浏览器将接收推送消息,确定推送针对的是哪个 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 Worker,否则 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。在开发者工具的 Application 面板中,点击 Service Workers 标签页下的 Push 按钮:

1ee499267eeccd1c

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

379105dfb0ea56d8

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

8. 点击通知

已完成的代码

如果您点击其中某条通知,就会发现没有任何反应。您可以通过监听 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。

试试看

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

9. 发送推送消息

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

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

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

bb202867962a0249.png

然后将以下内容粘贴到配套网站的 Subscription to Send To 文本区域:

a0dd93bc33a9e8cf.png

要发送的文本下,添加要通过推送消息发送的任何字符串。

点击发送推送消息按钮。

a5e8e89411ec034.png

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

f6815a356d4f9aaa.png

这样,您便有机会测试发送和接收数据,并最终操控通知。

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

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

10. 退订用户

已完成的代码

但缺少一项功能,那就是能让用户退订推送消息。为此,您需要对 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() 完成。您还可以添加一个捕获处理程序,以防调用 unsubscribe() 导致错误。之后,您便可以更新界面。

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

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

  updateBtn();
})

试试看

您应该能够在 Web 应用中按 Enable Push MessagingDisable Push Messaging,日志会显示用户已订阅和已退订。

81a07119235b53da

11. 已完成

恭喜您完成此 Codelab!

此 Codelab 向您展示了如何快速上手向 Web 应用添加推送通知。如果您想详细了解网络通知的功能,请参阅这些文档

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

深入阅读

相关博文