Cómo hacer que las PWAs se puedan volver a conectar usando Notificaciones y Push
================================================================================

* Anterior  (en-US)
* Overview: Progressive (en-US)
* Siguiente  (en-US)

Tener la capacidad de almacenar en caché el contenido de una aplicación para que funcione sin conexión es una gran característica. Permitir que el usuario instale la aplicación web en su pantalla de inicio es aún mejor. Pero en lugar de depender solo de las acciones del usuario, podemos hacer más, utilizando mensajes `push` y notificaciones para volver a interactuar automáticamente y entregar contenido nuevo siempre que esté disponible.

Dos APIs, un objetivo
---------------------

API Push y API de notificaciones son dos APIs independientes, pero funcionan bien juntas cuando deseas proporcionar una funcionalidad atractiva en tu aplicación. `Push` se utiliza para entregar contenido nuevo desde el servidor a la aplicación sin ninguna intervención del lado del cliente, y su operación es manejada por el servicio *worker* de la aplicación. El servicio *worker* puede utilizar las notificaciones para mostrar nueva información al usuario, o al menos alertarlo cuando algo se haya actualizado.

Funcionan fuera de la ventana del navegador, al igual que el servicio *worker*, por lo que se pueden enviar actualizaciones y se pueden mostrar notificaciones cuando la página de la aplicación está desenfocada o incluso cerrada.

Notificaciones
--------------

Comencemos con las notificaciones: pueden funcionar sin `push`, pero son muy útiles cuando se combinan con ellas. Para empezar, veámoslo de forma aislada.

### Pedir permiso

Para mostrar una notificación, primero debes solicitar permiso. Sin embargo, en lugar de mostrar la notificación de inmediato, la mejor práctica dicta que deberíamos mostrar la ventana emergente cuando el usuario la solicite haciendo clic en un botón:


```js
var button = document.getElementById("notifications");
button.addEventListener("click", function (e) {
  Notification.requestPermission().then(function (result) {
    if (result === "granted") {
      randomNotification();
    }
  });
});
```


Esto muestra una ventana emergente usando el propio servicio de notificaciones del sistema operativo:

![Notificación de js13kPWA.](/es/docs/Web/Progressive_web_apps/Tutorials/js13kGames/Re-engageable_Notifications_Push/js13kpwa-notification.png)

Cuando el usuario confirma recibir notificaciones, la aplicación las puede mostrar. El resultado de la acción del usuario puede ser predeterminada, otorgada o denegada. La opción predeterminada se elige cuando el usuario no hace una elección, y las otras dos se establecen cuando el usuario hace clic en sí o no, respectivamente.

Cuando se acepta, el permiso funciona tanto para notificaciones como para `push`.

### Crea una notificación

La aplicación de ejemplo crea una notificación a partir de los datos disponibles: se elige un juego al azar y el elegido alimenta la notificación con el contenido — establece el nombre del juego como título, menciona al autor en el cuerpo y muestra la imagen como un icono:


```js
function randomNotification() {
  var randomItem = Math.floor(Math.random() \* games.length);
  var notifTitle = games[randomItem].name;
  var notifBody = "Creado por " + games[randomItem].author + ".";
  var notifImg = "data/img/" + games[randomItem].slug + ".jpg";
  var options = {
    body: notifBody,
    icon: notifImg,
  };
  var notif = new Notification(notifTitle, options);
  setTimeout(randomNotification, 30000);
}
```


Se crea una nueva notificación aleatoria cada 30 segundos hasta que se vuelve demasiado molesta y el usuario la desactiva. (En una aplicación real, las notificaciones deberían ser mucho menos frecuentes y más útiles). La ventaja de la API de notificaciones es que utiliza la funcionalidad de notificación del sistema operativo. Esto significa que las notificaciones se pueden mostrar al usuario incluso cuando no están mirando la aplicación web, y las notificaciones son similares a las que muestran las aplicaciones nativas.

`Push`
------

`Push` es más complicado que las notificaciones: necesitamos suscribirnos a un servidor que luego enviará los datos a la aplicación. El servicio *worker* de la aplicación recibirá datos `push` del servidor, que luego se pueden mostrar usando el sistema de notificaciones u otro mecanismo si lo deseas.

