ATENCIÓN Este post lo he actualizado y ahora tienes una biblioteca específica Open Source en Github para ayudarte a sacar partido a las notificaciones de los navegadores. Pulsa aquí para acceder.

Los navegadores son, cada vez más, los sistemas operativos modernos. A lo que me refiero es a que la mayoría de las aplicaciones que usamos en el día a día residen cada vez más en el navegador y menos en aplicaciones específicas en el escritorio. Por ello, desde HTML5 tenemos que poder hacer más cosas que tradicionalmente eran coto exclusivo de las aplicaciones de escritorio. Una de ellas es la posibilidad de enviar notificaciones ante ciertos eventos:

campusMVP_Notification_Chrome

Chrome

campusMVP_Notification_Firefox

Firefox

Gracias a la API de notificaciones de HTML5 es posible conseguirlo, aunque tiene sus detalles que debemos controlar.

Actualmente Internet Explorer es el único navegador que no soporta la API de notificaciones de HTML5, y ésta además no es un estándar cerrado desde octubre de 2015, por lo que puede variar de un momento a otro. Lo que voy a explicar a continuación funcionará bien en Google Chrome, Firefox, Safari y Opera en el escritorio, aunque solamente lo he probado en Chrome y Firefox.
Lo de Internet Explorer me parece increíble dado el partido que le podrían sacar para aplicaciones ancladas, pero de momento según su página de estatus, está simplemente “bajo consideración”:
IE_UnderConsideration

Lo estupendo de las notificaciones web es que se ven en el escritorio y las puedes mostrar en cualquier momento, aunque el navegador no esté en primer plano o esté minimizado. Si tu aplicación comprueba información en segundo plano con AJAX (como hace GMail con los nuevos correos) el usuario puede dejar el navegador minimizado y aún así recibirá notificaciones cuando haya algo nuevo que atender. Como vemos las posibilidades son muchas.

Toda la funcionalidad de las notificaciones web se encuentra en el objeto Notification que posee la ventana (u objeto global) del navegador. Vamos a ver lo que nos ofrece.

MUY IMPORTANTE: Los ejemplos solo funcionarán si los alojas en un servidor web. Si descargas el ejemplo y lo intentas ejecutar en el ordenador local no te funcionarán algunas de las cosas, como por ejemplo otorgar permiso, así que asegúrate de ponerlo en un servidor (vale uno local, por ejemplo con IISExpress) para probar lo que se explica en este artículo. Esto es algo que se aprende a base de desesperarse un poco, así que espero haberte ahorrado un poco de tiempo con el consejo ;-)

Soporte de notificaciones

Lo primero es identificar si nuestro navegador soporta o no la API de notificaciones. Para ello lo único que debemos hacer es comprobar si expone o no el objeto Notification:

function GetWebNotificationsSupported() {
    return (!!window.Notification);
}

De este modo podemos saber con una simple llamada a esta función si el navegador actual expone la API o no.

Pidiendo permiso

Lo primero que tenemos que hacer para poder mostrar notificaciones en el escritorio es pedir permiso al usuario. Sin este paso previo las notificaciones fallarán.

Para pedir permiso necesitamos usar la función requestPermission, a la que llamaremos con un código similar a este:

function AskForWebNotificationPermissions()
{
    if (Notification) {
        Notification.requestPermission();
    }
}

Lo que se hace es comprobar si el navegador soporta las notificaciones, en cuyo caso se llama a la función requestPermission sin parámetros.

El navegador muestra un barra en la parte superior para reclamar dicho permiso:

Chrome_Notifications_Permission

En el caso de Chrome, y:

Firefox_Notifications_Permission

En el caso de Firefox.

