Comunicación bidireccional con los service workers

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

En algunos casos, es posible que una app web deba establecer un canal de comunicación doble entre los y el service worker.

Por ejemplo, en una AWP de podcast, se podría crear una función que le permita al usuario descargar episodios para para el consumo sin conexión service worker para mantener la página informada con regularidad sobre el progreso, de modo que la página principal de acceso puede actualizar la IU.

En esta guía, exploraremos las diferentes formas de implementar una comunicación doble vía entre con los comandos Window y service trabajador, ya que exploras diferentes APIs, la biblioteca de Workbox algunos casos avanzados.

Diagrama que muestra un service worker y la página intercambiando mensajes.

Uso de Workbox

workbox-window es un conjunto de módulos de la biblioteca de Workbox para que se ejecute en el contexto de la ventana. El Workbox proporciona un método messageSW() para enviar un mensaje al service worker registrado de la instancia. a la espera de una respuesta.

El siguiente código de página crea una nueva instancia de Workbox y envía un mensaje al service worker para obtener su versión:

const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);

El service worker implementa un objeto de escucha de mensajes en el otro extremo y responde al service worker:

const SW_VERSION = '1.0.0';

self.addEventListener('message', (event) => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

De forma interna, la biblioteca utiliza una API de navegador que revisaremos en la siguiente sección: Message Channel, pero abstrae muchas detalles de implementación, lo que facilita su uso, a la vez que se aprovecha el amplio navegador la compatibilidad que tiene esta API.

Diagrama que muestra la comunicación bidireccional entre la página y el service worker, con una ventana de Workbox.

Uso de las APIs de navegador

Si la biblioteca de Workbox no es suficiente para tus necesidades, hay varias APIs de nivel inferior disponibles para implementar la comunicación "bidireccional" entre las páginas y los service workers. Tienen algunas similitudes y diferencias:

Similitudes:

  • En todos los casos, la comunicación comienza en un extremo a través de la interfaz postMessage() y se recibe. del otro lado implementando un controlador message.
  • En la práctica, todas las APIs disponibles nos permiten implementar los mismos casos de uso, pero puede simplificar el desarrollo en algunos casos.

Diferencias:

  • Tienen diferentes formas de identificar el otro lado de la comunicación: algunos usan un referencia explícita al otro contexto, mientras que otros se pueden comunicar implícitamente a través de un proxy instancia en cada lado.
  • La compatibilidad con los navegadores varía entre ellos.
Diagrama que muestra la comunicación bidireccional entre la página y el service worker, y las APIs de navegador disponibles.

API de Broadcast Channel

Navegadores compatibles

  • Chrome: 54.
  • Borde: 79.
  • Firefox: 38.
  • Safari: 15.4.

Origen

La API de Broadcast Channel permite la comunicación básica entre contextos de navegación a través de BroadcastChannel de objetos.

Para implementarlo, primero, cada contexto debe crear una instancia de un objeto BroadcastChannel con el mismo ID y enviar y recibir mensajes de ella:

const broadcast = new BroadcastChannel('channel-123');

El objeto BroadcastChannel expone una interfaz postMessage() para enviar un mensaje a cualquier persona que esté escuchando. contexto:

//send message
broadcast.postMessage({ type: 'MSG_ID', });

Cualquier contexto del navegador puede escuchar mensajes a través del método onmessage de BroadcastChannel. objeto:

//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};

Como se ve, no hay referencia explícita a un contexto en particular, así que no es necesario obtener al service worker o a un cliente en particular.

Diagrama que muestra la comunicación bidireccional entre la página y el service worker, mediante un objeto Broadcast Channel.

La desventaja es que, en el momento de la redacción de este documento, la API es compatible con Chrome, Firefox y Edge, pero otros navegadores, como Safari, no lo admiten. aún.

API del cliente

Navegadores compatibles

  • Chrome: 40.
  • Límite: 17.
  • Firefox: 44.
  • Safari: 11.1.

Origen

La API de cliente te permite obtener una Referencia a todos los objetos WindowClient que representan las pestañas activas que controla el service worker

Dado que la página está controlada por un solo service worker, escucha y envía mensajes al Service worker activo directamente a través de la interfaz serviceWorker:

//send message
navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
});

//listen to messages
navigator.serviceWorker.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process response
  }
};

De manera similar, para escuchar los mensajes, el service worker implementa un objeto de escucha onmessage:

//listen to messages
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //Process message
  }
});

Para comunicarse con cualquiera de sus clientes, el service worker obtiene un array de WindowClient mediante la ejecución de métodos como Clients.matchAll() y Clients.get() Luego, puede postMessage() cualquiera de ellos:

//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
  if (clients && clients.length) {
    //Respond to last focused tab
    clients[0].postMessage({type: 'MSG_ID'});
  }
});
Diagrama que muestra un service worker que se comunica con un array de clientes.

Client API es una buena opción para comunicarse fácilmente con todas las pestañas activas de un service worker. de una manera relativamente sencilla. La API es compatible con todas las principales navegadores, pero no todos sus métodos pueden estar disponibles, así que asegúrate de verificar la compatibilidad del navegador antes implementarlo en tu sitio.

