Suscribir a un usuario

El primer paso es obtener el permiso del usuario para enviarle mensajes push y, luego, podemos obtener un PushSubscription.

La API de JavaScript para hacerlo es bastante sencilla, así que analicemos el flujo de lógica.

Detección de atributos

Primero, debemos verificar si el navegador actual admite la mensajería push. Podemos verificar si la función push es compatible con dos verificaciones simples.

  1. Busca serviceWorker en navigator.
  2. Busca PushManager en window.
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

Si bien la compatibilidad del navegador crece rápidamente para el trabajador de servicio y los mensajes push, siempre es una buena idea detectar funciones para ambos y mejorar de forma progresiva.

Registra un service worker

Con el detector de funciones, sabemos que se admiten los trabajadores del servicio y los envíos. El siguiente paso es “registrar” nuestro service worker.

Cuando registramos un service worker, le indicamos al navegador dónde se encuentra nuestro archivo. El archivo sigue siendo solo JavaScript, pero el navegador le “dará acceso” a las APIs del service worker, incluido el push. Para ser más exactos, el navegador ejecuta el archivo en un entorno de trabajador de servicio.

Para registrar un trabajador de servicio, llama a navigator.serviceWorker.register() y pasa la ruta de acceso a nuestro archivo. Sería algo como lo siguiente:

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

Esta función le indica al navegador que tenemos un archivo de service worker y dónde se encuentra. En este caso, el archivo del service worker se encuentra en /service-worker.js. En segundo plano, el navegador seguirá los siguientes pasos después de llamar a register():

  1. Descarga el archivo del servicio trabajador.

  2. Ejecuta el código JavaScript.

  3. Si todo se ejecuta correctamente y no hay errores, se resolverá la promesa que muestra register(). Si hay errores de cualquier tipo, se rechazará la promesa.

Si register() lo rechaza, vuelve a verificar si hay errores tipográficos o de otro tipo en tu código JavaScript en Chrome DevTools.

Cuando register() se resuelve, muestra un ServiceWorkerRegistration. Usaremos este registro para acceder a la API de PushManager.

Compatibilidad del navegador con la API de PushManager

Navegadores compatibles

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Origen

Cómo solicitar permiso

Registramos nuestro trabajador de servicio y estamos listos para suscribir al usuario. El siguiente paso es obtener su permiso para enviarle mensajes push.

La API para obtener permiso es relativamente simple. El inconveniente es que recientemente cambió de tomar una devolución de llamada a mostrar una promesa. El problema con esto es que no podemos saber qué versión de la API implementa el navegador actual, por lo que debes implementar ambas y controlarlas.

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

En el código anterior, el fragmento de código importante es la llamada a Notification.requestPermission(). Este método mostrará un mensaje al usuario:

Mensaje de permiso en Chrome para computadoras y dispositivos móviles

Una vez que el usuario haya interactuado con la solicitud de permiso presionando Permitir, Bloquear o simplemente cerrándola, se nos mostrará el resultado como una cadena: 'granted', 'default' o 'denied'.

En el código de ejemplo anterior, la promesa que muestra askPermission() se resuelve si se otorga el permiso. De lo contrario, arrojamos un error que hace que se rechace la promesa.

Un caso extremo que debes controlar es si el usuario hace clic en el botón "Bloquear". Si esto ocurre, tu app web no podrá volver a solicitarle permiso al usuario. Tendrá que “desbloquear” tu app de forma manual cambiando su estado de permiso, que se encuentra oculto en un panel de configuración. Piensa cuidadosamente cómo y cuándo le pedirás permiso al usuario, ya que, si hace clic en bloquear, no es fácil revertir esa decisión.

La buena noticia es que la mayoría de los usuarios otorgan el permiso con gusto, siempre que saben por qué se les solicita.

Más adelante, veremos cómo algunos sitios populares solicitan permiso.

Cómo suscribir a un usuario con PushManager

Una vez que tengamos registrado nuestro service worker y tengamos permiso, podremos suscribir a un usuario llamando a registration.pushManager.subscribe().

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Cuando llamamos al método subscribe(), pasamos un objeto options, que consta de parámetros opcionales y obligatorios.

Veamos todas las opciones que podemos pasar.

Opciones de userVisibleOnly

Cuando se agregó la función push a los navegadores por primera vez, había incertidumbre sobre si los desarrolladores deberían poder enviar un mensaje push y no mostrar una notificación. Por lo general, se conoce como push silencioso, ya que el usuario no sabe que ocurrió algo en segundo plano.