MUY IMPORTANTE: La función requestPermission sólo funcionará si es llamada como resultado de una acción directa del usuario, es decir, al pulsar un botón o un enlace. No podemos llamarla directamente desde cualquier sitio, por ejemplo al cargar la página. El motivo es evitar que páginas malintencionadas nos puedan estar incordiando todo el tiempo pidiéndonos este permiso para que al final se lo acabemos dando para que nos dejen tranquilo. Es algo parecido a lo que ocurre con la apertura de ventanas: si no queremos que el bloqueador de pop-ups las bloquee tenemos que hacerlo como consecuencia de la acción de un usuario. En el ejemplo descargable al final se hace desde un botón específico, como muestra la figura anterior, pero puede ser de cualquier otra manera. Por ejemplo GMail lo hace a través de unos botones de radio en la configuración:

PermisoGMail

Tú hazlo como quieras, pero siempre debes asegurarte de pedir ese permiso antes de intentar mostrar notificaciones nuevas.

Una versión más completa de esta función requestPermission toma como parámetro una función a la que se llamará asíncronamente cuando el usuario haya aceptado, denegado o evitado contestar a la pregunta de si permite o no recibir notificaciones. En ese caso la función de callback se ejecuta asíncronamente y recibe como parámetro una cadena que especifica uno de los tres siguientes valores:

  • "granted": si el usuario ha aceptado y por lo tanto hay permiso para enviar notificaciones.
  • "denied": si el usuario ha denegado la recepción de notificaciones.
  • "default": si no ha contestado la pregunta (cerrando el diálogo o, en el caso de Firefox, eligiendo “Ahora no”).

Gracias a este parámetro podemos recibir una notificación con el resultado de la pregunta y obrar en consecuencia.

Por ejemplo, podemos escribir un código similar a este:

Notification.requestPermission( function(status) {
    if (status == "granted")
         //Hacer algo
});

Lo que se hace es asignar como función de retorno una función anónima que recibe como parámetro el estado actual de permiso de notificación. Si el estado es “granted” es que el usuario ha aceptado recibir notificaciones y hacemos lo que tenga sentido hacer en nuestra aplicación en ese caso (lo mismo si lo hubiese denegado, claro).

De todos modos podemos averiguar en cualquier momento si tenemos permiso explícito del usuario para enviar notificaciones consultando la propiedad permission del objeto Notification. Ésta devuelve una cadena con alguno de los tres valores indicados antes. Así, averiguar si existe permiso o no para enviar notificaciones consiste en comprobar esa propiedad y compararla con alguno de los valores anteriores. Algo así como:

switch (Notification.permission) {
    case "granted":
        alert("¡Tenemos permiso para enviar notificaciones!");
    case "denied":
        alert("¡No tenemos permiso para enviar notificaciones!");
    default:
        alert("No se le ha preguntado al usuario, o éste no ha contestado.");
}

Denegar manualmente el permiso

Una cosa importante durante las pruebas de código es poder denegar de nuevo el permiso de enviar notificaciones, para así poder probar de nuevo a solicitarlo, a intentar enviar en caso de no tener permiso, etc... La forma de gestionar los permisos para notificaciones varía mucho de un navegador a otro.

En el caso de Chrome hay un icono en el área de notificación, al lado del reloj, con forma de campana que sirve para ver y gestionar las notificaciones. Si pulsamos sobre la rueda dentada de abajo a la derecha se nos muestra un listado con todos los sitios web que envían notificaciones en Chrome y podemos desmarcar aquellos que no nos interesen, en nuestro caso el correspondiente al servidor en el que estamos haciendo las pruebas:

Chrome-Disable-Notifications

En Firefox el método es diferente. Hay que pulsar con el botón derecho sobre cualquier parte libre de la página cuyas notificaciones queremos desactivar y elegir la opción “Page Info” (o “Información de página”) desde el menú contextual, y en la ventana que aparece, dentro de la pestaña de permisos, buscar los correspondientes a las notificaciones para desactivarlos:

Firefox-Disable-Notifications

De este modo podemos controlar a voluntad si recibimos o no notificaciones y así poder probar el código de la mejor manera posible.

Generando las notificaciones

Hasta ahora hemos visto cómo comprobar si tenemos permisos para enviar notificaciones o no, y cómo solicitárselo a los usuarios. Si tenemos permiso ya es hora de ver cómo podemos enviar las notificaciones, que es el objeto de todo esto.

