API BroadcastChannel: um barramento de mensagens para a Web

A API BroadcastChannel permite que scripts da mesma origem enviem mensagens para outros contextos de navegação. Ela pode ser considerada um barramento de mensagens simples que permite a semântica Pub/Sub entre janelas/guias, iframes, Web workers e service workers.

Princípios básicos da API

A API Broadcast Channel é uma API simples que facilita a comunicação entre contextos de navegação. Ou seja, a comunicação entre windows/tabs, iframes, web workers e service workers. As mensagens que são postadas em um canal específico são entregues a todos os ouvintes desse canal.

O construtor BroadcastChannel usa um único parâmetro: o nome de um canal. O nome identifica o canal e reside nos contextos de navegação.

// 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();

Como enviar mensagens

As mensagens podem ser strings ou qualquer elemento compatível com o algoritmo de clone estruturado (strings, objetos, matrizes, Blobs, ArrayBuffer, Map).

Exemplo: enviar um Blob ou File

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

Um canal não será transmitido para si mesmo. Portanto, se você tiver um listener onmessage na mesma página que um postMessage() para o mesmo canal, esse evento message não será disparado.

Diferenças com outras técnicas

Nesse momento, você deve estar se perguntando como isso se relaciona a outras técnicas de transmissão de mensagens, como WebSockets, SharedWorkers, a API MessageChannel e window.postMessage(). A API Broadcast Channel não substitui essas APIs. Cada uma tem uma finalidade. A API Broadcast Channel foi criada para facilitar a comunicação de um para muitos entre scripts na mesma origem.

Alguns casos de uso para canais de transmissão:

  • Detectar ações do usuário em outras guias
  • Saiba quando um usuário faz login em uma conta em outra janela/guia.
  • Instruir um worker a executar trabalhos em segundo plano
  • Saiba quando um serviço termina de realizar alguma ação.
  • Quando o usuário fizer upload de uma foto em uma janela, transmita-a para outras páginas abertas.

Exemplo: página que sabe quando o usuário sai, mesmo em outra guia aberta no mesmo site:

<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>

Em outro exemplo, digamos que você queira instruir um service worker para remover conteúdo em cache depois que o usuário mudar a "configuração de armazenamento off-line" no app. É possível excluir os caches usando window.caches, mas o service worker já pode conter um utilitário para fazer isso. Podemos usar a API Broadcast Channel para reutilizar esse código! Sem a API Broadcast Channel, você teria que retornar os resultados de self.clients.matchAll() e chamar postMessage() em cada cliente para conseguir a comunicação de um service worker para todos os clientes (código real que faz isso). O uso de um canal de transmissão torna este O(1) em vez de O(N).

Exemplo: instrua um service worker a remover um cache, reutilizando os métodos utilitários internos.

No 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]);

No 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});
    });
    }
};

Diferença com postMessage()

Ao contrário do postMessage(), não é mais necessário manter uma referência a um iframe ou um worker para se comunicar com ele:

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

O window.postMessage() também permite a comunicação entre origens. A API Broadcast Channel é da mesma origem. Como as mensagens têm a mesma origem, não é necessário validá-las como fazemos com 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);
};

Basta se inscrever em um canal específico para ter uma comunicação segura e bidirecional.

Diferença com o SharedWorkers

Use BroadcastChannel para casos simples em que você precisa enviar mensagens para várias janelas/guias ou workers.

Para casos de uso mais sofisticados, como gerenciar bloqueios, estado compartilhado, sincronizar recursos entre um servidor e vários clientes ou compartilhar uma conexão WebSocket com um host remoto, os workers compartilhados são a solução mais apropriada.

Diferença com a API MessageChannel

A principal diferença entre a API Channel Messaging e BroadcastChannel é que a última opção é um meio de enviar mensagens para vários listeners (um para muitos). MessageChannel destina-se à comunicação um para um diretamente entre scripts. Ela também é mais complexa, exigindo que você configure os canais com uma porta em cada extremidade.

Detecção de recursos e suporte a navegadores

Atualmente, o Chrome 54, o Firefox 38 e o Opera 41 são compatíveis com a API Broadcast Channel.

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

No caso dos polyfills, existem alguns:

Não tentei fazer isso, então sua quilometragem pode variar.

Recursos