API BroadcastChannel - Un bus di messaggi per il Web

Eric Bidelman

L'API BroadcastChannel consente agli script della stessa origine di inviare messaggi ad altri contesti di navigazione. Può essere considerato come un semplice bus di messaggi che consente la semantica Pub/Sub tra finestre/tabelle, iframe, web worker e service worker.

Nozioni di base sulle API

L'API Broadcast Channel è una semplice API che semplifica la comunicazione tra contesti di navigazione. In altre parole, comunicazione tra finestre/schede, iframe, web worker e service worker. I messaggi pubblicati su un determinato canale vengono recapitati a tutti gli ascoltatori di quel canale.

Il costruttore BroadcastChannel utilizza un solo parametro: il nome di un canale. Il nome identifica il canale e riguarda i contesti di navigazione.

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

l'invio di un messaggio e un altro

I messaggi possono essere stringhe o qualsiasi cosa supportata dall'algoritmo di clone strutturato (Stringhe, Oggetti, Array, Blob, ArrayBuffer, Mappa).

Esempio: invio di un BLOB o di un file

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

Un canale non trasmetterà automaticamente nulla al proprio canale. Pertanto, se hai un listener onmessage sulla stessa pagina di un postMessage() per lo stesso canale, l'evento message non viene attivato.

Differenze con altre tecniche

A questo punto ti starai chiedendo come questo sia correlato ad altre tecniche per la trasmissione dei messaggi come WebSocket, SharedWorkers, l'API MessageChannel e window.postMessage(). L'API Broadcast Channel non sostituisce queste API. Ognuno ha uno scopo. L'API Broadcast Channel è pensata per una facile comunicazione one-to-many tra script sulla stessa origine.

Alcuni casi d'uso per i canali televisivi:

  • Rileva le azioni degli utenti in altre schede
  • Scopri quando un utente accede a un account in un'altra finestra/scheda.
  • Indicare a un lavoratore di svolgere del lavoro in background
  • Scopri quando un servizio completa un'azione.
  • Quando l'utente carica una foto in una finestra, passala ad altre pagine aperte.

Esempio: pagina che indica quando l'utente si disconnette, anche da un'altra scheda aperta sullo stesso sito:

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

In un altro esempio, supponiamo che tu voglia indicare a un service worker di rimuovere i contenuti memorizzati nella cache dopo che l'utente ha modificato le "impostazioni di archiviazione offline" nella tua app. Potresti eliminare le cache utilizzando window.caches, ma il service worker potrebbe già contenere un'utilità per eseguire questa operazione. Possiamo usare l'API Broadcast Channel per riutilizzare quel codice. Senza l'API Broadcast Channel, dovresti eseguire il loop dei risultati di self.clients.matchAll() e chiamare postMessage() su ciascun client per ottenere la comunicazione da un service worker a tutti i suoi client (il codice effettivo che esegue questa operazione). L'uso di un canale di trasmissione rende questo O(1) anziché O(N).

Esempio: indica a un service worker di rimuovere una cache, riutilizzando i metodi di utilità interna.

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

Nel file 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});
    });
    }
};

Differenza con postMessage()

A differenza di postMessage(), non devi più mantenere un riferimento a un iframe o a un worker per comunicare con l'iframe:

// 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() ti consente anche di comunicare tra origini. L'API Broadcast Channel ha la stessa origine. Poiché i messaggi provengono dalla stessa origine, non è necessario convalidarli come facevamo prima 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);
};

Basta "iscriversi" a un determinato canale e avere una comunicazione bidirezionale sicura.

Differenza con SharedWorkers

Utilizza BroadcastChannel per casi semplici in cui devi inviare un messaggio a più finestre/schede o worker.

Per casi d'uso più elaborati come la gestione di blocchi, lo stato condiviso, la sincronizzazione delle risorse tra un server e più client o la condivisione di una connessione WebSocket con un host remoto, i lavoratori condivisi sono la soluzione più appropriata.

Differenza con l'API MessageChannel

La differenza principale tra l'API Channel Messaging e BroadcastChannel è che quest'ultima è un mezzo per inviare messaggi a più listener (one-to-many). MessageChannel è progettato per la comunicazione one-to-one direttamente tra gli script. Inoltre, è più coinvolto, poiché è necessario configurare canali con una porta su ogni estremità.

Rilevamento delle funzionalità e supporto del browser

Attualmente, Chrome 54, Firefox 38 e Opera 41 supportano l'API Broadcast Channel.

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

Per quanto riguarda i polyfill, ce ne sono alcuni:

Non le ho provate, quindi il chilometraggio potrebbe variare.

Risorse