Para ello lo único que hay que hacer es instanciar un objeto de tipo Notification, pasándole como mínimo el título de la notificación:

var notif = new Notification("Mi primera notificación!");

Aunque solo con esto se visualizará un mensaje muy simple. La verdadera definición de la notificación viene a través de su segundo parámetro opcional. En él podemos pasar un objeto anónimo que define una serie de propiedades que podemos utilizar para definir más el aspecto de la notificación, a saber:

  • body: esta propiedad define el contenido del mensaje a mostrar en la notificación, debajo del título. Conviene no pasarse demasiado con la longitud o se cortará a partir de cierto punto, poniendo puntos suspensivos. En cada navegador la longitud máxima es distinta y depende de cómo esté construida la cadena  de texto (donde se corte debido a los espacios, etc...).
  • icon: especifica la URL a una imagen que se usará para mostrar en un lateral de la notificación, como se ve en la figura del principio de este artículo. Un tamaño apropiado para la imagen podría ser, según mis pruebas, de unos 60x60 pixeles, pero puedes probar varios hasta dar con el que más encaje en tu caso concreto.
  • tag: un identificador opcional para la notificación que nos permite sustituirla enviando una notificación nueva con el mismo tag. Su principal utilidad es la de no notificar varias veces de lo mismo a los usuarios. Por ejemplo, si en un cliente de correo como GMail tenemos abierta una notificación con un evento sobre un mensaje concreto, si llegan más notificaciones relacionadas con ese mensaje, si usamos la misma tag sustituirán a las anteriores sin generar una nueva.
  • lang: el lenguaje en el que se está mostrando la notificación, en formato estándar BCP 47. Por ejemplo: “es”, “en-us” o “es-es”.
  • dir: define la dirección del texto a mostrar. Por defecto es “auto” que quiere decir que se usa la indicada por el lenguaje del navegador, pero podemos especificar explícitamente “ltr” (de izquierda a derecha, que es como leemos en occidente) o de “rtl” ( de derecha a izquierda, que es como leen por ejemplo los árabes).

Así, podemos enviar una notificación más completa, por ejemplo, con este código:

var options = {
    body: "Este es le cuerpo de la notificación",
    icon: "imgs/logoNotifs.png"
};
 
var notif = new Notification("Ejemplo de notificación", options);

En este caso solamente especificamos el mensaje y el icono a utilizar, que será el caso más común.

Cerrando notificaciones

El comportamiento de las notificaciones es diferente según el navegador:

  • En Firefox las notificaciones se cierran solas al cabo de unos pocos segundos. Además si se envían muchas seguidas no las trata de limitar en modo alguno: salen por la parte de arriba de la pantalla aunque queden fuera de la vista.
  • Chrome es más elegante en cuanto a esto último: en función de la resolución de la pantalla muestra más o menos notificaciones unas encima de las otras. En mi pantalla Full HD muestra un máximo de 4 notificaciones. Sin embargo lo que no hace es cerrarlas automáticamente al cabo de unos segundos. Mientras el usuario no las cierre o las pulse se quedan ahí, y podemos consultarlas todas desde el icono con la campanita que mencionamos anteriormente.

Para unificar criterios en cuanto a cuándo se cierran conviene que nuestro código se encargue de cerrarlas automáticamente al cabo de un tiempo máximo determinado. Para ello lo mejor es poner un temporizador que las cierre al cabo de un cierto número de milisegundos.

Si nos fijamos antes al mostrar una nueva notificación guardábamos una referencia a la misma en una variables (notif en los ejemplos). Esto es así precisamente para poder borrarla cuando queramos usando su método close:

var options = {
    body: "Este es le cuerpo de la notificación",
    icon: "imgs/logoNotifs.png"
};
 
var notif = new Notification("Ejemplo de notificación", options);
setTimeout(notif.close, 3000);

Ahora, tras haber creado la notificación, creamos un temporizador que llama a su método close al cabo de 3 segundos, de modo que si el usuario no la cierra se cierra sola. Si ya la ha cerrado el propio navegador o el usuario no pasa nada: el método close no fallará por el hecho de que la notificación ya no esté visible.

