API BroadcastChannel - Un bus di messaggi per il Web

L'API BroadcastChannel consente agli script della 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 inviati a tutti gli ascoltatori di quel 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 con 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 è progettata per facilitare la comunicazione one-to-many tra gli 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ò inviare 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 le proprie "impostazioni di archiviazione offline" nella tua app. Puoi eliminare le sue cache utilizzando window.caches, ma il service worker potrebbe già contenere un'utilità per eseguire questa operazione. Possiamo utilizzare l'API Broadcast Channel per riutilizzare il codice. Senza l'API Broadcast Channel, dovresti eseguire il loop sui risultati di self.clients.matchAll() e chiamare postMessage() su ciascun client per ottenere la comunicazione da un service worker a tutti i suoi client (codice effettivo che esegue questa operazione). L'utilizzo di un canale di trasmissione genera questo O(1) anziché O(N).

Esempio: indica a un service worker di rimuovere una cache, riutilizzando i suoi 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]);

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 è più necessario gestire un riferimento a un iframe o a un worker per poter comunicare con quell'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 è di tipo same-origin. Poiché è garantito che i messaggi provengano 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 "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 diverse 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 è pensato per la comunicazione one-to-one diretta tra gli script. Inoltre, richiede di configurare canali con una porta su entrambe le estremità.

Rilevamento delle funzionalità e supporto dei browser

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