在通知有变化时通知您

Matt Gaunt

首先,对于这个糟糕的标题,我深表歉意,但我做不到。

在 Chrome 44 中,我们添加了 Notfication.dataServiceWorkerRegistration.getNotifications(),并在处理包含推送消息的通知时开放 / 简化了一些常见用例。

通知数据

Notification.data 允许您将 JavaScript 对象与通知相关联。

简单来说,当您收到推送消息时,您可以创建包含一些数据的通知,然后在 notificationclick 事件中,您可以获取用户点击的通知并获取其数据。

例如,创建一个数据对象并将其添加到通知选项中,如下所示:

self.addEventListener('push', function(event) {
    console.log('Received a push message', event);

    var title = 'Yay a message.';
    var body = 'We have received a push message.';
    var icon = '/images/icon-192x192.png';
    var tag = 'simple-push-demo-notification-tag';
    var data = {
    doge: {
        wow: 'such amaze notification data'
    }
    };

    event.waitUntil(
    self.registration.showNotification(title, {
        body: body,
        icon: icon,
        tag: tag,
        data: data
    })
    );
});

这意味着我们可以获取 notificationclick 事件中的信息:

self.addEventListener('notificationclick', function(event) {
    var doge = event.notification.data.doge;
    console.log(doge.wow);
});

在此之前,您必须将数据存储在 IndexDB 中,或在图标网址末尾添加内容(例如 eek)。

ServiceWorkerRegistration.getNotifications()

开发推送通知的开发者的一项常见请求是更好地控制它们显示的通知。

一个示例用例是用户发送多条消息,而接收者显示多条通知的聊天应用。理想情况下,Web 应用能够注意到您有多个尚未查看的通知,并将它们折叠为一个通知。

如果不使用 getNotifications(),最好的方法是将旧通知替换为最新消息。借助 getNotifications(),您可以在已显示通知的情况下“折叠”通知,从而提供更好的用户体验。

将通知分组的示例。

执行此操作的代码相对简单。在推送事件内,调用 ServiceWorkerRegistration.getNotifications() 以获取当前通知的数组,然后从该数组中确定正确的行为,是收起所有通知还是使用 Notification.tag。

function showNotification(title, body, icon, data) {
    var notificationOptions = {
    body: body,
    icon: icon ? icon : 'images/touch/chrome-touch-icon-192x192.png',
    tag: 'simple-push-demo-notification',
    data: data
    };

    self.registration.showNotification(title, notificationOptions);
    return;
}

self.addEventListener('push', function(event) {
    console.log('Received a push message', event);

    // Since this is no payload data with the first version
    // of Push notifications, here we'll grab some data from
    // an API and use it to populate a notification
    event.waitUntil(
    fetch(API_ENDPOINT).then(function(response) {
        if (response.status !== 200) {
        console.log('Looks like there was a problem. Status Code: ' +
            response.status);
        // Throw an error so the promise is rejected and catch() is executed
        throw new Error();
        }

        // Examine the text in the response
        return response.json().then(function(data) {
        var title = 'You have a new message';
        var message = data.message;
        var icon = 'images/notification-icon.png';
        var notificationTag = 'chat-message';

        var notificationFilter = {
            tag: notificationTag
        };
        return self.registration.getNotifications(notificationFilter)
            .then(function(notifications) {
            if (notifications && notifications.length > 0) {
                // Start with one to account for the new notification
                // we are adding
                var notificationCount = 1;
                for (var i = 0; i < notifications.length; i++) {
                var existingNotification = notifications[i];
                if (existingNotification.data &&
                    existingNotification.data.notificationCount) {
                    notificationCount +=
existingNotification.data.notificationCount;
                } else {
                    notificationCount++;
                }
                existingNotification.close();
                }
                message = 'You have ' + notificationCount +
                ' weather updates.';
                notificationData.notificationCount = notificationCount;
            }

            return showNotification(title, message, icon, notificationData);
            });
        });
    }).catch(function(err) {
        console.error('Unable to retrieve data', err);

        var title = 'An error occurred';
        var message = 'We were unable to get the information for this ' +
        'push message';

        return showNotification(title, message);
    })
    );
});

