Compila un servidor de notificaciones push

En este codelab, compilarás un servidor de notificaciones push. El servidor administrará una lista de suscripciones push y les enviará notificaciones.

El código del cliente ya está completo. En este codelab, trabajarás en la funcionalidad del servidor.

Las notificaciones se bloquean automáticamente desde la app de Glitch incorporada, por lo que no podrás obtener una vista previa de la app en esta página. En su lugar, haz lo siguiente:

  1. Haz clic en Remix para editar para que el proyecto se pueda editar.
  2. Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona Pantalla completapantalla completa.

La app publicada se abre en una nueva pestaña de Chrome. En Glitch incorporado, haz clic en Ver código fuente para volver a mostrar el código.

A medida que trabajes en este codelab, realiza cambios en el código de Glitch incorporado en esta página. Actualiza la nueva pestaña con tu app en vivo para ver los cambios.

Familiarízate con la app de partida y su código

Para comenzar, observa la IU del cliente de la app.

En la nueva pestaña de Chrome, haz lo siguiente:

  1. Presiona `Control + Mayúsculas + J` (o `Command + Option + J` en Mac) para abrir Herramientas para desarrolladores. Haz clic en la pestaña Consola.

  2. Intenta hacer clic en los botones de la IU (consulta el resultado en la consola de Chrome Dev).

    • Registra un service worker registra un service worker para el alcance de la URL de tu proyecto de Glitch. Cancelar el registro del service worker quita el service worker. Si se adjunta una suscripción push, esta también se desactivará.

    • Suscribirse a envío crea una suscripción de envío. Solo está disponible cuando se registró un service worker y hay una constante VAPID_PUBLIC_KEY presente en el código del cliente (obtén más información sobre esto más adelante), por lo que aún no puedes hacer clic en él.

    • Cuando tengas una suscripción push activa, Notificar suscripción actual solicita que el servidor envíe una notificación a su extremo.

    • Notificar a todas las suscripciones le indica al servidor que envíe una notificación a todos los extremos de suscripción de su base de datos.

      Ten en cuenta que es posible que algunos de estos extremos estén inactivos. Siempre es posible que una suscripción desaparezca cuando el servidor le envíe una notificación.

Veamos qué sucede del lado del servidor. Para ver los mensajes del código del servidor, consulta el registro de Node.js en la interfaz de Glitch.

  • En la app de Glitch, haz clic en Tools -> Logs.

    Es probable que veas un mensaje como Listening on port 3000.

    Si intentaste hacer clic en Notificar suscripción actual o Notificar todas las suscripciones en la IU de la app publicada, también verás el siguiente mensaje:

    TODO: Implement sendNotifications()
    Endpoints to send to:  []

Ahora, veamos algunos códigos.

  • public/index.js contiene el código de cliente completo. Realiza la detección de funciones, registra y cancela el registro del trabajador de servicio, y controla la suscripción del usuario a las notificaciones push. También envía información sobre las suscripciones nuevas y borradas al servidor.

    Como solo trabajarás en la funcionalidad del servidor, no editarás este archivo (además de propagar la constante VAPID_PUBLIC_KEY).

  • public/service-worker.js es un service worker simple que captura eventos push y muestra notificaciones.

  • /views/index.html contiene la IU de la app.

  • .env contiene las variables de entorno que Glitch carga en el servidor de apps cuando se inicia. Propagarás .env con los detalles de autenticación para enviar notificaciones.

  • server.js es el archivo en el que realizarás la mayor parte del trabajo durante este codelab.

    El código inicial crea un servidor web Express simple. Hay cuatro elementos TODO para ti, marcados en los comentarios de código con TODO:. Realice lo siguiente:

    En este codelab, trabajarás en estos elementos TODO de a uno por vez.

Genera y carga detalles de VAPID

Tu primer elemento TODO es generar detalles de VAPID, agregarlos a las variables de entorno de Node.js y actualizar el código del cliente y del servidor con los nuevos valores.

Segundo plano

Cuando los usuarios se suscriben a notificaciones, deben confiar en la identidad de la app y su servidor. Los usuarios también deben tener la seguridad de que, cuando reciban una notificación, provenga de la misma app que configuró la suscripción. También deben confiar en que nadie más pueda leer el contenido de la notificación.

El protocolo que hace que las notificaciones push sean seguras y privadas se denomina Identificación voluntaria del servidor de aplicaciones para notificaciones push web (VAPID). VAPID usa criptografía de clave pública para verificar la identidad de las apps, los servidores y los extremos de suscripción, y encriptar el contenido de las notificaciones.

