Crear notificaciones web push con NodeJs, una nueva forma de contactar a tus clientes

koaJS mar. 24, 2020

Durante mucho tiempo el √ļnico enfoque para realizar un contacto directo con nuestros usuarios es solicitar un email, el cual sirve para enviar cualquier tipo de informaci√≥n y recordarles nuestra existencia, sin embargo en estos √ļltimos a√Īos tenemos una soluci√≥n alternativa y eficaz en t√©rminos de fidelizaci√≥n siempre y cuando quien reciba las notificaciones sea el mismo usuario que se suscribe a este mecanismo, nos referimos a las notificaciones push, que si bien son muy conocidas en las aplicaciones m√≥viles, es posible realizar ese mismo funcionamiento desde un navegador, pero en este lado se les conoce como web push notificacions, ¬°vamos hacia a lo bueno!

¬ŅNotificaciones push para web?

Sí claro, está apoyado por los trabajadores de servicio (service workers), que hacen que tengamos una especie de listeners en background, que pueden ejecutar eventos incluso si el navegador esta cerrado. Ahora, todo el flujo no sería posible si no tuviéramos a la mano dos APIs de la web, Push y Notification, sabemos que esta tecnología no es nueva y sólo por si acaso no las has visto (o eres nuevo o eres despistado) un web push notification tiene ésta apariencia:

Ejemplo de web push notification

Ventajas de notificaciones web push

  • Crear lealtad: Puedes aprovechar para establecer una comunicaci√≥n cada vez que tengas una actualizaci√≥n, notificar a suscriptores como van los pedidos en caso de una tienda online, tus clientes te recordar√°n y regresar√°n a tu web.
  • Espera mientras el usuario est√° offline: Es decir, cuando tu env√≠as la notificaci√≥n, esta se almacena en una cola, hasta que el dispositivo se conecte a internet y pueda enviarse la notificaci√≥n.
  • Visibilidad: Este tipo de comunicaci√≥n es dif√≠cil de pasar desapercibidas, cuando llegan se superponen en cualquier dispositivo, lo que hace que tenga una interesante tasa de clicks(CTR).
  • Gran compatibilidad: Los navegadores m√°s populares soportan √©sta caracter√≠stica, Google, Chrome, Opera, Firefox son algunos, m√°s adelante mostramos unos datos interesantes.
  • Mensajes directos: Al ser mensajes cortos, estos deben contener informaci√≥n precisa, es decir sin rodeos.
  • Bajo coste: Comparado con otros canales de comunicaci√≥n, esta es una forma muy barata de llegar a los usuarios.
  • No hay SPAM: Si te dieron permisos para notificar, el mensaje llegar√° a tu p√ļblico si o si, no hay la clasificaci√≥n de SPAM como lo hacen en los emails.
  • Subscripci√≥n r√°pida: No hay que llenar nada, y menos pedir informaci√≥n, un solo click basta para tener los permisos que permitir√°n enviar las notificaciones web push.

Como funcionan las notificaciones web push

Ahora, el proceso para crear notificaciones web push es sencillo, y lo puedes tener ya mismo en un sitio web, ya sea tu propio sitio web o de tus clientes, bien, no queremos realizar un gráfico que te hará confundir de manera innecesaria, es por eso que podemos resumirlo en tres pasos importantes: el lado del cliente, envío de web push desde el servidor e inserción de la notificación en el dispositivo:

El lado del cliente

Lado del cliente. Fuente: developers.google.com
  • Primero: Al ingresar a la p√°gina, se solicitan los permisos para mostrar notificaciones.
  • Segundo: Se genera una suscripci√≥n push, que no es m√°s que un objeto con informaci√≥n importante.
  • Tercero: Enviamos la suscripci√≥n push hacia el servidor para que sea almacenado en alguna fuente de datos.

Envío de web push desde el servidor

