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 en la app incorporada de Glitch, por lo que no podrás obtener una vista previa de la app en esta página. En cambio, esto es lo que debes hacer:

  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 completa pantalla completa

La app publicada se abre en una nueva pestaña de Chrome. En el error 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 la Glitch incorporada en esta página. Actualiza la pestaña nueva con la app publicada para ver los cambios.

Familiarízate con la app inicial y su código

Primero, 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).

    • La opción Registrar service worker registra un service worker para el alcance de la URL de tu proyecto de Glitch. La opción Cancelar el registro del service worker quita el service worker. Si se le adjunta una suscripción de envío, esta también se desactivará.

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

    • 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 en 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 lo que sucede en el 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 -> Registros (Logs).

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

    Si intentaste hacer clic en Notificar la suscripción actual o Notificar a todas las suscripciones en la IU de la app activa, 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 service worker, y controla la suscripción del usuario a las notificaciones push. También envía información sobre 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 de inicio 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 con estos elementos de tareas pendientes uno a la 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.

Información general

Cuando los usuarios se suscriben a las notificaciones, necesitan confiar en la identidad de la app y su servidor. Los usuarios también deben estar seguros de que, cuando reciben una notificación, proviene de la misma app que configuró la suscripción. También deben confiar en que nadie más podrá leer el contenido de la notificación.

El protocolo que hace que las notificaciones push sean seguras y privadas se llama Identificación voluntaria del servidor de aplicaciones para el envío 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 web-push de npm para generar claves VAPID, y encriptar y enviar notificaciones.

Implementación

En este paso, genera un par de claves 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 VAPID.

    En server.js, quita los comentarios alrededor 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 reinicia tu app, genera 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 VAPID, selecciona Herramientas -> Logs en la interfaz de Glitch.

    Asegúrate de copiar tus claves públicas y privadas 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 se desplace fuera de la vista a medida que se generen más resultados.

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

    En 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, marca como comentario esas dos líneas de código nuevamente, ya que solo necesitas generar las claves 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 de 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. Copia y pega la clave public en el código de cliente también.

    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

Información general

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

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

web-push acepta múltiples 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 para una notificación. De este modo, el servidor evita enviar una notificación al usuario cuando ya no es relevante.

La opción vapidDetails contiene las claves 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);
  });
}

Dado que webpush.sendNotification() muestra una promesa, puedes agregar fácilmente el manejo de errores.

En server.js, modifica la función sendNotifications nuevamente:

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

Información general

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

  1. El usuario hace clic en Suscribirse a la notificación.

  2. El cliente usa la constante VAPID_PUBLIC_KEY (la clave VAPID pública 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 en cadena en el cuerpo.

  4. El servidor recupera el subscription en cadena del cuerpo de la solicitud POST, lo analiza de nuevo 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 de POST. Verás un controlador de rutas 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 nueva suscripción del cuerpo de la solicitud.
  • Accede a la base de datos de suscripciones activas.
  • Agrega la nueva suscripción a la lista de suscripciones activas.

Para administrar 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);
    });

Cómo controlar las cancelaciones de suscripciones

Información general

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

Sin embargo, el servidor puede detectar 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 un montón de notificaciones a endpoints 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 de 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..."
}

Sigue estos pasos para controlar las cancelaciones de suscripciones:

  • 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);
  });