En esta app, usarás el paquete npm web-push para generar claves de VAPID y encriptar y enviar notificaciones.

Implementación

En este paso, genera un par de claves de VAPID para tu app y agrégalas a las variables de entorno. Carga las variables de entorno en el servidor y agrega la clave pública como una constante en el código del cliente.

  1. Usa la función generateVAPIDKeys de la biblioteca web-push para crear un par de claves de VAPID.

    En server.js, quita los comentarios de las siguientes líneas de código:

    server.js

    // Generate VAPID keys (only do this once).
    /*
     * const vapidKeys = webpush.generateVAPIDKeys();
     * console.log(vapidKeys);
     */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  2. Después de que Glitch reinicie tu app, mostrará las claves generadas en el registro de Node.js dentro de la interfaz de Glitch (no en la consola de Chrome). Para ver las claves de VAPID, selecciona Tools -> Logs en la interfaz de Glitch.

    Asegúrate de copiar las claves pública y privada del mismo par de claves.

    Glitch reinicia tu app cada vez que editas el código, por lo que es posible que el primer par de claves que generes desaparezca de la vista a medida que se muestra más salida.

  3. En .env, copia y pega las claves de VAPID. Encierra las claves entre comillas dobles ("...").

    Para VAPID_SUBJECT, puedes ingresar "mailto:test@test.test".

    .env

    # process.env.SECRET
    VAPID_PUBLIC_KEY
    =
    VAPID_PRIVATE_KEY
    =
    VAPID_SUBJECT
    =
    VAPID_PUBLIC_KEY
    ="BN3tWzHp3L3rBh03lGLlLlsq..."
    VAPID_PRIVATE_KEY
    ="I_lM7JMIXRhOk6HN..."
    VAPID_SUBJECT
    ="mailto:test@test.test"
  4. En server.js, vuelve a comentar esas dos líneas de código, ya que solo debes generar claves de VAPID una vez.

    server.js

    // Generate VAPID keys (only do this once).
    /*
    const vapidKeys = webpush.generateVAPIDKeys();
    console.log(vapidKeys);
    */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  5. En server.js, carga los detalles de VAPID desde las variables de entorno.

    server.js

    const vapidDetails = {
     
    // TODO: Load VAPID details from environment variables.
      publicKey
    : process.env.VAPID_PUBLIC_KEY,
      privateKey
    : process.env.VAPID_PRIVATE_KEY,
      subject
    : process.env.VAPID_SUBJECT
    }
  6. También copia y pega la clave pública en el código del cliente.

    En public/index.js, ingresa el mismo valor para VAPID_PUBLIC_KEY que copiaste en el archivo .env:

    public/index.js

    // Copy from .env
    const VAPID_PUBLIC_KEY = '';
    const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...';
    ````

Implementa la funcionalidad para enviar notificaciones

Segundo plano

En esta app, usarás el paquete npm web-push para enviar notificaciones.

Este paquete encripta automáticamente las notificaciones cuando se llama a webpush.sendNotification(), por lo que no debes preocuparte por eso.

web-push acepta varias opciones para las notificaciones; por ejemplo, puedes adjuntar encabezados al mensaje y especificar la codificación de contenido.

En este codelab, solo usarás dos opciones, definidas con las siguientes líneas de código:

let options = {
  TTL
: 10000; // Time-to-live. Notifications expire after this.
  vapidDetails
: vapidDetails; // VAPID keys from .env
};

La opción TTL (tiempo de actividad) establece un tiempo de espera de vencimiento en una notificación. Esta es una forma en que el servidor evita enviar una notificación a un usuario después de que ya no es relevante.

La opción vapidDetails contiene las claves de VAPID que cargaste desde las variables de entorno.

Implementación

En server.js, modifica la función sendNotifications de la siguiente manera:

server.js

function sendNotifications(database, endpoints) {
 
// TODO: Implement functionality to send notifications.
  console
.log('TODO: Implement sendNotifications()');
  console
.log('Endpoints to send to: ', endpoints);
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000, // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
 
});
}

Como webpush.sendNotification() muestra una promesa, puedes agregar fácilmente el control de errores.

En server.js, vuelve a modificar la función sendNotifications:

server.js

function sendNotifications(database, endpoints) {
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000; // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails; // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
    let id
= endpoint.substr((endpoint.length - 8), endpoint.length);
    webpush
.sendNotification(subscription, notification, options)
   
.then(result => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Result: ${result.statusCode} `);
   
})
   
.catch(error => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Error: ${error.body} `);
   
});
 
});
}