Envio web push desde el servidor. Fuente: developers.google.com
  • Primero: El servidor env√≠a una notificaci√≥n a trav√©s del protocolo web push.
  • Segundo: El servicio push recibe la llamada hacia la API.
  • Tercero: El servicio push se encarga de ponerlo en cola hasta que el dispositivo se conecte.

Inserción de la notificación en el dispositivo

Inserción de notificación en dispositivo. Fuente: developers.google.com
  • Primero: El dispositivo se conecta a internet y recibe el evento de notificaci√≥n si es que no ha expirado.
  • Segundo: El navegador descifra el mensaje solo si es necesario y "despierta" al service worker.
  • Tercero: El service worker recibe el evento de notificaci√≥n y ejecuta cualquier tarea en segundo plano, es decir background.

Compatibilidad

Para Push API, ciertamente no estamos en un 100% de compatibilidad, que sin duda lo m√°s raro es que Safari en ninguna de sus versiones lo soporta a√ļn, vamos Apple ponte las pilas, de todas formas, estoy seguro de que pronto se va a sumar al 78.04% de navegadores web que si lo hacen completamente o m√≠nimo al 0.54% que lo soportan parcialmente, aqu√≠ Can I Use nos da un panorama completo de la compatibilidad que hay actualmente con esta API.

Push API. Fuente: Can I Use

Por otra parte Web Notifications tiene un poco menos de compatibilidad en general, para ser exactos 76.94% completamente y 0.5% parcialmente, pero el punto calve aquí es que la gran baja (por ahora) es el navegador Safari, quien se encuentra obligatoriamente en dispositivos iOS y MacOS, se prive de esta genial característica, el siguiente es un vistazo completo que nos ofrece nuevamente Can I Use.

Web Notifications. Fuente: Can I Use

Manos a la obra

Para implementar notificaciones web push, primero vamos a realizar un proyecto javascript empezando desde el backend con nodejs, ubícate en el lugar de tu preferencia y empezamos a crear el package.json.

npm init

y empezará una ronda de preguntas, que en realidad no es el propósito del post, así que simplemente dale enter a todo lo que se ponga en frente.

Para nuestro ejemplo es necesario instalar las siguientes dependencias, personalmente prefiero agregarlas a mano, para tener claro cuales versiones son la que utiliza el proyecto.

Dependencias necesarias para el proyecto

Bien, ahora vamos a volcar estas dependencias en el archivo index.js en la raíz del proyecto.

const Koa = require('koa');
const router = require('koa-router');
const body = require('koa-body');
const Pug = require('koa-pug');
const serve = require('koa-static');
const webpush = require('web-push');
const path = require('path');

require('dotenv').config();
Importando dependencias

Credenciales VAPID

El siguiente paso es crear unas credenciales VAPID, que b√°sicamente es un c√≥digo √ļnico que es asignado a un servidor de push para identificarlo y asegurarnos que las subscripciones hacia un servidor sean √ļnicas. Puedes darle un vistazo a la doc en este enlace.

Directo al grano, vamos a generar las keys con la siguiente linea, OJO! Esto solo es necesario ejecutar UNA UNICA VEZ:

...

const vapidKeys = webpush.generateVAPIDKeys();
console.log(vapidKeys);

Debe de devolvernos un objeto con con las credenciales necesarias:

Credenciales generados

Estas credenciales podemos moverlas hacia un archivo .env (que para eso hemos agregado el paquete dotenv xD) de la siguiente forma:

Credenciales en archivo .env

Bien, las estas keys podemos utilizarlas en el paquete web-push, a través de la función setVapidDetails() de la siguiente forma:

const webpush = require('web-push');
require('dotenv').config(); // cargamos las variables de entorno

...


const vapidKeys = {
  publicKey: process.env.PUBLIC_KEY,
  privateKey: process.env.PRIVATE_KEY
};

webpush.setVapidDetails(
  'mailto:example@yourdomain.org', // 1
  vapidKeys.publicKey, // 2
  vapidKeys.privateKey // 3
);
Ingresando claves generadas