La preocupación era que los desarrolladores pudieran hacer cosas desagradables, como hacer un seguimiento de la ubicación de un usuario de forma continua sin que este lo sepa.

Para evitar esta situación y darles tiempo a los autores de especificaciones para que consideren la mejor manera de admitir esta función, se agregó la opción userVisibleOnly y pasar un valor de true es un acuerdo simbólico con el navegador de que la app web mostrará una notificación cada vez que se reciba un mensaje push (es decir, no hay un mensaje push silencioso).

Por el momento, debes pasar un valor de true. Si no incluyes la clave userVisibleOnly o pasas false, recibirás el siguiente error:

Actualmente, Chrome solo admite la API de Push para suscripciones que generarán mensajes visibles para el usuario. Para indicar esto, llama a pushManager.subscribe({userVisibleOnly: true}). Consulta https://goo.gl/yqv4Q4 para obtener más información.

Actualmente, parece que los mensajes push silenciosos generales nunca se implementarán en Chrome. En cambio, los autores de especificaciones están explorando la noción de una API de presupuesto que permitirá a las apps web una cierta cantidad de mensajes push silenciosos en función del uso de una app web.

Opción applicationServerKey

Mencionamos brevemente las "claves del servidor de aplicaciones" en la sección anterior. Un servicio push usa las "claves de servidor de la aplicación" para identificar la aplicación que suscribe a un usuario y garantizar que la misma aplicación le envíe mensajes a ese usuario.

Las claves del servidor de aplicaciones son un par de claves públicas y privadas que son únicas para tu aplicación. La clave privada debe mantenerse en secreto para tu aplicación, y la clave pública se puede compartir libremente.

La opción applicationServerKey que se pasa a la llamada subscribe() es la clave pública de la aplicación. El navegador lo pasa a un servicio push cuando suscribe al usuario, lo que significa que el servicio push puede vincular la clave pública de tu aplicación al PushSubscription del usuario.

En el siguiente diagrama, se ilustran estos pasos.

  1. Tu app web se carga en un navegador y llamas a subscribe(), pasando tu clave pública del servidor de aplicaciones.
  2. Luego, el navegador realiza una solicitud de red a un servicio push que generará un extremo, lo asociará con la clave pública de la aplicación y lo devolverá al navegador.
  3. El navegador agregará este extremo a PushSubscription, que se muestra a través de la promesa subscribe().

Ilustración de la clave pública del servidor de aplicaciones que se usa en el método de suscripción.

Cuando quieras enviar un mensaje push más adelante, deberás crear un encabezado Authorization que contendrá información firmada con la clave privada del servidor de aplicaciones. Cuando el servicio de notificaciones push recibe una solicitud para enviar un mensaje push, puede validar este encabezado Authorization firmado buscando la clave pública vinculada al extremo que recibe la solicitud. Si la firma es válida, el servicio de push sabe que debe provenir del servidor de aplicaciones con la clave privada coincidente. Básicamente, es una medida de seguridad que evita que cualquier otra persona envíe mensajes a los usuarios de una aplicación.

Cómo se usa la clave privada del servidor de la aplicación cuando se envía un mensaje

Técnicamente, applicationServerKey es opcional. Sin embargo, la implementación más fácil en Chrome la requiere, y es posible que otros navegadores también lo requieran en el futuro. Es opcional en Firefox.

La especificación que define qué debe ser la clave del servidor de aplicaciones es la especificación de VAPID. Cada vez que leas algo que haga referencia a "claves del servidor de aplicaciones" o "claves de VAPID", recuerda que son lo mismo.

Cómo crear claves de servidor de aplicaciones

Para crear un conjunto público y privado de claves del servidor de aplicaciones, visita web-push-codelab.glitch.me o usa la línea de comandos web-push para generar claves. Para ello, haz lo siguiente:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

Solo debes crear estas claves una vez para tu aplicación. Solo asegúrate de mantener la clave privada en privado. (Sí, lo acabo de decir).

Permisos y subscribe()

Llamar a subscribe() tiene un efecto secundario. Si tu app web no tiene permisos para mostrar notificaciones en el momento de llamar a subscribe(), el navegador te solicitará los permisos. Esto es útil si tu IU funciona con este flujo, pero si deseas tener más control (y creo que la mayoría de los desarrolladores lo harán), quédate con la API de Notification.requestPermission() que usamos antes.

¿Qué es una PushSubscription?

