Aviso sobre cambios en las notificaciones

En primer lugar, pido disculpas por el título tan malo, pero no pude evitarlo.

En Chrome 44, se agregaron Notfication.data y ServiceWorkerRegistration.getNotifications(), que abren o simplifican algunos casos de uso comunes cuando se trata de notificaciones con mensajes push.

Datos de notificación

Notification.data te permite asociar un objeto JavaScript con una notificación.

En términos sencillos, cuando recibes un mensaje push, puedes crear una notificación con algunos datos y, luego, en el evento notificationclick, puedes recibir la notificación en la que se hizo clic y obtener sus datos.

Por ejemplo, crea un objeto de datos y agrégalo a tus opciones de notificaciones de la siguiente manera:

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

Significa que podemos obtener la información en el evento notificationclick:

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

Antes de esto, debías ocultar los datos en IndexDB o agregar algo al final de la URL del ícono.

ServiceWorkerRegistration.getNotifications()

Una solicitud común de los desarrolladores que trabajan en notificaciones push es tener un mejor control sobre las notificaciones que muestran.

Un ejemplo de caso de uso sería una aplicación de chat en la que un usuario envía varios mensajes y el destinatario muestra varias notificaciones. Idealmente, la app web debería detectar que tienes varias notificaciones que no se vieron y contraerlas en una sola.

Sin getNotifications(), lo mejor que puedes hacer es reemplazar la notificación anterior por el mensaje más reciente. Con getNotifications(), puedes "contraer" las notificaciones si ya se muestra una, lo que genera una experiencia del usuario mucho mejor.

Ejemplo de agrupación de notificaciones

El código para hacerlo es relativamente simple. Dentro de tu evento push, llama a ServiceWorkerRegistration.getNotifications() para obtener un array de notificaciones actuales y, desde allí, decide el comportamiento correcto, ya sea contraer todas las notificaciones o usar 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);
    }));
    }
});

Lo primero que debes destacar con este fragmento de código es que filtramos nuestras notificaciones pasando un objeto de filtro a getNotifications(). Esto significa que podemos obtener una lista de notificaciones para una etiqueta específica (en este ejemplo, para una conversación en particular).

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

Luego, revisamos las notificaciones visibles y verificamos si hay un recuento de notificaciones asociado con esa notificación y lo incrementamos en función de eso. De esta manera, si hay una notificación que le indica al usuario que hay dos mensajes no leídos, queremos señalar que hay tres mensajes no leídos cuando llega una notificación push nueva.

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

Una sutileza que debes destacar es que debes llamar a close() en la notificación para asegurarte de que se quite de la lista de notificaciones. Este es un error de Chrome, ya que cada notificación se reemplaza por la siguiente porque se usa la misma etiqueta. Por el momento, este reemplazo no se refleja en el array que se muestra de getNotifications().

Este es solo un ejemplo de getNotifications(), y, como puedes imaginar, esta API abre un abanico de otros casos de uso.

NotificationOptions.vibrate

A partir de Chrome 45, puedes especificar un patrón de vibración cuando creas una notificación. En los dispositivos que admiten la API de Vibration (actualmente, solo Chrome para Android), puedes personalizar el patrón de vibración que se usará cuando se muestre la notificación.

Un patrón de vibración puede ser un array de números o un solo número que se considera un array de un número. Los valores del array representan tiempos en milisegundos, en los que los índices pares (0, 2, 4, ...) indican durante cuánto tiempo se debe vibrar y los índices impares indican durante cuánto tiempo se debe pausar antes de la siguiente vibración.

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

Solicitudes de funciones comunes restantes

La única solicitud de función común que queda por parte de los desarrolladores es la capacidad de cerrar una notificación después de un período determinado o la capacidad de enviar una notificación push con el objetivo de cerrar una notificación si está visible.

Por el momento, no hay forma de hacerlo, y no hay nada en las especificaciones que lo permita :(, pero el equipo de Ingeniería de Chrome está al tanto de este caso de uso.

Notificaciones de Android

En computadoras, puedes crear una notificación con el siguiente código:

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

Esta función nunca se admitía en Android debido a las restricciones de la plataforma: específicamente, Chrome no admite las devoluciones de llamada en el objeto de notificación, como "onClick". Sin embargo, se usa en computadoras para mostrar notificaciones de apps web que podrías tener abiertas en ese momento.

La única razón por la que lo menciono es que, originalmente, una detección de funciones simple como la siguiente te ayudaría a admitir computadoras de escritorio y no causaría ningún error en Android:

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

Sin embargo, con la compatibilidad con notificaciones push ahora en Chrome para Android, las notificaciones se pueden crear desde un ServiceWorker, pero no desde una página web, lo que significa que este detector de funciones ya no es adecuado. Si intentas crear una notificación en Chrome para Android, recibirás el siguiente mensaje de error:

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

Por el momento, la mejor manera de detectar funciones para Android y computadoras de escritorio es hacer lo siguiente:

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

Se puede usar de la siguiente manera:

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