Transmite actualizaciones a las páginas con service workers

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

En algunas situaciones, es posible que el service worker deba comunicarse de manera proactiva con cualquiera de las pestañas activas que controla para informar sobre un evento determinado. Los siguientes son algunos ejemplos:

  • Informar a la página cuándo se instaló una nueva versión del service worker, de modo que el usuario pueda ver en la página el botón "Actualizar para actualizar" y así acceder a la nueva funcionalidad de inmediato
  • Informar al usuario sobre un cambio en los datos almacenados en caché que se produjo en el servicio trabajador, mostrando una indicación, como "La app ya está lista para funcionar sin conexión" o "Hay una versión nueva del contenido disponible"
Diagrama que muestra un service worker que se comunica con la página para enviar una actualización.

Llamaremos a estos tipos de casos de uso en los que el trabajador de servicio no necesita recibir un mensaje de la página para iniciar una comunicación "actualizaciones de transmisión". En esta guía, revisaremos diferentes formas de implementar este tipo de comunicación entre páginas y trabajadores del servicio con las APIs de navegador estándares y la biblioteca Workbox.

Casos de producción

Tinder

La AWP de Tinder usa workbox-window para escuchar momentos importantes del ciclo de vida del trabajador de servicio desde la página ("instalado", "controlado" y "activado"). De esta manera, cuando se activa un nuevo service worker, se muestra un banner "Update Available" para que el usuario pueda actualizar la AWP y acceder a las funciones más recientes:

Captura de pantalla de la funcionalidad "Actualización disponible" de la aplicación web de Tinder.
En la AWP de Tinder, el trabajador de servicio le indica a la página que hay una versión nueva lista, y la página les muestra a los usuarios un banner que indica que hay una actualización disponible.

Squoosh

En la PWA de Squaoosh, cuando el trabajador de servicio almacena en caché todos los recursos necesarios para que funcione sin conexión, envía un mensaje a la página para mostrar un aviso emergente "Listo para funcionar sin conexión", que le informa al usuario sobre la función:

Captura de pantalla de la funcionalidad "Listo para funcionar sin conexión" de la app web de Squoosh.
En la AWP de Squoosh, el trabajador de servicio transmite una actualización a la página cuando la caché está lista, y la página muestra el aviso "Listo para funcionar sin conexión".

Uso de Workbox

Cómo escuchar eventos del ciclo de vida del servicio de trabajo

workbox-window proporciona una interfaz sencilla para escuchar eventos importantes del ciclo de vida de los service workers. En el fondo, la biblioteca usa APIs del cliente, como updatefound y statechange, y proporciona objetos de escucha de eventos de nivel superior en el objeto workbox-window, lo que facilita que el usuario consuma estos eventos.

El siguiente código de página te permite detectar cada vez que se instala una versión nueva del trabajador de servicio para que puedas comunicarlo al usuario:

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

wb.addEventListener('installed', (event) => {
  if (event.isUpdate) {
    // Show "Update App" banner
  }
});

wb.register();

Informa a la página sobre los cambios en los datos de la caché

El paquete Workbox workbox-broadcast-update proporciona una forma estándar de notificar a los clientes de Windows que se actualizó una respuesta almacenada en caché. Por lo general, se usa junto con la estrategia StaleWhileRevalidate.

Para transmitir actualizaciones, agrega un broadcastUpdate.BroadcastUpdatePlugin a tus opciones de estrategia en el lado del trabajador de servicio:

import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';

registerRoute(
  ({url}) => url.pathname.startsWith('/api/'),
  new StaleWhileRevalidate({
    plugins: [
      new BroadcastUpdatePlugin(),
    ],
  })
);

En tu app web, puedes escuchar estos eventos, como los siguientes:

navigator.serviceWorker.addEventListener('message', async (event) => {
  // Optional: ensure the message came from workbox-broadcast-update
  if (event.data.meta === 'workbox-broadcast-update') {
    const {cacheName, updatedUrl} = event.data.payload;

    // Do something with cacheName and updatedUrl.
    // For example, get the cached content and update
    // the content on the page.
    const cache = await caches.open(cacheName);
    const updatedResponse = await cache.match(updatedUrl);
    const updatedText = await updatedResponse.text();
  }
});

Uso de las APIs del navegador

Si la funcionalidad que proporciona Workbox no es suficiente para tus necesidades, usa las siguientes APIs del navegador para implementar "actualizaciones de transmisión":

API de Broadcast Channel

El service worker crea un objeto BroadcastChannel y comienza a enviarle mensajes. Cualquier contexto (p. ej., una página) interesado en recibir estos mensajes puede crear una instancia de un objeto BroadcastChannel e implementar un controlador de mensajes para recibirlos.

Para informar a la página cuando se instala un nuevo trabajador de servicio, usa el siguiente código:

// Create Broadcast Channel to send messages to the page
const broadcast = new BroadcastChannel('sw-update-channel');

self.addEventListener('install', function (event) {
  // Inform the page every time a new service worker is installed
  broadcast.postMessage({type: 'CRITICAL_SW_UPDATE'});
});

La página escucha estos eventos suscribiéndose a sw-update-channel:

// Create Broadcast Channel and listen to messages sent to it
const broadcast = new BroadcastChannel('sw-update-channel');

broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'CRITICAL_SW_UPDATE') {
    // Show "update to refresh" banner to the user.
  }
};

Esta es una técnica simple, pero su limitación es la compatibilidad con el navegador: en el momento de escribir este artículo, Safari no admite esta API.

API del cliente

La API de cliente proporciona una forma directa de comunicarse con varios clientes desde el trabajador de servicio iterando sobre un array de objetos Client.

Usa el siguiente código de trabajador de servicio para enviar un mensaje a la última pestaña enfocada:

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

La página implementa un controlador de mensajes para interceptar estos mensajes:

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

La API de cliente es una excelente opción para casos como la transmisión de información a varias pestañas activas. Todos los navegadores principales admiten la API, pero no todos sus métodos. Verifica la compatibilidad del navegador antes de usarlo.

Canal de mensajes

El canal de mensajes requiere un paso de configuración inicial, ya que pasa un puerto de la página al trabajador de servicio para establecer un canal de comunicación entre ellos. La página crea una instancia de un objeto MessageChannel y pasa un puerto al service worker a través de la interfaz postMessage():

const messageChannel = new MessageChannel();

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

La página escucha los mensajes mediante la implementación de un controlador "onmessage" en ese puerto:

// Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};

El service worker recibe el puerto y guarda una referencia a él:

// Initialize
let communicationPort;

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

A partir de ese momento, puede enviar mensajes a la página llamando a postMessage() en la referencia al puerto:

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

MessageChannel puede ser más complejo de implementar, debido a la necesidad de inicializar puertos, pero es compatible con todos los navegadores principales.

Próximos pasos

En esta guía, exploramos un caso particular de comunicación de Windows con el trabajador de servicio: "Actualizaciones de transmisión". Entre los ejemplos explorados, se incluyen la escucha de eventos importantes del ciclo de vida del service worker y la comunicación a la página sobre cambios en el contenido o los datos almacenados en caché. Puedes pensar en casos de uso más interesantes en los que el service worker se comunica de manera proactiva con la página, sin recibir ningún mensaje antes.

Para obtener más patrones de comunicación de Window y el trabajador de servicio, consulta lo siguiente:

Recursos adicionales