Llamamos a subscribe(), pasamos algunas opciones y, a cambio, obtenemos una promesa que se resuelve en un PushSubscription, lo que genera un código como el siguiente:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

El objeto PushSubscription contiene toda la información necesaria para enviar un mensaje push a ese usuario. Si imprimes el contenido con JSON.stringify(), verás lo siguiente:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint es la URL de los servicios push. Para activar un mensaje push, realiza una solicitud POST a esta URL.

El objeto keys contiene los valores que se usan para encriptar los datos de los mensajes enviados con un mensaje push (que analizaremos más adelante en esta sección).

Resubscripción periódica para evitar el vencimiento

Cuando te suscribes a las notificaciones push, sueles recibir un PushSubscription.expirationTime de null. En teoría, esto significa que la suscripción nunca vence (a diferencia de cuando recibes un DOMHighResTimeStamp, que te indica el momento exacto en que vence la suscripción). Sin embargo, en la práctica, es común que los navegadores permitan que las suscripciones venzan, por ejemplo, si no se recibieron notificaciones push durante un período prolongado o si el navegador detecta que el usuario no está usando una app que tenga el permiso de notificaciones push. Un patrón para evitar esto es volver a suscribir al usuario cada vez que recibe una notificación, como se muestra en el siguiente fragmento. Esto requiere que envíes notificaciones con la frecuencia suficiente para que el navegador no venza automáticamente la suscripción. Además, debes sopesar cuidadosamente las ventajas y desventajas de las necesidades legítimas de notificaciones en comparación con el envío de spam al usuario de forma involuntaria solo para que no venza la suscripción. En última instancia, no debes intentar luchar contra el navegador en sus esfuerzos por proteger al usuario de suscripciones a notificaciones olvidadas hace mucho tiempo.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Envía una suscripción a tu servidor

Una vez que tengas una suscripción push, deberás enviarla a tu servidor. Depende de ti cómo hacerlo, pero una pequeña sugerencia es usar JSON.stringify() para obtener todos los datos necesarios del objeto de suscripción. Como alternativa, puedes obtener el mismo resultado de forma manual de la siguiente manera:

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

Para enviar la suscripción, sigue estos pasos en la página web:

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 de nodos recibe esta solicitud y guarda los datos en una base de datos para usarlos más adelante.

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

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

Con los detalles de PushSubscription en nuestro servidor, podemos enviarle un mensaje al usuario cuando queramos.

Resubscripción periódica para evitar el vencimiento

Cuando te suscribes a las notificaciones push, sueles recibir un PushSubscription.expirationTime de null. En teoría, esto significa que la suscripción nunca vence (a diferencia de cuando recibes un DOMHighResTimeStamp, que te indica el momento exacto en que vence la suscripción). Sin embargo, en la práctica, es común que los navegadores permitan que las suscripciones venzan, por ejemplo, si no se recibieron notificaciones push durante mucho tiempo o si el navegador detecta que el usuario no está usando la app que tiene el permiso de notificaciones push. Un patrón para evitar esto es volver a suscribir al usuario cada vez que recibe una notificación, como se muestra en el siguiente fragmento. Esto requiere que envíes notificaciones con la frecuencia suficiente para que el navegador no venza automáticamente la suscripción. Además, debes sopesar con mucho cuidado las ventajas y desventajas de las necesidades legítimas de notificaciones en comparación con el envío de spam al usuario solo para que no venza la suscripción. En última instancia, no debes intentar luchar contra el navegador en sus esfuerzos por proteger al usuario de suscripciones a notificaciones olvidadas hace mucho tiempo.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Preguntas frecuentes

Estas son algunas preguntas frecuentes que las personas hicieron en este punto:

¿Puedo cambiar el servicio push que usa un navegador?

No. El navegador selecciona el servicio push y, como vimos con la llamada a subscribe(), el navegador hará solicitudes de red al servicio push para recuperar los detalles que conforman PushSubscription.

Cada navegador usa un servicio push diferente, ¿no tienen APIs diferentes?

Todos los servicios push esperarán la misma API.

Esta API común se denomina Protocolo de notificaciones push web y describe la solicitud de red que deberá realizar tu aplicación para activar un mensaje push.

Si suscribo a un usuario en su computadora de escritorio, ¿también se suscribe en su teléfono?

Lamentablemente, no. Un usuario debe registrarse para recibir notificaciones push en cada navegador en el que desee recibir mensajes. También es importante tener en cuenta que esto requerirá que el usuario otorgue permiso en cada dispositivo.

Próximos pasos

Code labs