Canal de mensajes

Navegadores compatibles

  • Chrome: 2.
  • Límite: 12.
  • Firefox: 41.
  • Safari: 5.

Origen

Message Channel requiere definir y pasar un puerto de un contexto a otro para establecer una comunicación en dos direcciones canal.

Para inicializar el canal, la página crea una instancia de un objeto MessageChannel y lo usa. para enviar un puerto al service worker registrado. La página también implementa un objeto de escucha onmessage en para recibir mensajes del otro contexto:

const messageChannel = new MessageChannel();

//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
Diagrama que muestra una página que pasa un puerto a un service worker para establecer una comunicación bidireccional.

El service worker recibe el puerto, guarda una referencia a él y lo usa para enviar un mensaje al otro lado:

let communicationPort;

//Save reference to port
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

//Send messages
communicationPort.postMessage({type: 'MSG_ID'});

Actualmente, MessageChannel es compatible con todas las principales del navegador.

APIs avanzadas: sincronización y recuperación en segundo plano

En esta guía, exploramos formas de implementar técnicas de comunicación bidireccional para lograr casos simples, como pasar un mensaje de cadena que describe la operación que se realizará o una lista de URLs almacenar en caché de un contexto a otro. En esta sección, exploraremos dos APIs para controlar como la falta de conectividad y las descargas prolongadas.

Sincronización en segundo plano

Navegadores compatibles

  • Chrome: 49.
  • Borde: 79.
  • Firefox: No es compatible.
  • Safari: no es compatible.

Origen

Es posible que una app de chat quiera asegurarse de que los mensajes nunca se pierdan por una mala conectividad. El La API de Background Sync te permite aplazar las acciones para que se vuelvan a intentar cuando el usuario tenga una conectividad estable Esto es útil para garantizar que lo que el usuario quiere enviar, en realidad se envía.

En lugar de la interfaz postMessage(), la página registra un sync:

navigator.serviceWorker.ready.then(function (swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});

Luego, el service worker escucha el evento sync para procesar el mensaje:

self.addEventListener('sync', function (event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});

La función doSomeStuff() debe mostrar una promesa que indique el éxito o fracaso de lo que fue. lo que intentan hacer. Si se cumple, la sincronización se completa. Si falla, se programará otra sincronización para vuelve a intentarlo. Las sincronizaciones de reintento también esperan la conectividad y emplean una retirada exponencial.

Una vez realizada la operación, el service worker puede volver a comunicarse con la página para actualizar la IU usando cualquiera de las APIs de comunicación exploradas anteriormente.

La Búsqueda de Google usa la sincronización en segundo plano para conservar las consultas fallidas por mala conectividad y reintentar cuando el usuario esté en línea. Una vez realizada la operación, comunican el resultado a al usuario mediante una notificación push web:

Diagrama que muestra una página que pasa un puerto a un service worker para establecer una comunicación bidireccional.

Recuperación en segundo plano

Navegadores compatibles

  • Chrome: 74.
  • Borde: 79.
  • Firefox: No es compatible.
  • Safari: no es compatible.

Origen

Para trabajos relativamente cortos, como enviar un mensaje o una lista de URLs para almacenar en caché, las opciones explorado hasta ahora son una buena opción. Si la tarea tarda demasiado, el navegador cerrará el servicio. de lo contrario, representa un riesgo para la privacidad del usuario y la batería.

La API de Background Fetch te permite transferir una tarea larga a un service worker, como la descarga de películas, podcasts o niveles. de un juego.

Para comunicarte con el service worker desde la página, usa backgroundFetch.fetch, en lugar de postMessage():

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch(
    'my-fetch',
    ['/ep-5.mp3', 'ep-5-artwork.jpg'],
    {
      title: 'Episode 5: Interesting things.',
      icons: [
        {
          sizes: '300x300',
          src: '/ep-5-icon.png',
          type: 'image/png',
        },
      ],
      downloadTotal: 60 * 1024 * 1024,
    },
  );
});

El objeto BackgroundFetchRegistration permite que la página escuche el evento progress para seguir. el progreso de la descarga:

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(
    (bgFetch.downloaded / bgFetch.downloadTotal) * 100,
  );
  console.log(`Download progress: ${percent}%`);
});
Diagrama que muestra una página que pasa un puerto a un service worker para establecer una comunicación bidireccional.
La IU se actualiza para indicar el progreso de una descarga (izquierda). Gracias a los service workers, la operación puede seguir ejecutándose incluso después de cerrar todas las pestañas (derecha).

Próximos pasos

En esta guía, exploramos el caso más general de la comunicación entre los service workers y las páginas. (comunicación bidireccional).

Muchas veces, es posible que uno solo necesite un contexto para comunicarse con el otro, sin recibir un respuesta. Consulta las siguientes guías para obtener orientación sobre cómo implementar técnicas unidireccionales en tus páginas desde y hasta el service worker, junto con casos de uso y ejemplos de producción: