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 de cliente ya está completo. En este codelab, trabajarás en la funcionalidad del servidor.

Hacer un remix de la app de ejemplo y verla en una pestaña nueva

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 cambio, esto es lo que debes hacer:

  1. Haz clic en Remix to Edit para que el proyecto sea editable.
  2. Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona Pantalla completa pantalla completa.

La app en vivo se abrirá en una nueva pestaña de Chrome. En la Glitch incorporada, haz clic en View Source para mostrar el código de nuevo.

A medida que avances 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 activa para ver los cambios.

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

Comienza por observar la IU del cliente de la app.

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

  1. Presiona "Control + Mayús + J" (o bien "Comando + Opción + J" en Mac) para abrir Herramientas para desarrolladores. Haz clic en la pestaña Consola.

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

    • La opción Registrar 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 le adjunta una suscripción de envío, también se desactivará la suscripción de envío.

    • Suscríbete a push crea una suscripción push. Solo está disponible cuando se registra un service worker y hay una constante VAPID_PUBLIC_KEY en el código del cliente (obtén más información al respecto más adelante), por lo que aún no puedes hacer clic en ella.

    • Cuando tienes una suscripción de envío activa, Notificar la 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 algunos de estos extremos pueden estar inactivos. Siempre es posible que la suscripción desaparezca cuando el servidor le envía una notificación.

Veamos lo que ocurre 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 -> Logs.

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

    Si intentaste hacer clic en Notificar la suscripción actual o Notificar 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 algo del código.

  • 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 las suscripciones nuevas y borradas al servidor.

    Dado que solo trabajarás en la funcionalidad del servidor, no editarás este archivo (aparte 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 tu 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 de tu 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:. Realiza lo siguiente:

    En este codelab, trabajarás de a uno por uno los elementos TODO.

Genera y carga los detalles de VAPID

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

Información general

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

El protocolo que hace que las notificaciones push sean seguras y privadas se denomina 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 apps, servidores y extremos de suscripción, así como para encriptar el contenido de las notificaciones.

En esta app, usarás el paquete de 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 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 reinicia tu app, genera las claves generadas para el registro de Node.js dentro de la interfaz de Glitch (no a la consola de Chrome). Para ver las claves de VAPID, selecciona Tools -> Logs en la interfaz de Glitch.

    Asegúrate de copiar tus claves públicas y privadas desde el 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 surjan 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 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. Copia y pega la clave pública también 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

Información general

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

Este paquete encripta las notificaciones automáticamente cuando se llama a webpush.sendNotification(), por lo que no debes 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 por vencimiento en una notificación. De esta manera, el servidor evita enviar una notificación a un usuario cuando esta 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 manejo 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 controlar las suscripciones nuevas

Información general

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

  1. El usuario hace clic en Suscribirse al envío.

  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 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 para 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 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 cumplir con los siguientes requisitos:

  • 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 administrar suscripciones nuevas, haz lo siguiente:

  • En server.js, modifica el controlador de rutas 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 cierre el service worker.

Sin embargo, el servidor puede detectar 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 en realidad con una app de prueba simple, pero se hace importante a mayor escala.

Implementación

Las solicitudes para cancelar suscripciones se envían a la URL de POST /remove-subscription.

El controlador de rutas de stub en server.js se ve así:

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 cumplir con los siguientes requisitos:

  • 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 administrar las cancelaciones de suscripciones, haz lo siguiente:

  • En server.js, modifica el controlador de rutas 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);
  });