Envía mensajes con bibliotecas web push

Uno de los problemas cuando se trabaja con push web es que activar un mensaje push es extremadamente “difícil”. Para activar un mensaje de envío, una aplicación debe realizar una solicitud POST a un servicio de envío mediante el protocolo de envío web. Para usar el envío en todos los navegadores, debes usar VAPID (también conocida 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 encriptarse y se deben agregar encabezados específicos para que el navegador pueda desencriptar el mensaje de forma correcta.

El principal problema con la activación del envío es que si encuentras un problema, es difícil de diagnosticarlo. Esto está mejorando con el tiempo y una mayor compatibilidad con los navegadores, pero no es sencillo. Por este motivo, te recomendamos que uses una biblioteca para manejar la encriptación, el formato y la activación de tu mensaje push.

Si realmente quieres obtener más información sobre lo que hacen las bibliotecas, lo veremos en la siguiente sección. Por ahora, veremos la administración de suscripciones y el uso de una biblioteca web push existente para realizar las solicitudes de envío.

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

Completaremos los siguientes pasos:

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

Cómo guardar suscripciones

Guardar y consultar las PushSubscription de una base de datos variará según el lenguaje del servidor y la base de datos que elijas, pero podría ser útil ver un ejemplo de cómo se puede hacer.

En la página web de demostración, se envía PushSubscription 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 demostración tiene un objeto de escucha de solicitudes coincidentes 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 sea correcta y no esté llena de elementos no utilizados:

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.',
        },
      }),
    );
  });

En esta demostración, se usa nedb para almacenar las suscripciones; es una base de datos simple basada en archivos, pero puedes usar cualquier base de datos que desees. Solo la usamos porque no requiere configuración. Para la producción, deberías usar algo más confiable. (tiendo a seguir usando el buen MySQL viejo).

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 de envío. Sin embargo, puedes crear un programa para que se ejecute 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 de “me gusta de administrador” que te permite activar un envío. Como es solo una demostración, es una página pública.

Voy a repasar cada uno de los pasos necesarios para que la demostración funcione. Estos serán pasos pequeños para que todos puedan seguirlo, incluso cualquiera que sea nuevo en Node.

Cuando hablamos sobre la suscripción de un usuario, abordamos cómo agregar un applicationServerKey a las opciones subscribe(). Es el backend que necesitaremos esta clave privada.

En la demostración, estos valores se agregan a nuestra app de Node de esta manera (conozco el código aburrido, pero quiero que sepas que no existe la 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, necesitamos 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 informar al módulo web-push sobre nuestras claves de servidor de aplicaciones. (Recuerda que también se conocen como claves 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 string debe ser una URL o una dirección de correo electrónico de mailto. En realidad, esta información se enviará al servicio de envío web como parte de la solicitud para activar un envío. Esto se hace para que, si un servicio push web necesita comunicarse con el remitente, este tenga información que le permitirá hacerlo.

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

La demostración usa el panel de administración simulado para activar los mensajes push.

Captura de pantalla de la página Administrador.

Si haces clic en el botón “Activar mensaje push” se realizará una solicitud POST a /api/trigger-push-msg/, que es el indicador para que nuestro backend envíe mensajes push, por lo que crearemos la ruta en exprés 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() podrá 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 hay nada que debamos hacer. Si se rechaza, debes examinar el error, ya que te informará si PushSubscription sigue siendo válido o no.

Para determinar el tipo de error de un servicio de envío, es mejor mirar 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, se verifican los códigos de estado 404 y 410, que son los códigos de estado HTTP "No encontrado" y "No disponible". Si recibimos uno de estos, significa que la suscripción venció o ya no es válida. En estas situaciones, debemos quitar las suscripciones de nuestra base de datos.

En caso de 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 envío web con más detalle.

Después de recorrer las suscripciones en bucle, 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}'`
    }
}));
});

Repasamos los principales pasos de implementación:

  1. Crear una API para enviar suscripciones desde nuestra página web al 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 supuesto panel de administración).
  3. Recuperar todas las suscripciones de nuestro backend y enviar un mensaje a cada suscripción con una de las bibliotecas 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 por nosotros estas bibliotecas web-push?

Próximos pasos

Code labs