Cómo administrar suscripciones nuevas

Segundo plano

Esto es lo que sucede cuando el usuario se suscribe a las notificaciones push:

  1. El usuario hace clic en Suscribirse a notificaciones push.

  2. El cliente usa la constante VAPID_PUBLIC_KEY (la clave pública de VAPID del servidor) para generar un objeto subscription único y específico del servidor. El objeto subscription se ve de la siguiente manera:

       {
         
    "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         
    "expirationTime": null,
         
    "keys":
         
    {
           
    "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           
    "auth": "0IyyvUGNJ9RxJc83poo3bA"
         
    }
       
    }
  3. El cliente envía una solicitud POST a la URL /add-subscription, incluida la suscripción como JSON con formato de cadena en el cuerpo.

  4. El servidor recupera el subscription con cadena del cuerpo de la solicitud POST, lo analiza nuevamente en JSON y lo agrega a la base de datos de suscripciones.

    La base de datos almacena las suscripciones con sus propios extremos como clave:

    {
     
"https://fcm...1234": {
        endpoint
: "https://fcm...1234",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...abcd": {
        endpoint
: "https://fcm...abcd",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...zxcv": {
        endpoint
: "https://fcm...zxcv",
        expirationTime
: ...,
        keys
: { ... }
     
},
   
}

Ahora, la nueva suscripción está disponible en el servidor para enviar notificaciones.

Implementación

Las solicitudes de suscripciones nuevas llegan a la ruta /add-subscription, que es una URL POST. Verás un controlador de ruta de stub en server.js:

server.js

app.post('/add-subscription', (request, response) => {
 
// TODO: implement handler for /add-subscription
  console
.log('TODO: Implement handler for /add-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

En tu implementación, este controlador debe hacer lo siguiente:

  • Recupera la suscripción nueva del cuerpo de la solicitud.
  • Accede a la base de datos de suscripciones activas.
  • Agrega la suscripción nueva a la lista de suscripciones activas.

Para controlar las suscripciones nuevas, haz lo siguiente:

  • En server.js, modifica el controlador de ruta para /add-subscription de la siguiente manera:

    server.js

    app.post('/add-subscription', (request, response) => {
     
// TODO: implement handler for /add-subscription
      console
.log('TODO: Implement handler for /add-subscription');
      console
.log('Request body: ', request.body);
      let subscriptions
= Object.assign({}, request.session.subscriptions);
      subscriptions
[request.body.endpoint] = request.body;
      request
.session.subscriptions = subscriptions;
      response
.sendStatus(200);
   
});

Controla las cancelaciones de suscripciones

Segundo plano

El servidor no siempre sabrá cuándo una suscripción se vuelve inactiva; por ejemplo, una suscripción podría borrarse cuando el navegador cierre el service worker.

Sin embargo, el servidor puede obtener información sobre las suscripciones que se cancelan a través de la IU de la app. En este paso, implementarás la funcionalidad para quitar una suscripción de la base de datos.

De esta manera, el servidor evita enviar muchas notificaciones a extremos inexistentes. Por supuesto, esto no importa con una app de prueba simple, pero se vuelve importante a mayor escala.

Implementación

Las solicitudes para cancelar suscripciones llegan a la URL POST de /remove-subscription.

El controlador de rutas de stub en server.js se ve de la siguiente manera:

server.js

app.post('/remove-subscription', (request, response) => {
 
// TODO: implement handler for /remove-subscription
  console
.log('TODO: Implement handler for /remove-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

En tu implementación, este controlador debe hacer lo siguiente:

  • Recupera el extremo de la suscripción cancelada del cuerpo de la solicitud.
  • Accede a la base de datos de suscripciones activas.
  • Quita la suscripción cancelada de la lista de suscripciones activas.

El cuerpo de la solicitud POST del cliente contiene el extremo que debes quitar:

{
 
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}

Para controlar las cancelaciones de suscripciones, sigue estos pasos:

  • En server.js, modifica el controlador de ruta para /remove-subscription de la siguiente manera:

    server.js

  app.post('/remove-subscription', (request, response) => {
   
// TODO: implement handler for /remove-subscription
    console
.log('TODO: Implement handler for /remove-subscription');
    console
.log('Request body: ', request.body);
    let subscriptions
= Object.assign({}, request.session.subscriptions);
   
delete subscriptions[request.body.endpoint];
    request
.session.subscriptions = subscriptions;
    response
.sendStatus(200);
 
});