self.addEventListener('notificationclick', function(event) {
    console.log('On notification click: ', event);

    if (Notification.prototype.hasOwnProperty('data')) {
    console.log('Using Data');
    var url = event.notification.data.url;
    event.waitUntil(clients.openWindow(url));
    } else {
    event.waitUntil(getIdb().get(KEY_VALUE_STORE_NAME,
event.notification.tag).then(function(url) {
        // At the moment you cannot open third party URL's, a simple trick
        // is to redirect to the desired URL from a URL on your domain
        var redirectUrl = '/redirect.html?redirect=' +
        url;
        return clients.openWindow(redirectUrl);
    }));
    }
});

此代码段首先要强调的是,我们通过向 getNotifications() 传递过滤条件对象来过滤通知。这意味着我们可以获取特定标记的通知列表(在此示例中,该名单针对特定对话)。

var notificationFilter = {
    tag: notificationTag
};
return self.registration.getNotifications(notificationFilter)

然后,我们会查看可见的通知,并检查是否存在与该通知关联的通知计数,并根据该计数递增。这样一来,如果有一条通知告知用户有两条未读消息,我们需要指出,当收到新的推送时,有三条未读消息。

var notificationCount = 1;
for (var i = 0; i < notifications.length; i++) {
    var existingNotification = notifications[i];
    if (existingNotification.data && existingNotification.data.notificationCount) {
    notificationCount += existingNotification.data.notificationCount;
    } else {
    notificationCount++;
    }
    existingNotification.close();
}

需要重点强调的是,您需要对通知调用 close(),以确保从通知列表中移除该通知。这是 Chrome 中的一个 bug,因为由于使用了相同的标记,每条通知都会被下一条通知所取代。目前,此替换未反映在从 getNotifications() 返回的数组中。

这只是 getNotifications() 的一个示例,您可以想象到,此 API 打开了一系列其他用例。

NotificationOptions.vibrate

从 Chrome 45 开始,您可以在创建通知时指定振动模式。在支持 Vibration API 的设备(目前仅适用于 Android 版 Chrome)上,您可以自定义在显示通知时使用的振动模式。

振动模式可以是数字数组,也可以是被视为由一个数字组成的数组的单个数字。该数组中的值表示以毫秒计的时间,偶数索引(0、2、4 ...)表示振动的时长,奇数索引表示下次振动前暂停的时间。

self.registration.showNotification('Buzz!', {
    body: 'Bzzz bzzzz',
    vibrate: [300, 100, 400] // Vibrate 300ms, pause 100ms, then vibrate 400ms
});

其他通用功能请求

开发者还有一项常见的功能请求是,能够在特定时间段后关闭通知,或者能够发送推送通知,只是为了关闭可见的通知。

目前您无法执行此操作,规范中也没有任何内容支持此操作 :( 但 Chrome 工程团队了解此用例。

Android 通知

在桌面设备上,您可以使用以下代码创建通知:

new Notification('Hello', {body: 'Yay!'});

由于平台限制,Android 从不支持此功能:具体而言,Chrome 不支持对通知对象的回调,例如 onClick。但它可以在桌面设备上用于显示您当前可能打开的 Web 应用的通知。

我之所以提到它,是因为最初,如下所示的简单功能检测可以帮助您支持桌面设备,并且不会在 Android 上导致任何错误:

if (!'Notification' in window) {
    // Notifications aren't supported
    return;
}

不过,借助 Android 版 Chrome 现在支持推送通知,可以通过 ServiceWorker 创建通知,但不能通过网页创建通知,这意味着此功能检测不再适用。如果您尝试在 Android 版 Chrome 中创建通知,则会收到以下错误消息:

_Uncaught TypeError: Failed to construct 'Notification': Illegal constructor.
Use ServiceWorkerRegistration.showNotification() instead_

目前,针对 Android 和桌面设备进行功能检测的最佳方法是执行以下操作:

    function isNewNotificationSupported() {
        if (!window.Notification || !Notification.requestPermission)
            return false;
        if (Notification.permission == 'granted')
            throw new Error('You must only call this \*before\* calling
    Notification.requestPermission(), otherwise this feature detect would bug the
    user with an actual notification!');
        try {
            new Notification('');
        } catch (e) {
            if (e.name == 'TypeError')
                return false;
        }
        return true;
    }

其使用方式如下:

    if (window.Notification && Notification.permission == 'granted') {
        // We would only have prompted the user for permission if new
        // Notification was supported (see below), so assume it is supported.
        doStuffThatUsesNewNotification();
    } else if (isNewNotificationSupported()) {
        // new Notification is supported, so prompt the user for permission.
        showOptInUIForNotifications();
    }