La tecnología aún se encuentra en una etapa muy temprana; algunos ejemplos de uso utilizan la plataforma de mensajería en la nube de Google, pero se están reescribiendo para admitir IDVAP (**Id**entificación **vo**luntaria de la **ap**licación), que ofrece una capa adicional de seguridad para tu aplicación. Puedes examinar los ejemplos del libro de recetas del servicio *workers*, intenta configurar un servidor de mensajería `push` usando Firebase, o crea tu propio servidor (utilizando Node.js, por ejemplo).

Como se mencionó anteriormente, para poder recibir mensajes `push`, debes tener un servicio *worker*, cuyos conceptos básicos ya se explican en Cómo hacer que las PWAs funcionen sin conexión con el servicio workers (en-US). Dentro del servicio *workers*, se crea un mecanismo de suscripción del servicio `push`.


```js
registration.pushManager.getSubscription().then(/\* ... \*/);
```


Una vez que el usuario está suscrito, puede recibir notificaciones automáticas del servidor.

Desde el lado del servidor, todo el proceso tiene que estar encriptado con claves públicas y privadas por razones de seguridad — permitir que todos envíen mensajes `push` sin seguridad usando tu aplicación sería una idea terrible. Consulta la página de prueba de encriptación de datos `Push` en la Web para obtener información detallada sobre cómo proteger el servidor. El servidor almacena toda la información recibida cuando el usuario se suscribió, por lo que los mensajes se pueden enviar más tarde cuando sea necesario.

Para recibir mensajes `push`, podemos escuchar el evento `push` en el archivo `Service Worker`:


```js
self.addEventListener("push", function (e) {
  /\* ... \*/
});
```


Los datos se pueden recuperar y luego mostrar como una notificación al usuario inmediatamente. Esto, por ejemplo, se puede usar para recordarle algo al usuario o para informarle sobre contenido nuevo disponible en la aplicación.

### Ejemplo `push`

`Push` necesita que la parte del servidor funcione, por lo que no podemos incluirla en el ejemplo js13kPWA alojado en las páginas de GitHub, ya que solo ofrece alojamiento de archivos estáticos. Todo se explica en el Libro de recetas para servicios *worker*; consulta el Demo de carga `push`.

Esta demostración consta de tres archivos:

* index.js, que contiene el código fuente de nuestra aplicación
* server.js, que contiene la parte del servidor (escrito en Node.js)
* service-worker.js, que contiene el código específico de `Service Worker`.

Exploremos todos estos

#### `index.js`

El archivo `index.js` comienza registrando el servicio `worker`:


```js
navigator.serviceWorker
  .register("service-worker.js")
  .then(function (registration) {
    return registration.pushManager
      .getSubscription()
      .then(async function (subscription) {
        // parte de registro
      });
  })
  .then(function (subscription) {
    // parte de la suscripción
  });
```


Es un poco más complicado que el servicio *worker* que vimos en la demostración de js13kPWA. En este caso particular, después de registrarse, usamos el objeto de registro para suscribirnos y luego usamos el objeto de suscripción resultante para completar todo el proceso.

En la parte de registro, el código se ve así:


```js
if (subscription) {
  return subscription;
}
```


Si el usuario ya se ha suscrito, devolvemos el objeto de suscripción y pasamos a la parte de suscripción. Si no, iniciamos una nueva suscripción:


```js
const response = await fetch("./vapidPublicKey");
const vapidPublicKey = await response.text();
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
```


La aplicación obtiene la clave pública del servidor y convierte la respuesta en texto; luego se debe convertir a un Uint8Array (para admitir Chrome). Para obtener más información sobre las claves *IDVAP*, puedes leer Envío de notificaciones *WebPush* identificadas por *IDVAP* a través de la publicación de blog del servicio `Push` de Mozilla.

La aplicación ahora puede usar `PushManager` para suscribir al nuevo usuario. Hay dos opciones pasadas al método `PushManager.subscribe()` (en-US) — la primera es `userVisibleOnly: true`, lo cual significa que todas las notificaciones enviadas al usuario serán visibles para ellos, y el segundo es `applicationServerKey`, que contiene nuestra clave *IDVAP* adquirida y convertida con éxito.


```js
return registration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: convertedVapidKey,
});
```


