BroadcastChannel API – magistrala komunikatów dla internetu

Interfejs BroadcastChannel API umożliwia skryptom z tego samego źródła wysyłanie wiadomości do innych kontekstów przeglądania. Można go traktować jako prosty interfejs wiadomości, który umożliwia semantykę pub/sub między oknami/kartami, iframe’ami, workerami internetowymi i workerami usługowymi.

Podstawy interfejsu API

Interfejs Broadcast Channel API to prosty interfejs API, który ułatwia komunikację między kontekstami przeglądania. Oznacza to, że skrypty mogą się komunikować z oknami i kartami, elementami iframe, skryptami web worker i skryptami service worker. Wiadomości publikowane na danym kanale są dostarczane do wszystkich słuchaczy tego kanału.

Konstruktor BroadcastChannel przyjmuje jeden parametr: nazwę kanału. Nazwa identyfikuje kanał i jest widoczna w różnych kontekstach przeglądania.

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

Wysyłanie wiadomości

Wiadomości mogą być ciągami znaków lub dowolnymi obiektami obsługiwanymi przez algorytm strukturalnego klonowania (ciągi znaków, obiekty, tablice, bloby, tablice ArrayBuffer, mapy).

Przykład – wysyłanie bloba lub pliku

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

kanał nie będzie nadawał do siebie samego. Jeśli więc masz listenera onmessage na tej samej stronie co postMessage() na tym samym kanale, zdarzenie message nie zostanie wywołane.

Różnice w stosunku do innych technik

Zastanawiasz się pewnie, jak to się ma do innych technik przekazywania wiadomości, takich jak WebSockets, SharedWorkers, interfejs MessageChannel API czy window.postMessage(). Interfejs Broadcast Channel API nie zastępuje tych interfejsów API. Każdy z nich służy do czegoś innego. Interfejs Broadcast Channel API służy do łatwej komunikacji jeden-do-wielu między skryptami w ramach tej samej domeny.

Przykładowe zastosowania kanałów transmisji:

  • Wykrywanie działań użytkowników na innych kartach
  • Dowiedz się, kiedy użytkownik loguje się na konto w innym oknie lub na innej karcie.
  • Instrukcja dla pracownika, aby wykonał zadanie w tle
  • Dowiedz się, kiedy usługa wykonała określone działanie.
  • Gdy użytkownik prześle zdjęcie w jednym oknie, prześlij je na inne otwarte strony.

Przykład: strona, która wie, kiedy użytkownik się wyloguje, nawet z innej otwartej karty w tej samej witrynie:

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

Załóżmy też, że chcesz zlecić pracownikowi usługi usunięcie z pamięci podręcznej treści po tym, jak użytkownik zmieni „ustawienie pamięci offline” w Twojej aplikacji. Możesz usunąć pamięć podręczną za pomocą funkcji window.caches, ale pracownik usługi może już mieć do tego odpowiednie narzędzie. Możemy użyć interfejsu Broadcast Channel API, aby ponownie użyć tego kodu. Bez interfejsu Broadcast Channel API musisz przetworzyć wyniki funkcji self.clients.matchAll() i wywołać funkcję postMessage() w przypadku każdego klienta, aby umożliwić komunikację między usługą wątekową a wszystkimi jej klientami (dodatkowy kod, który to umożliwia). Korzystanie z kanału transmisji powoduje, że zamiast O(N) jest O(1).

Przykład: zleć pracownikowi usługi usunięcie pamięci podręcznej, używając do tego jego wewnętrznych metod.

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

W pliku 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});
    });
    }
};

Różnica w przypadku postMessage()

W przeciwieństwie do postMessage() nie musisz już utrzymywać odwołania do elementu iframe ani workera, aby się z nimi komunikować:

// 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() umożliwia też komunikację między źródłami. Interfejs Broadcast Channel API jest interfejsem typu same-origin. Ponieważ wiadomość pochodzi z tego samego źródła, nie trzeba jej weryfikować tak jak w przypadku 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);
};

Wystarczy, że „zasubskrybujesz” dany kanał, aby mieć możliwość bezpiecznej komunikacji dwukierunkowej.

Różnica w przypadku SharedWorkers

Użyj BroadcastChannel w prostych przypadkach, gdy chcesz wysłać wiadomość do kilku okien lub kart lub do pracowników.

W przypadku bardziej zaawansowanych zastosowań, takich jak zarządzanie blokadami, współdzielenie stanu, synchronizowanie zasobów między serwerem a wieloma klientami czy udostępnianie połączenia WebSocket z hostem zdalnym, najbardziej odpowiednie są współdzielone procesy robocze.

Różnica między interfejsem MessageChannel API

Główna różnica między Channel Messaging APIBroadcastChannel polega na tym, że ta druga usługa umożliwia wysyłanie wiadomości do wielu odbiorców (model jeden-do-wielu). MessageChannel służy do komunikacji bezpośredniej między skryptami. Jest to też bardziej skomplikowane, ponieważ wymaga konfiguracji kanałów z portem na każdym końcu.

Wykrywanie funkcji i obsługa przeglądarek

Obecnie przeglądarki Chrome 54, Firefox 38 i Opera 41 obsługują interfejs Broadcast Channel API.

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

Jeśli chodzi o polyfille, jest ich kilka:

Nie testowaliśmy tych rozwiązań, więc wyniki mogą się różnić.

Zasoby