Eventos de notificaciones

La referencia a la notificación nos sirve también para poder asignarle manejadores de eventos y responder ante ciertas situaciones que ocurren con la notificación. Los cuatro eventos a los que podemos responder para una notificación son los siguientes:

  • show: este evento se llama en el momento en que se muestra la notificación. Esto no tiene porque ser en el momento en el que la instanciamos, ya que puede entrar en la cola y no mostrarse hasta que se cierren notificaciones anteriores.
  • click: se produce cuando el usuario pulsa sobre la notificación. Muy interesante para enviarlo a una vista concreta de la aplicación o a otra página.
  • close: se genera cuando la notificación se cierra, bien por código o bien por que lo hace el usuario.
  • error: se produce cuando ha habido errores al mostrar la notificación.

Para asignar manejadores a estos eventos podemos utilizar sus propiedades onshow, onclick, onclose y onerror o, mucho mejor aún, dado que Notification hereda de EventTarget, utilizar la función AddEventListener:

notif.addEventListener("show", Notification_OnEvent);
notif.addEventListener("click", Notification_OnEvent);
notif.addEventListener("close", Notification_OnEvent);

De esta manera podemos además obtener una referencia al evento en cuestión usando el objeto event que se pasa al manejador, por eso en este ejemplo puedo usar el mismo manejador para todos los eventos. Así, en el ejemplo descargable al final, vamos mostrando por pantalla un log de todas las notificaciones:

Notification_Log_Ejemplo_Eventos

Esto es muy potente pues nos permite conseguir que, por ejemplo, cuando el usuario pulse en una notificación lo enviemos a una vista concreta de nuestra aplicación.

Un wrapper para facilitarte la vida

He incluido como descarga de este artículo un archivo ZIP (4,94KB) con un ejemplo completo de uso de notificaciones. Para que funcione tienes que colocarlo en algún servidor web, incluso local, y debes probarlo con Chrome o Firefox (con Safari y Opera debería funcionar, pero con IE no funciona).

Dentro de la descarga hay un archivo WebNotifications.js que es un pequeño wrapper que he creado alrededor de la API estándar, pero pensando en poder adaptarlo a diferentes variaciones que pudieran surgir (por ejemplo, si IE u otro navegador lo lanzan con un prefijo propio, cosa que no sería la primera vez que ocurre).

Hay unas cuantas funciones, y todas tienen comentarios explicando para qué sirven, pero las importantes son:

  • getWebNotificationsSupported: devuelve true o false dependiendo de si se soporta o no la API de notificaciones.
  • getWebNotificationPermissionStatus: devuelve una constante de tres posibles (WEBNOTIF_PERMISSION_DEFAULT, WEBNOTIF_PERMISSION_DENIED, WEBNOTIF_PERMISSION_GRANTED), para indicar respectivamente si el usuario no ha contestado, ha denegado o ha permitido recibir notificaciones. Uso estas constantes para unificar criterios frente a las cadenas que usa la API por defecto, y que podrían cambiar o variar según el navegador.
  • askForWebNotificationPermissions: fuerza que el navegador pregunte al usuario si quiere recibir notificaciones de la página. OJO, recuerda que debe llamarse como parte de un acción del usuario (por ejemplo, pulsar un botón) o no funcionará.
  • showWebNotification: muestra una notificación. Se le pasa como parámetros el título, el contenido del mensaje, la URL de un gráfico para el icono, una etiqueta identificativa y un tiempo máximo para mostrarlo. Solo son obligatorios los dos primeros. El resto de pueden omitir.

Con esto es realmente fácil manejar las notificaciones, y si en el futuro lo implementan otros navegadores de forma diferente es fácil de actualizar para que sigan funcionando las páginas que lo usen sin tener que tocarlas, adaptando únicamente el wrapper.

¡Espero que te sea útil!

Puedes leer este post  en inglés en Medium.

Escrito por un humano, no por una IA