Solo necesita 3 par√°metros, 1 direcci√≥n de contacto, la clave p√ļblica y la privada, con eso ya estamos tenemos configurado web-push y listos para pasar al siguiente paso.

Servidor koa

En toda aplicación web es importante manejar un servidor y este no es la excepción, como lo has notado en las dependencias se utilizará koajs.

const Koa = require('koa');
const body = require('koa-body');
const Pug = require('koa-pug');
const serve = require('koa-static');
...

const app = new Koa(); // 1

app.use(body()); // 2

const pug = new Pug({ // 3
  viewPath: path.resolve(__dirname, './views'),
  basedir: './views',
  app: app
});

app.use(serve('assets')); // 4
Código básico del servidor

Aquí básicamente estamos realizando las siguientes acciones:

  1. Crear una instancia de koajs.
  2. Utilizar el middleware de koa-body para leer el contenido de peticiones POST.
  3. Crear una instancia de koa-pug, indic√°ndole la ruta de las vistas y pas√°ndole la instancia de koajs.
  4. Utilizar el middleware de koa-static, para servir los archivos est√°ticos (pueden ser im√°genes, .css, .js).

Las rutas que nos conducen Roma

Imposible pasar de largo sin utilizar a koa-router, es el middleware por excelencia para utilizar con koa, así que sin más tendremos 3 rutas disponibles, para eso primero realizamos una instancia de koa-router, en este caso se lo estamos asignando a "_":

const router = require('koa-router');

...

const app = new Koa();
...

const _ = router();

_.get('/', async ctx => {
  return ctx.render('index');
});
Rutas de la aplicación en koa

La primera ruta no tiene nada de especial, lo que hace es renderizar la vista "index" cuando la solicitud sea GET a la raíz de la página.

¬ŅPero de qu√© vista me estas hablando?... Pues esta, solo que m√°s adelante veremos qu√© hay en el index.pug.

Vistas de pug

La siguiente ruta es una llamada POST hacia /subscribe, que b√°sicamente espera un valor sub, ese valor debe ser empujado hacia a un array de clientes, recuerda que este array es solo es para efectos del ejemplo, debes almacenar estos valores en una base de datos de tu preferencia.

...
const clientList = [];

const app = new Koa();
...

_.post('/subscribe', async ctx => {
  const { sub } = ctx.request.body;
  try {
    clientList.push(sub);
  } catch (e) {
    console.log(e);
  }

  ctx.body = 'ok';
});
Ruta de suscripción

Finalmente, la ruta esta disponible para llamadas POST a la raíz del proyecto, aquí deben enviarnos el titulo(title) y el cuerpo(body) que utilizaremos para enviar una notificación. El mensaje será enviado a todos los clientes que fueron suscritos previamente, como paso final al terminar la tarea vuelve a renderizar la vista index.js.

const clientList = [];
...

_.post('/', async ctx => {
  const { title, body } = ctx.request.body;

  try {
    clientList.forEach(subItem => {
      webpush.sendNotification(subItem, JSON.stringify({ title, body }));
    });
  } catch (e) {
    console.log(e);
  }

  return ctx.render('index');
});

app.use(_.routes());

app.listen(4444, () => {
  console.log('listen: ' + 4444);
});
Ruta de envío de push

¬ŅQu√© hay ahora del frontend?

De este lado tenemos la parte del cliente, aquí necesitamos solicitar el permiso para que el usuario pueda recibir notificaciones de nuestra página, ¡empecemos!

De esta parte lo primero es crear un archivo index.pug la ruta /views de tu proyecto con el siguiente contenido:

head
    title Web push
body
    h1 Probando web push
    form(action="/", method="POST")
        input(type="text", placeholder="Titulo", name="title")
        input(type="text", placeholder="Mensaje", name="body")
        button(type="submit") Enviar push
Formulario para enviar notificaciones

En esta parte simplemente necesitamos un formulario con un input para el título y otro para el cuerpo de la notificación, obviamente un botón para enviar esta información hacia el servidor.

head
    title Web push
