Envía mensajes con bibliotecas web push

Uno de los problemas cuando se trabaja con notificaciones web push es que activar un mensaje push es muy engorroso. Para activar un mensaje push, una aplicación debe realizar una solicitud POST a un servicio de push que siga el protocolo de notificaciones push web. Para usar la función push en todos los navegadores, debes usar VAPID (también conocidas como claves de servidor de aplicaciones), que básicamente requiere configurar un encabezado con un valor que demuestre que tu aplicación puede enviar mensajes a un usuario. Para enviar datos con un mensaje push, los datos deben estar encriptados y se deben agregar encabezados específicos para que el navegador pueda desencriptar el mensaje correctamente.

El problema principal con el envío de notificaciones push es que, si tienes un problema, es difícil diagnosticarlo. Esto mejora con el tiempo y la compatibilidad con más navegadores, pero no es nada fácil. Por este motivo, te recomiendo que uses una biblioteca para controlar la encriptación, el formato y el activación de tu mensaje push.

Si realmente quieres saber qué hacen las bibliotecas, lo veremos en la siguiente sección. Por ahora, analizaremos la administración de suscripciones y el uso de una biblioteca de notificaciones web push existente para realizar las solicitudes de push.

En esta sección, usaremos la biblioteca de Node web-push. Otros idiomas tendrán diferencias, pero no serán demasiado diferentes. Estamos analizando Node porque es JavaScript y debería ser la opción más accesible para los lectores.

Seguiremos estos pasos:

  1. Envía una suscripción a nuestro backend y guárdala.
  2. Recuperar suscripciones guardadas y activar un mensaje push

Cómo guardar suscripciones

El guardado y la consulta de PushSubscription desde una base de datos variarán según el idioma del servidor y la base de datos que elijas, pero podría ser útil ver un ejemplo de cómo se podría hacer.

En la página web de demostración, el PushSubscription se envía a nuestro backend mediante una solicitud POST simple:

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

El servidor Express de nuestra demo tiene un objeto de escucha de solicitudes coincidente para el extremo /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

En esta ruta, validamos la suscripción solo para asegurarnos de que la solicitud esté bien y no esté llena de basura:

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

Si la suscripción es válida, debemos guardarla y mostrar una respuesta JSON adecuada:

return saveSubscriptionToDatabase(req.body)
  .then(function (subscriptionId) {
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({data: {success: true}}));
  })
  .catch(function (err) {
    res.status(500);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'unable-to-save-subscription',
          message:
            'The subscription was received but we were unable to save it to our database.',
        },
      }),
    );
  });

Esta demostración usa nedb para almacenar las suscripciones. Es una base de datos simple basada en archivos, pero puedes usar la base de datos que elijas. Solo lo usamos porque no requiere configuración. Para la producción, te recomendamos que uses algo más confiable. (tiendo a pegarme al buen MySQL).

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

Envío de mensajes push

Cuando se trata de enviar un mensaje push, en última instancia, necesitamos algún evento para activar el proceso de envío de un mensaje a los usuarios. Un enfoque común es crear una página de administrador que te permita configurar y activar el mensaje push. Sin embargo, puedes crear un programa para ejecutar de forma local o cualquier otro enfoque que permita acceder a la lista de PushSubscription y ejecutar el código para activar el mensaje push.

Nuestra demostración tiene una página "como administrador" que te permite activar un mensaje push. Como es solo una demostración, es una página pública.

Voy a explicar cada paso necesario para que funcione la demostración. Estos serán pasos básicos para que todos puedan seguirlos, incluso quienes sean nuevos en Node.

Cuando hablamos de la suscripción de un usuario, mencionamos que se debe agregar un applicationServerKey a las opciones de subscribe(). Necesitaremos esta clave privada en el backend.

En la demostración, estos valores se agregan a nuestra app de Node de la siguiente manera (sé que es un código aburrido, pero solo quiero que sepas que no hay magia):

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

A continuación, debemos instalar el módulo web-push para nuestro servidor de Node:

npm install web-push --save

Luego, en nuestra secuencia de comandos de Node, requerimos el módulo web-push de la siguiente manera:

const webpush = require('web-push');

Ahora podemos comenzar a usar el módulo web-push. Primero, debemos informarle al módulo web-push sobre las claves de nuestro servidor de aplicaciones. (Recuerda que también se conocen como claves de VAPID porque ese es el nombre de la especificación).

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

Ten en cuenta que también incluimos una cadena "mailto:". Esta cadena debe ser una URL o una dirección de correo electrónico mailto. Esta información se enviará al servicio de notificaciones push web como parte de la solicitud para activar un mensaje push. El motivo de esto es que, si un servicio de notificaciones web push necesita comunicarse con el remitente, tenga información que le permita hacerlo.

Con esto, el módulo web-push está listo para usarse. El siguiente paso es activar un mensaje push.

En la demostración, se usa el panel de administrador simulado para activar los mensajes push.

Captura de pantalla de la página Administrador.

Si haces clic en el botón "Trigger Push Message", se realizará una solicitud POST a /api/trigger-push-msg/, que es la señal para que nuestro backend envíe mensajes push, por lo que creamos la ruta en Express para este extremo:

app.post('/api/trigger-push-msg/', function (req, res) {

Cuando se recibe esta solicitud, tomamos las suscripciones de la base de datos y, para cada una, activamos un mensaje push.

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

Luego, la función triggerPushMsg() puede usar la biblioteca web-push para enviar un mensaje a la suscripción proporcionada.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

La llamada a webpush.sendNotification() mostrará una promesa. Si el mensaje se envió correctamente, la promesa se resolverá y no tendremos que hacer nada. Si se rechaza, debes examinar el error, ya que te informará si el PushSubscription sigue siendo válido o no.

Para determinar el tipo de error de un servicio push, es mejor consultar el código de estado. Los mensajes de error varían entre los servicios push, y algunos son más útiles que otros.

En este ejemplo, busca los códigos de estado 404 y 410, que son los códigos de estado HTTP de "No encontrado" y "No disponible". Si recibimos uno de estos, significa que la suscripción caducó o ya no es válida. En estos casos, debemos quitar las suscripciones de nuestra base de datos.

En caso de que haya algún otro error, solo throw err, lo que hará que se rechace la promesa que muestra triggerPushMsg().

Analizaremos algunos de los otros códigos de estado en la siguiente sección cuando analicemos el protocolo de notificaciones push web con más detalle.

Después de recorrer las suscripciones, debemos mostrar una respuesta JSON.

.then(() => {
res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ data: { success: true } }));
})
.catch(function(err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({
    error: {
    id: 'unable-to-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

Analizamos los principales pasos de implementación:

  1. Crear una API para enviar suscripciones de nuestra página web a nuestro backend para que pueda guardarlas en una base de datos
  2. Crea una API para activar el envío de mensajes push (en este caso, una API a la que se llama desde el panel de administración simulado).
  3. Recupera todas las suscripciones de nuestro backend y envía un mensaje a cada una con una de las bibliotecas de Web Push.

Independientemente de tu backend (Node, PHP, Python, etc.), los pasos para implementar el envío serán los mismos.

A continuación, ¿qué hacen exactamente estas bibliotecas de notificaciones web push por nosotros?

Próximos pasos

Code labs