API de BroadcastChannel: un bus de mensajes para la Web

La API de BroadcastChannel permite que las secuencias de comandos del mismo origen envíen mensajes a otros contextos de navegación. Se puede interpretar como un simple bus de mensajes que permite la semántica de Pub/Sub entre ventanas o pestañas, iframes, trabajadores web y service workers.

Conceptos básicos de API

La API de Broadcast Channel es una API simple que facilita la comunicación entre los contextos de navegación. Es decir, la comunicación entre ventanas o pestañas, iframes, trabajadores web y trabajadores de servicio. Los mensajes que se publican en un canal determinado se entregan a todos los oyentes de ese canal.

El constructor BroadcastChannel toma un solo parámetro: el nombre de un canal. El nombre identifica al canal y se encuentra en todos los contextos de navegación.

// Connect to the channel named "my_bus".
const channel = new BroadcastChannel('my_bus');

// Send a message on "my_bus".
channel.postMessage('This is a test message.');

// Listen for messages on "my_bus".
channel.onmessage = function(e) {
    console.log('Received', e.data);
};

// Close the channel when you're done.
channel.close();

Cómo mandar mensajes

Los mensajes pueden ser cadenas o cualquier elemento compatible con el algoritmo de clonación estructurada (cadenas, objetos, arrays, blobs, ArrayBuffer y mapas).

Ejemplo: envía un BLOB o File

channel.postMessage(new Blob(['foo', 'bar'], {type: 'plain/text'}));

Un canal no se transmitirá a sí mismo. Por lo tanto, si tienes un objeto de escucha onmessage en la misma página que un postMessage() en el mismo canal, no se activará ese evento message.

Diferencias con otras técnicas

En este punto, es posible que te preguntes cómo se relaciona esto con otras técnicas para pasar mensajes, como WebSockets, SharedWorkers, la API de MessageChannel y window.postMessage(). La API de Broadcast Channel no reemplaza estas APIs. Cada uno tiene un propósito. La API de Broadcast Channel está diseñada para que la comunicación entre secuencias de comandos del mismo origen sea sencilla.

Estos son algunos casos de uso de los canales de transmisión:

  • Cómo detectar acciones del usuario en otras pestañas
  • Saber cuándo un usuario accede a una cuenta en otra ventana o pestaña
  • Instruye a un trabajador para que realice un trabajo en segundo plano
  • Saber cuándo un servicio termina de realizar una acción
  • Cuando el usuario suba una foto en una ventana, transfiérela a otras páginas abiertas.

Ejemplo: Página que sabe cuándo el usuario sale, incluso desde otra pestaña abierta en el mismo sitio:

<button id="logout">Logout</button>

<script>
function doLogout() {
    // update the UI login state for this page.
}

const authChannel = new BroadcastChannel('auth');

const button = document.querySelector('#logout');
button.addEventListener('click', e => {
    // A channel won't broadcast to itself so we invoke doLogout()
    // manually on this page.
    doLogout();
    authChannel.postMessage({cmd: 'logout', user: 'Eric Bidelman'});
});

authChannel.onmessage = function(e) {
    if (e.data.cmd === 'logout') {
    doLogout();
    }
};
</script>

En otro ejemplo, supongamos que quieres indicarle a un trabajador de servicio que quite el contenido almacenado en caché después de que el usuario cambie su "configuración de almacenamiento sin conexión" en tu app. Podrías borrar sus cachés con window.caches, pero es posible que el trabajador de servicio ya contenga una utilidad para hacerlo. Podemos usar la API de Broadcast Channel para volver a usar ese código. Sin la API de Broadcast Channel, tendrías que iterar por los resultados de self.clients.matchAll() y llamar a postMessage() en cada cliente para lograr la comunicación de un trabajador de servicio a todos sus clientes (código real que hace eso). El uso de un canal de transmisión hace que sea O(1) en lugar de O(N).

Ejemplo: Instruye a un trabajador de servicio para que quite una caché y reutilice sus métodos de utilidad interna.

En index.html

const channel = new BroadcastChannel('app-channel');
channel.onmessage = function(e) {
    if (e.data.action === 'clearcache') {
    console.log('Cache removed:', e.data.removed);
    }
};

const messageChannel = new MessageChannel();

// Send the service worker a message to clear the cache.
// We can't use a BroadcastChannel for this because the
// service worker may need to be woken up. MessageChannels do that.
navigator.serviceWorker.controller.postMessage({
    action: 'clearcache',
    cacheName: 'v1-cache'
}, [messageChannel.port2]);

En sw.js

function nukeCache(cacheName) {
    return caches.delete(cacheName).then(removed => {
    // ...do more stuff (internal) to this service worker...
    return removed;
    });
}

self.onmessage = function(e) {
    const action = e.data.action;
    const cacheName = e.data.cacheName;

    if (action === 'clearcache') {
    nukeCache(cacheName).then(removed => {
        // Send the main page a response via the BroadcastChannel API.
        // We could also use e.ports[0].postMessage(), but the benefit
        // of responding with the BroadcastChannel API is that other
        // subscribers may be listening.
        const channel = new BroadcastChannel('app-channel');
        channel.postMessage({action, removed});
    });
    }
};

Diferencia con postMessage()

A diferencia de postMessage(), ya no tienes que mantener una referencia a un iframe o un trabajador para comunicarte con él:

// Don't have to save references to window objects.
const popup = window.open('https://another-origin.com', ...);
popup.postMessage('Sup popup!', 'https://another-origin.com');

window.postMessage() también te permite comunicarte entre orígenes. La API de Broadcast Channel es del mismo origen. Dado que se garantiza que los mensajes provengan del mismo origen, no es necesario validarlos como solíamos hacerlo con window.postMessage():

// Don't have to validate the origin of a message.
const iframe = document.querySelector('iframe');
iframe.contentWindow.onmessage = function(e) {
    if (e.origin !== 'https://expected-origin.com') {
    return;
    }
    e.source.postMessage('Ack!', e.origin);
};

Simplemente, "suscríbete" a un canal en particular y disfruta de una comunicación segura y bidireccional.

Diferencia con SharedWorkers

Usa BroadcastChannel para casos simples en los que necesites enviar un mensaje a posiblemente varias ventanas y pestañas, o trabajadores.

Para casos de uso más sofisticados, como administrar bloqueos, estado compartido, sincronizar recursos entre un servidor y varios clientes, o compartir una conexión de WebSocket con un host remoto, los trabajadores compartidos son la solución más apropiada.

Diferencia con la API de MessageChannel

La principal diferencia entre la API de Channel Messaging y BroadcastChannel es que esta última es un medio para enviar mensajes a varios objetos de escucha (uno a muchos). MessageChannel está diseñado para la comunicación directa entre secuencias de comandos. También es más complejo, ya que debes configurar canales con un puerto en cada extremo.

Detección de funciones y compatibilidad con navegadores

Actualmente, Chrome 54, Firefox 38 y Opera 41 admiten la API de Broadcast Channel.

if ('BroadcastChannel' in self) {
    // BroadcastChannel API supported!
}

En cuanto a los polyfills, hay algunos:

No las probé, por lo que tu kilometraje puede variar.

Recursos