Aviso sobre cambios en las notificaciones

En primer lugar, te pido disculpas por ese terrible título, pero no pude.

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

Datos de notificación

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

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

Por ejemplo, puedes crear un objeto de datos y agregarlo a tus opciones de notificación 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
    })
    );
});

Esto 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, tenías que almacenar datos en IndexDB o colocar algo al final de la URL del ícono... ¡bueno!

ServiceWorkerRegistration.getNotifications()

Una solicitud común de los desarrolladores que trabajan con 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 podría detectar que tienes varias notificaciones sin ver y contraerlas en una sola.

Sin getNotifications(), lo mejor que puedes hacer es reemplazar la notificación anterior con el último mensaje. Con getNotifications(), puedes “contraer” las notificaciones si ya se muestran, lo que permite una experiencia del usuario mucho mejor.

Ejemplo de cómo agrupar notificaciones.

El código para hacerlo es relativamente simple. Dentro del evento push, llama a ServiceWorkerRegistration.getNotifications() para obtener un array de las notificaciones actuales y, a partir de ahí, 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 debemos destacar con este fragmento de código es que filtramos las 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 que están visibles y verificamos si existe un recuento de notificaciones asociado a esa notificación y aumentamos según eso. De esta manera, si hay una notificación que le dice al usuario que hay dos mensajes no leídos, debemos señalar que hay tres mensajes no leídos cuando llega un nuevo envío.

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

Un detalle 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 en 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 arreglo que se muestra de getNotifications().

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

NotificationOptions.vibrate

A partir de Chrome 45, puedes especificar un patrón de vibración al crear una notificación. En dispositivos compatibles con la API de Vibration (actualmente solo Chrome para Android), esto te permite 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 trata como un array de un número. Los valores del array representan los tiempos en milisegundos. Los índices pares (0, 2, 4, ...) representan el tiempo de vibración y los índices impares son el tiempo de pausa 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 restante 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 fin de cerrar una notificación si es visible.

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

Notificaciones de Android

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

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

Nunca se admitió en Android debido a las restricciones de la plataforma: 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 quizás tengas abiertas en ese momento.

El único motivo por el que menciono es que, en un principio, una detección de funciones simple como la que se muestra a continuación ayudaría a brindar compatibilidad con computadoras de escritorio y no causaría ningún error en Android:

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

Sin embargo, como las notificaciones push ahora se encuentran disponibles en Chrome para Android, las notificaciones se pueden crear desde un ServiceWorker, pero no desde una página web, lo que significa que esta función de detección ya no es adecuada. Si intentas crear una notificación en Chrome para Android, recibirás este 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;
    }

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