Уведомление вас об изменениях в уведомлениях

Во-первых, прошу прощения за такое ужасное название, но я не мог не сделать этого.

В Chrome 44 добавлены Notfication.data и ServiceWorkerRegistration.getNotifications(), которые открывают/упрощают некоторые распространенные случаи использования при работе с уведомлениями с помощью push-сообщений.

Данные уведомления

Notification.data позволяет связать объект JavaScript с уведомлением.

По сути, это сводится к тому, что когда вы получаете push-сообщение, вы можете создать уведомление с некоторыми данными, а затем в событии 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 или помещать что-то в конец URL-адреса значка — eek.

ServiceWorkerRegistration.getNotifications()

Одним из распространенных запросов разработчиков, работающих над push-уведомлениями, является лучший контроль над отображаемыми ими уведомлениями.

Примером использования может быть приложение чата, в котором пользователь отправляет несколько сообщений, а получатель отображает несколько уведомлений. В идеале веб-приложение могло бы заметить, что у вас есть несколько непросмотренных уведомлений, и свернуть их в одно уведомление.

Без getNotifications() лучшее, что вы можете сделать, — это заменить предыдущее уведомление последним сообщением. С помощью getNotifications() вы можете «свернуть» уведомления, если уведомление уже отображается, что значительно улучшит взаимодействие с пользователем.

Пример группировки уведомлений.

Код для этого относительно прост. Внутри вашего push-события вызовите 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)

Затем мы просматриваем видимые уведомления и проверяем, есть ли счетчик уведомлений, связанный с этим уведомлением, и увеличиваем его на основе этого. Таким образом, если есть одно уведомление, сообщающее пользователю о наличии двух непрочитанных сообщений, мы хотели бы указать, что при поступлении нового push-уведомления имеется три непрочитанных сообщения.

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, поскольку каждое уведомление заменяется следующим, поскольку используется тот же тег. На данный момент эта замена не отражается в массиве, возвращаемом функцией getNotifications() .

Это только один пример getNotifications(), и, как вы можете себе представить, этот API открывает ряд других вариантов использования.

NotificationOptions.vibrate

Начиная с Chrome 45, вы можете указать шаблон вибрации при создании уведомления. На устройствах, поддерживающих Vibration API (в настоящее время только Chrome для Android), это позволяет настроить шаблон вибрации, который будет использоваться при отображении уведомления.

Шаблон вибрации может представлять собой либо массив чисел, либо одно число, которое рассматривается как массив из одного числа. Значения в массиве представляют время в миллисекундах, при этом четные индексы (0, 2, 4,...) обозначают продолжительность вибрации, а нечетные индексы обозначают продолжительность паузы перед следующей вибрацией.

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

Остальные запросы на общие функции

Единственная оставшаяся общая просьба разработчиков — это возможность закрывать уведомление через определенный период времени или возможность отправлять push-уведомление с целью просто закрыть уведомление, если оно видимо.

На данный момент нет способа сделать это, и в спецификации нет ничего, что позволяло бы это :( но команда разработчиков Chrome знает об этом варианте использования.

Уведомления Android

На рабочем столе вы можете создать уведомление с помощью следующего кода:

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

Это никогда не поддерживалось на Android из-за ограничений платформы: в частности, Chrome не может поддерживать обратные вызовы для объекта Notification, такие как onclick. Но он используется на рабочем столе для отображения уведомлений о веб-приложениях, которые вы сейчас можете открыть.

Единственная причина, по которой я об этом упоминаю, заключается в том, что изначально простое обнаружение функции, подобное приведенному ниже, поможет вам поддерживать настольные компьютеры и не вызывать ошибок на Android:

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

Однако благодаря поддержке push-уведомлений в Chrome для Android уведомления можно создавать из ServiceWorker, но не с веб-страницы, а это означает, что эта функция обнаружения больше не подходит. Если вы попытаетесь создать уведомление в Chrome для Android, вы получите следующее сообщение об ошибке:

_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();
    }