在通知有变化时通知您

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()

开发者在使用推送通知时,常常会要求更好地控制所显示的通知。

一个示例用例是聊天应用,在该应用中,用户发送多条消息,接收方显示多条通知。理想情况下,网络应用能够注意到您有多个未查看的通知,并将它们合并到一条通知中。

如果不使用 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(目前仅限 Chrome for Android)的设备上,您可以自定义显示通知时使用的振动模式。

振动模式可以是数字数组,也可以是单个数字(被视为一个数字的数组)。数组中的值表示以毫秒为单位的时间,其中偶数索引 (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 不支持对 Notification 对象的回调,例如 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();
    }
的最佳实践