Transmite actualizaciones a las páginas con service workers

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

En algunas situaciones, el service worker puede necesitar comunicarse de manera proactiva con cualquiera de las pestañas activas que controla para informar un evento determinado. Los ejemplos incluyen:

  • Se le informa a la página cuándo se instaló una versión nueva del service worker, de modo que la página pueda mostrar el botón "Update to refresh" al usuario para que acceda a la funcionalidad nueva de inmediato.
  • Informa al usuario sobre un cambio en los datos almacenados en caché que se produjo en el service worker. Para ello, se debe mostrar una indicación como "The app is now ready to work offline" o "New version of the content available".
Diagrama en el que se muestra un service worker que se comunica con la página para enviar una actualización.

Llamaremos “publicar actualizaciones” a estos tipos de casos de uso en los que el service worker no necesita recibir un mensaje de la página para comenzar una comunicación. En esta guía, revisaremos diferentes formas de implementar este tipo de comunicación entre páginas y service worker mediante el uso de APIs estándar del navegador y la biblioteca de Workbox.

Casos de producción

Tinder

La AWP de Tinder usa workbox-window para escuchar momentos importantes del ciclo de vida de un service worker desde la página ("instalada", "controlada" y "activada"). De esa manera, cuando entra en juego un service worker nuevo, muestra un banner de "actualización disponible" para que puedan actualizar la AWP y acceder a las funciones más recientes:

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

Squoosh

En la AWP de Squoosh, cuando el service worker almacena en caché todos los recursos necesarios para que funcione sin conexión, envía un mensaje a la página con el aviso de "Listo para trabajar sin conexión" que informa al usuario sobre la función:

Captura de pantalla de la funcionalidad "Listo para trabajar sin conexión" de la aplicación web de Squoosh.
En la AWP de Squoosh, el service worker transmite una actualización a la página cuando la caché está lista, y en ella se muestra el aviso "Listo para trabajar sin conexión".

Usa Workbox

Detecta eventos de ciclo de vida de service worker

workbox-window proporciona una interfaz sencilla para escuchar eventos importantes del ciclo de vida del service worker. De forma interna, la biblioteca usa las 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 la página te permite detectar cada vez que se instala una versión nueva del service worker para que puedas comunicárselo 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 de caja de trabajo workbox-broadcast-update proporciona una forma estándar de notificar a los clientes de ventanas que se actualizó una respuesta almacenada en caché. Este método es el que más se usa junto con la estrategiaStale whileRevalidate.

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

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 de la siguiente manera:

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 recibir mensajes.

Para informar a la página cuando se instala un nuevo service worker, 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'});
});

Para escuchar estos eventos, la página se suscribe 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 en que se redactó este documento, Safari no admite esta API.

API del cliente

La API de cliente proporciona una forma directa de comunicarse con varios clientes desde el service worker mediante la iteración sobre un array de objetos Client.

Usa el siguiente código de service worker 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 Client es una excelente opción para casos como la transmisión de información a varias pestañas activas. La API es compatible con todos los navegadores principales, pero no todos sus métodos lo son. Verifica la compatibilidad del navegador antes de usarlo.

Canal de mensajes

Message Channel requiere un paso de configuración inicial, que consiste en pasar un puerto de la página al service worker, 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,
]);

Para escuchar los mensajes, la página implementa 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 en él:

// Initialize
let communicationPort;

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

Desde ese punto, puede enviar mensajes a la página si llama a postMessage() en la referencia al puerto:

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

Puede que MessageChannel sea 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 ventana a service worker: "broadcast updates". Los ejemplos que se exploran incluyen la detección de eventos importantes del ciclo de vida de un service worker y la comunicación con 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 con la página de forma proactiva, sin recibir ningún mensaje anterior.

Para conocer más patrones de comunicación entre Windows y service worker, consulta lo siguiente:

Recursos adicionales