Ahora pasemos a la parte de la suscripción: la aplicación primero envía los detalles de la suscripción como JSON al servidor mediante `Fetch`.


```js
fetch("./register", {
  method: "post",
  headers: {
    "Content-type": "application/json",
  },
  body: JSON.stringify({
    subscription: subscription,
  }),
});
```


Luego, se define la función `GlobalEventHandlers.onclick` en el botón *Suscribirse*:


```js
document.getElementById("doIt").onclick = function () {
  const payload = document.getElementById("notification-payload").value;
  const delay = document.getElementById("notification-delay").value;
  const ttl = document.getElementById("notification-ttl").value;

  fetch("./sendNotification", {
    method: "post",
    headers: {
      "Content-type": "application/json",
    },
    body: JSON.stringify({
      subscription: subscription,
      payload: payload,
      delay: delay,
      ttl: ttl,
    }),
  });
};
```


Cuando se hace clic en el botón, `fetch` solicita al servidor que envíe la notificación con los parámetros dados: `payload` es el texto que se mostrará en la notificación, `delay` define un retraso en segundos hasta que se muestra la notificación, y `ttl` es la configuración de tiempo de vida que mantiene la notificación disponible en el servidor durante un período de tiempo específico, también definido en segundos.

Ahora, en el siguiente archivo JavaScript.

#### `server.js`

La parte del servidor está escrita en Node.js y se debe alojar en un lugar adecuado, que es un tema de un artículo completamente separado. Aquí solo proporcionaremos una descripción general de alto nivel.

El módulo `web-push` se utiliza para configurar las claves `IDVAP` y, opcionalmente, generarlas si aún no están disponibles.


```js
const webPush = require("web-push");

if (!process.env.VAPID\_PUBLIC\_KEY || !process.env.VAPID\_PRIVATE\_KEY) {
  console.log(
    "Debes configurar las variables de entorno VAPID\_PUBLIC\_KEY y " +
      "VAPID\_PRIVATE\_KEY. Puedes utilizar las siguientes: ",
  );
  console.log(webPush.generateVAPIDKeys());
  return;
}

webPush.setVapidDetails(
  "https://github.com/mdn/serviceworker-cookbook/",
  process.env.VAPID\_PUBLIC\_KEY,
  process.env.VAPID\_PRIVATE\_KEY,
);
```


A continuación, un módulo define y exporta todas las rutas que una aplicación necesita manejar: obtener la clave pública *IDVAP*, registrarse y luego enviar notificaciones. Puedes ver las variables del archivo `index.js` que se está utilizando: `payload`, `delay` y `ttl`.


```js
module.exports = function (app, route) {
  app.get(route + "vapidPublicKey", function (req, res) {
    res.send(process.env.VAPID\_PUBLIC\_KEY);
  });

  app.post(route + "register", function (req, res) {
    res.sendStatus(201);
  });

  app.post(route + "sendNotification", function (req, res) {
    const subscription = req.body.subscription;
    const payload = req.body.payload;
    const options = {
      TTL: req.body.ttl,
    };

    setTimeout(function () {
      webPush
        .sendNotification(subscription, payload, options)
        .then(function () {
          res.sendStatus(201);
        })
        .catch(function (error) {
          console.log(error);
          res.sendStatus(500);
        });
    }, req.body.delay \* 1000);
  });
};
```


#### `service-worker.js`

El último archivo que veremos es el del servicio *worker*:


```js
self.addEventListener("push", function (event) {
  const payload = event.data ? event.data.text() : "no payload";
  event.waitUntil(
    self.registration.showNotification("ServiceWorker Cookbook", {
      body: payload,
    }),
  );
});
```


Todo lo que hace es agregar un escucha para el evento `push`, crear la variable de carga útil que consiste en el texto tomado de los datos (o crear una cadena para usar si los datos están vacíos), y luego esperar hasta la notificación se muestra al usuario.

No dudes en explorar el resto de los ejemplos en el Libro de recetas para el servicio *workers* si deseas saber cómo se manejan: el código fuente completo está disponible en GitHub. Hay una gran colección de ejemplos de uso que muestran el uso general, pero también la inserción web, las estrategias de almacenamiento en caché, el rendimiento, el trabajo sin conexión y más.

* Anterior  (en-US)
* Overview: Progressive (en-US)
* Siguiente  (en-US)