Notificar você sobre alterações nas notificações

Em primeiro lugar, peço desculpas por esse título horrível, mas não consegui.

No Chrome 44, foram adicionados Notfication.data e ServiceWorkerRegistration.getNotifications() e abrem / simplificam alguns casos de uso comuns ao lidar com notificações com mensagens push.

Dados da notificação

Notification.data permite associar um objeto JavaScript a uma notificação.

Isso basicamente se resume a: quando você recebe uma mensagem push, pode criar uma notificação com alguns dados e, no evento "notificationclick", receber a notificação que foi clicada e consultar os dados.

Por exemplo, criar um objeto de dados e adicioná-lo às opções de notificação da seguinte maneira:

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

Isso significa que podemos receber as informações no evento notificationclick:

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

Antes disso, você tinha que armazenar dados no IndexDB ou colocar algo no final do URL do ícone.

ServiceWorkerRegistration.getNotifications()

Uma solicitação comum de desenvolvedores que trabalham com notificações push é ter mais controle sobre as notificações que eles exibem.

Um exemplo de caso de uso seria um aplicativo de chat em que um usuário envia várias mensagens e o destinatário exibe várias notificações. O ideal é que o app da Web possa perceber que você tem várias notificações que não foram visualizadas e recolhê-las em uma única notificação.

Sem o getNotifications(), o melhor que você pode fazer é substituir a notificação anterior pela mensagem mais recente. Com o getNotifications(), você pode "recolher" as notificações se uma delas já estiver sendo mostrada, levando a uma experiência muito melhor para o usuário.

Exemplo de agrupamento de notificações.

O código para fazer isso é relativamente simples. Dentro do evento push, chame ServiceWorkerRegistration.getNotifications() para receber uma matriz de notificações atuais e, a partir daí, decida o comportamento certo, seja para recolher todas as notificações ou usar a 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);
    }));
    }
});

A primeira coisa a destacar com esse snippet de código é que filtramos nossas notificações transmitindo um objeto de filtro para getNotifications(). Isso significa que podemos receber uma lista de notificações para uma tag específica (neste exemplo, para uma conversa específica).

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

Em seguida, analisamos as notificações que estão visíveis e verificamos se há uma contagem de notificações associada a elas e incrementamos com base nela. Dessa forma, se houver uma notificação informando ao usuário que há duas mensagens não lidas, indicaremos que há três mensagens não lidas quando um novo push chega.

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

Um destaque sutil é que você precisa chamar close() na notificação para garantir que ela seja removida da lista. Esse é um bug no Chrome, já que cada notificação é substituída pela próxima porque a mesma tag é usada. No momento, essa substituição não está sendo refletida na matriz retornada de getNotifications().

Esse é apenas um exemplo de getNotifications() e, como você pode imaginar, essa API abre uma série de outros casos de uso.

NotificationOptions.vibrate

A partir do Chrome 45, você pode especificar um padrão de vibração ao criar uma notificação. Em dispositivos com suporte à API Vibration (atualmente apenas o Chrome para Android), é possível personalizar o padrão de vibração que será usado quando a notificação for exibida.

Um padrão de vibração pode ser uma matriz de números ou um único número que é tratado como uma matriz de um número. Os valores na matriz representam os tempos em milissegundos. Os índices pares (0, 2, 4, ...) representam o tempo de vibração, e os índices ímpares indicam o tempo de pausa antes da próxima vibração.

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

Outras solicitações de recursos comuns

Uma solicitação de recurso comum restante dos desenvolvedores é a capacidade de fechar uma notificação após um determinado período ou de enviar uma notificação push com o objetivo de apenas fechar uma notificação se ela estiver visível.

No momento, não é possível fazer isso e nada na especificação que permita isso :( mas a equipe de engenharia do Chrome está ciente desse caso de uso.

Notificações do Android

No computador, você pode criar uma notificação com o seguinte código:

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

Nunca houve suporte para isso no Android devido a restrições da plataforma. Especificamente, o Chrome não oferece suporte aos callbacks no objeto de notificação, como a função "onclick". No entanto, ele é usado na área de trabalho para exibir notificações de apps da Web que você pode ter abertos no momento.

O único motivo pelo qual mencionei é que, originalmente, uma detecção de recurso simples como a abaixo ajudaria você a oferecer suporte a computadores e não causaria erros no Android:

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

No entanto, com a compatibilidade com notificações push agora no Chrome para Android, as notificações podem ser criadas a partir de um ServiceWorker, mas não em uma página da Web, o que significa que essa detecção de recurso não é mais apropriada. Se você tentar criar uma notificação no Chrome para Android, receberá esta mensagem de erro:

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

No momento, a melhor maneira de detectar recursos para Android e computadores é fazendo o seguinte:

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

Isso pode ser usado da seguinte forma:

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