API BroadcastChannel - Un bus di messaggi per il Web

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

Nozioni di base sulle API

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

Il costruttore BroadcastChannel accetta un solo parametro: il nome di un canale. Il nome identifica il canale ed è presente in tutti 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 elemento supportato dall'algoritmo di clonazione strutturata (stringhe, oggetti, array, blob, ArrayBuffer, mappe).

Esempio: invio di un blob o di un file

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

Un canale non trasmette a se stesso. Pertanto, se hai un ascoltatore onmessage nella stessa pagina di un postMessage() per lo stesso canale, l'evento message non viene attivato.

Differenze rispetto ad altre tecniche

A questo punto potresti chiederti come si relaziona questa tecnica ad altre tecniche di trasmissione dei messaggi come WebSocket, SharedWorker, l'API MessageChannel e window.postMessage(). L'API Broadcast Channel non sostituisce queste API. Ognuno ha uno scopo. L'API Broadcast Channel è pensata per semplificare la comunicazione uno a molti tra script sulla stessa origine.

Alcuni casi d'uso per i canali di trasmissione:

  • Rilevare le azioni degli utenti in altre schede
  • Scopri quando un utente accede a un account in un'altra finestra/scheda.
  • Chiedere a un worker di eseguire un'attività in background
  • Scopri quando un servizio ha completato l'esecuzione di un'azione.
  • Quando l'utente carica una foto in una finestra, la può passare ad altre pagine aperte.

Esempio: pagina che sa quando l'utente esce, 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 chiedere a un service worker di rimuovere i contenuti memorizzati nella cache dopo che l'utente ha modificato l'impostazione "Memorizzazione offline" nella tua app. Potresti eliminare le cache utilizzando window.caches, ma il service worker potrebbe già contenere un'utilità per farlo. Possiamo utilizzare l'API Broadcast Channel per riutilizzare il codice. Senza l'API Broadcast Channel, devi eseguire un ciclo sui risultati di self.clients.matchAll() e chiamare postMessage() su ogni client per realizzare la comunicazione da un worker di servizio a tutti i suoi client (codice effettivo che esegue questa operazione). Se utilizzi un canale di trasmissione, il valore è O(1) anziché O(N).

Esempio: chiedi a un worker di servizio di rimuovere una cache riutilizzando i suoi metodi di utilità interni.

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

In 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 esso:

// 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 inoltre di comunicare tra origini. L'API Broadcast Channel è di origine stessa. Poiché è garantito che i messaggi provengano dalla stessa origine, non è necessario convalidarli come facevamo in precedenza 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 "abbonarsi" a un determinato canale per avere una comunicazione bidirezionale sicura.

Differenza con SharedWorkers

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

Per casi d'uso più sofisticati, come la gestione di blocchi, stato condiviso, sincronizzazione delle risorse tra un server e più client o condivisione di una connessione WebSocket con un host remoto, i worker 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ù ascoltatori (uno a molti). MessageChannel è destinato alla comunicazione 1:1 direttamente tra script. Inoltre, è più complessa, in quanto richiede la configurazione di canali con una porta su ciascun lato.

Rilevamento delle funzionalità e supporto dei browser

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

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

Esistono diversi polyfill:

Non le ho provate, quindi i risultati potrebbero variare.

Risorse