body
    ...
    script.
        window.addEventListener('load', async () => {
            if (!'serviceWorker' in navigator) {
                console.log('Service workers are not supported.')
                return
            }
            console.log('Service working is supported');

            const sw = await navigator.serviceWorker.register('/sw.js');
            await subscribe();

            console.log('ready to receive web push')
        })
Script cuando carga el navegador

Este primer extracto de script ejecutará apenas termine de cargarse la ventana del navegador una verificación que compruebe que los serviceWorkers son soportados por el navegador actual, esto nos sirve para poder registrar las instrucciones que nos permitan recibir notificaciones.

...
const sw = await navigator.serviceWorker.register('/sw.js');
...
Registro del service worker

Nota que si ya llegamos a esta línea, es que el navegador soporta correctamente los serviceWorkers, bien, estamos apunto de registrar uno, no importa si se ejecuta register() cada vez que se actualiza la página, el navegador determina si ya lo ha registrado.

Instrucciones del service worker

Aquí la cosa se pone un poco más interesante, ya empezamos a programar qué es lo que va a realizar el service worker en nuestro archivo sw.js en /assets.

Como primer objetivo realizaremos un listener que recibe mensajes de eventos push, tal cual como en el siguiente código:

self.addEventListener('push', function(e) {
  const message = e.data.json(); // 1

  const options = { // 2
    body: message.body,
    data: 'http://localhost:4444',
    actions: [
      {
        action: 'Detail',
        title: 'Detalles'
      }
    ]
  };

  e.waitUntil(self.registration.showNotification(message.title, options)); // 3
});
Listener de evento push

Veamos en detalle:

  1. La data la estamos recibiendo desde el servidor y luego la formateamos a JSON con la función json().
  2. Creamos un objeto configurando el body de la notificación (cuerpo del mensaje), data(información que se utiliza al hacer click en la notificación) y actions(agrega botones extras).
  3. showNotification() necesita 2 parámetros el título que lo traemos desde la data obtenida desde el backend, y las opciones para la notificación creadas previamente.

Es importante que utilicemos la función waitUntil(), esto le dice al navegador que mantenga ejecutando el serviceWorker hasta que complete su tarea, en este caso, mostrar la notificación.

El siguiente extracto de código lo utilizaremos para escuchar el evento 'click' en la notificación:

...
self.addEventListener('notificationclick', function(e) {
  console.log('Notification click Received.', e.notification.data);

  e.notification.close(); // 1
  e.waitUntil(clients.openWindow(e.notification.data)); // 2
});
Listener de click en la notificación

Tenemos consideradas dos acciones muy sencillas, primero (1) necesitamos que la notificaci√≥n se cierre, y como √ļltima acci√≥n(2) abrimos nueva ventana con la URL que enviamos a trav√©s del data, por cierto a√ļn necesitamos utilizar waitUntil() para que termine de completar la tarea que definimos (abrir una ventana).

Subscribiendo a un service worker

body
    ...
    script.
    ...
    	window.addEventListener('load', async () => {
            ...
            const sw = await navigator.serviceWorker.register('/sw.js'); // done
            await subscribe(); // we are here!

            console.log('ready to receive web push')
        })
        
        const subscribe = async () => {
            const serviceWorker = await navigator.serviceWorker.ready; // 1
            const subscription = await serviceWorker.pushManager.getSubscription(); // 2

            if (!subscription) {
                console.log('subscribing....');
                const push = await serviceWorker.pushManager.subscribe({ // 3
                    userVisibleOnly: true,
                    applicationServerKey: 'BLTrRpzJQuGKQCOx5PrbIn_dI9d8ZuzZ7iVPRQ0Wf7EJBm0Bt4-f08zrej7I8WBEevAQriPtUAKCW_AbEtQLhh0'
                })
                console.log('subscribed. ', push);

                await sendToServer(push);
            }
        }
Código para suscribir al usuario

