我们将了解 Web 推送的一些常见实现模式。
这将涉及使用 Service Worker 中提供的一些不同 API。
通知关闭事件
在上一部分中,我们了解了如何监听 notificationclick
事件。
此外,如果用户关闭您的其中一个应用,则会调用 notificationclose
事件。
通知(即用户没有点击通知,而是点击叉号或滑动
通知)。
此事件通常用于分析,以跟踪用户与通知的互动情况。
self.addEventListener('notificationclose', function (event) {
const dismissedNotification = event.notification;
const promiseChain = notificationCloseAnalytics();
event.waitUntil(promiseChain);
});
向通知添加数据
收到推送消息时,通常只有 在用户已点击通知时有用。例如,网址 用户点击通知时应打开的广告素材。
从推送事件获取数据并将其附加到
通知是向传递的 options 对象添加 data
参数
复制到 showNotification()
中,如下所示:
const options = {
body:
'This notification has data attached to it that is printed ' +
"to the console when it's clicked.",
tag: 'data-notification',
data: {
time: new Date(Date.now()).toString(),
message: 'Hello, World!',
},
};
registration.showNotification('Notification with Data', options);
在点击处理程序中,可以使用 event.notification.data
访问数据。
const notificationData = event.notification.data;
console.log('');
console.log('The notification data has the following parameters:');
Object.keys(notificationData).forEach((key) => {
console.log(` ${key}: ${notificationData[key]}`);
});
console.log('');
打开窗口
对通知的最常见响应之一是打开
按 Tab 键即可切换到特定网址。我们可以使用
clients.openWindow()
API。
在 notificationclick
事件中,我们会运行如下一些代码:
const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);
在下一节中,我们将了解如何检查要将用户引导至的网页是否为 是否已打开。这样,我们就可以聚焦于打开的标签页,而不是打开新的标签页 标签页。
将焦点置于现有窗口
如果可能的话,我们应该聚焦一个窗口,而不是每次用户都打开新窗口 点击通知。
在我们介绍如何实现这一目标之前,有必要强调 只对您源中的网页有效。这是因为我们可以 只能查看打开的属于我们网站的网页。这可防止 使开发者无法查看用户正在浏览的所有网站。
以前面为例,我们要修改代码,看看
/demos/notification-examples/example-page.html
已经打开。
const urlToOpen = new URL(examplePage, self.location.origin).href;
const promiseChain = clients
.matchAll({
type: 'window',
includeUncontrolled: true,
})
.then((windowClients) => {
let matchingClient = null;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
return clients.openWindow(urlToOpen);
}
});
event.waitUntil(promiseChain);
下面我们单步调试一下代码。
首先,我们使用 网址 API 解析示例网页。这个技巧是我从小杰那里学到的
Posnick。使用 location
对象调用 new URL()
:
如果传入的字符串是相对网址,则返回绝对网址(即 /
将变为
https://example.com/
)。
我们将该网址设为绝对网址,以便稍后将其与窗口网址进行匹配。
const urlToOpen = new URL(examplePage, self.location.origin).href;
然后获取 WindowClient
对象的列表,也就是
当前打开的标签页和窗口。(请注意,这些标签页仅适用于您的源)。
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true,
});
传递到 matchAll
的选项会告知浏览器我们只想要
搜索“window”键入客户端(即查找标签页和窗口,
和排除 Web Worker)。includeUncontrolled
可让我们搜索
您的源中不受当前服务控制的所有标签页
工作器,即运行此代码的 Service Worker。一般来说,您需要
在调用 matchAll()
时始终希望 includeUncontrolled
为 true。
我们将返回的 promise 捕获为 promiseChain
,以便将其传入
event.waitUntil()
,让 Service Worker 保持活跃状态。
当 matchAll()
promise 进行解析时,我们会遍历返回的窗口客户端,并
将其网址与我们要打开的网址进行比较如果找到匹配内容,我们会重点关注
这样便可吸引用户注意该窗口使用
matchingClient.focus()
通话。
如果找不到匹配的客户,我们将打开一个新窗口,这与上一部分相同。
.then((windowClients) => {
let matchingClient = null;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
return clients.openWindow(urlToOpen);
}
});
合并通知
我们发现,向通知添加标记会导致系统出现 会替换具有相同标记的现有通知。
不过,您可以使用 Notifications API。以一款聊天应用为例,开发者可能希望在该应用中收到新通知, 显示类似于“你收到了两封邮件,来自马特”的邮件而不只是显示 消息。
为此,您可以使用 registration.getNotifications() 通过 API,您可以访问您的 Web 应用当前显示的所有通知。
我们来看看如何使用此 API 来实现聊天示例。
在我们的聊天应用中,我们假设每条通知都有一些数据,其中包括用户名。
我们首先要做的是查找具有特定
用户名。我们将获取 registration.getNotifications()
,并循环遍历这些函数,然后检查
notification.data
:
const promiseChain = registration.getNotifications().then((notifications) => {
let currentNotification;
for (let i = 0; i < notifications.length; i++) {
if (notifications[i].data && notifications[i].data.userName === userName) {
currentNotification = notifications[i];
}
}
return currentNotification;
});
下一步是用新通知替换此通知。
在此虚假消息应用中,我们将通过向新消息中添加一个计数来跟踪新消息的数量 并在每次有新通知时递增该数据。
.then((currentNotification) => {
let notificationTitle;
const options = {
icon: userIcon,
}
if (currentNotification) {
// We have an open notification, let's do something with it.
const messageCount = currentNotification.data.newMessageCount + 1;
options.body = `You have ${messageCount} new messages from ${userName}.`;
options.data = {
userName: userName,
newMessageCount: messageCount
};
notificationTitle = `New Messages from ${userName}`;
// Remember to close the old notification.
currentNotification.close();
} else {
options.body = `"${userMessage}"`;
options.data = {
userName: userName,
newMessageCount: 1
};
notificationTitle = `New Message from ${userName}`;
}
return registration.showNotification(
notificationTitle,
options
);
});
如果当前显示了一条通知,我们将递增消息计数并将
相应通知标题和正文消息。如果有
没有通知,我们会创建一个新的通知,并将 newMessageCount
设为 1。
结果是第一条消息将如下所示:
第二个通知会将通知收起到以下代码中:
这种方法的好处在于,如果您的用户目睹 逐一显示通知,这样看起来 和感觉更连贯 而不是直接用最新消息替换通知
规则的例外情况
我一直说,当您收到推送时,必须显示一条通知, 在大多数情况下都是 true。不必显示通知的情况是 当用户打开您的网站并聚焦于其内容时
在推送事件中,您可以检查是否需要显示通知,方法是 检查窗口客户端并寻找聚焦的窗口。
获取所有窗口并查找聚焦窗口的代码如下所示:
function isClientFocused() {
return clients
.matchAll({
type: 'window',
includeUncontrolled: true,
})
.then((windowClients) => {
let clientIsFocused = false;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.focused) {
clientIsFocused = true;
break;
}
}
return clientIsFocused;
});
}
我们使用 clients.matchAll()
获取所有窗口客户端,然后对它们进行循环检查,检查 focused
参数。
在推送事件内,我们将使用此函数来确定是否需要显示通知:
const promiseChain = isClientFocused().then((clientIsFocused) => {
if (clientIsFocused) {
console.log("Don't need to show a notification.");
return;
}
// Client isn't focused, we need to show a notification.
return self.registration.showNotification('Had to show a notification.');
});
event.waitUntil(promiseChain);
通过推送事件向网页发送消息
我们发现,如果用户当前在浏览您的网站,您可以跳过通知显示。但是 如果您仍想通知用户已发生了事件,但又收到一条通知,该怎么办? 习惯用手太重?
一种方法是从 Service Worker 向页面发送消息,这样网页 可以向用户显示通知或更新,以告知用户所发生的事件。这对于 。
假设我们收到了推送,检查了我们的 Web 应用当前处于聚焦状态, 那么我们可以“发布消息”附加到每个打开的网页,如下所示:
const promiseChain = isClientFocused().then((clientIsFocused) => {
if (clientIsFocused) {
windowClients.forEach((windowClient) => {
windowClient.postMessage({
message: 'Received a push message.',
time: new Date().toString(),
});
});
} else {
return self.registration.showNotification('No focused windows', {
body: 'Had to show a notification instead of messaging each page.',
});
}
});
event.waitUntil(promiseChain);
在每个页面中,我们都通过添加消息事件来监听消息 监听器:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('Received a message from service worker: ', event.data);
});
在此消息监听器中,您可以执行任何想要的操作, 或完全忽略该消息。
另外值得注意的是,如果您没有在网页中定义消息监听器, 则来自 Service Worker 的消息不会执行任何操作。
缓存网页并打开窗口
有一种情况不在本指南的讨论范围内,但值得讨论: 通过缓存您希望用户在访问之后访问的网页,提升 Web 应用的整体用户体验 点击通知
这需要设置您的 Service Worker 以处理 fetch
事件,
但如果您实现了fetch
事件监听器
通过缓存网页和素材资源,在 push
事件中加以利用
您需要的信息。
浏览器兼容性
notificationclose
事件
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
Clients.openWindow()
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
ServiceWorkerRegistration.getNotifications()
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
clients.matchAll()
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
有关详情,请查看Service Worker 简介 帖子。