Ya estamos cerca de cumplir nuestro cometido, en el código anterior dentro de la función subscribe, estamos realizando algunas cosas super sencillas, primero activamos el service worker(1), esta promesa no va a fallar nunca si no más bien espera indefinidamente a que el serviceWorker esté listo para realizar algunas acciones como obtener subscripciones, que es lo que viene a continuación.

Gracias a la función getSubscription() de pushManager es posible obtener una subscripción activa(2) si es que la hubiera, en una primera vez tendremos un valor nulo, que nos permitirá pasar la validación y pasamos por fin, a suscribirnos.

La subscripci√≥n es apoyada por el manejador de push (pushManager), aqu√≠ le pasamos un objeto con la configuraci√≥n necesaria, como primer par√°metro userVisibleOnly: true, para indicarle que todas las notificaciones ser√°n visibles para el usuario, y como segundo par√°metro applicationServerKey con la clave p√ļblica del servidor, s√≠, la misma que generamos y esta en el .env:

Claves en archivo .env

Bien, tenemos suscrito al usuario, ¬Ņahora qu√©?.... simple, ahora viene el paso de la persistencia...

Acabamos de crear la data de la subscripción es momento de enviarla al servidor para que la considere cuando necesite enviar notificaciones, en eso fetch API nos puede dar una mano sin problemas, mira el siguiente código.


			...

			await sendToServer(push); // send to server
        }
            
        const sendToServer = async subData => {
            console.log('saving to server...');
            await fetch("/subscribe", {
                method: "POST",
                headers: {
                    'content-type': 'application/json'
                },
                body: JSON.stringify({ sub: subData })
            });
        }
Envío de la suscripción generada al servidor

En el servidor procederemos a guardarlo en un array de clientes, por cierto para evitarnos de complejidades y para efectos del ejemplo, asumimos que todo salió bien, aquí te dejo la imagen por si no recuerdas para evitarte hacer un scroll arriba.

Ruta de suscripción en el servidor

Probando la m√°quina

Ya estamos en la carrera final, ¡atento! abre la terminal en la raíz de tu proyecto y ejecuta el servidor.

Ejecutando el servidor

Ahora ingresa a tu navegador, abre una nueva pesta√Īa y tambi√©n la consola de desarrollador del navegador, ahora s√≠, escribe la url http://localhost:4444 e ingresa.

La web nos solicita permisos para mostrar notificaciones

Nota que el navegador nos pregunta si queremos darle permisos a la página para que nos pueda mostrar notificaciones, que esperas...¡dale que sí!

Luego de permitirle el envío de notificaciones, podrás notar que en la consola se muestra cada paso que se ha realizado:

Consola del navegador

¡Genial! ahora podemos trabajar con los web push, en el formulario que tenemos disponible agregamos un título y un mensaje para nuestra notificación, luego clic en "Enviar push".

Formulario para enviar web push

Y whoalaaa! La notificación fue un éxito!

Web push funcionando

Conclusiones finales

Los web push son una herramienta demasiado √ļtil para diferentes casos de uso, imagina todo lo que podr√≠as hacer, te pongo un par de ejemplos, puedes hacerle recordar a tus clientes que abandonaron su carrito de compras, una alerta de compras a tiempo limitado, notificaciones de un nuevo post, marcadores en vivo, etc...crear notificaciones push web para ti ahora es sencillo, pero el l√≠mite es tu imaginaci√≥n.

Fuente: Giphy

Realmente te felicito, acabas de adquirir una nueva habilidad, aprovéchala y mejora el mundo a base de código. Si consideras que este post te ha dado valor de alguna manera, no dudes es seguirnos y compartirlo con tus amigos, casi lo olvido...¡déjanos comentarios! Nos vemos en otra entrega.

Fernando Palacios

¡Hoy es un gran día para hacer grandes cosas!

¡Genial! Te has suscrito con éxito.
¬°Genial! Ahora, completa el checkout para tener acceso completo.
¡Bienvenido de nuevo! Has iniciado sesión con éxito.
√Čxito! Su cuenta est√° totalmente activada, ahora tienes acceso a todo el contenido.