BroadcastChannel API – magistrala komunikatów dla internetu

Interfejs BroadcastChannel API umożliwia skryptom z tej samej domeny wysyłanie komunikatów do innych kontekstów przeglądania. Można ją uznać za prostą szynę wiadomości, która zezwala na semantykę Pub/Sub między oknami/kartami, elementami iframe, mechanizmami webowymi i skryptami service worker.

Podstawy interfejsu API

Broadcast Channel API to prosty interfejs API, który ułatwia komunikację między kontekstami przeglądania. Oznacza to komunikację między oknami/kartami, elementami iframe, instancjami roboczymi i skryptami service worker. Wiadomości publikowane na danym kanale są dostarczane do wszystkich jego słuchaczy.

Konstruktor BroadcastChannel przyjmuje pojedynczy parametr: nazwę kanału. Nazwa identyfikuje kanał i występuje w kontekście 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 tekstowymi lub dowolnymi innymi obiektami obsługiwanymi przez algorytm klonowania ustrukturyzowanego (ciągi, obiekty, tablice, obiekty blob, bufory tablicy, mapy).

Przykład – wysyłanie obiektu blob lub pliku

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

Kanał nie może transmitować do siebie. Jeśli więc na tej samej stronie co tag postMessage() na tym samym kanale znajduje się detektor onmessage, zdarzenie message nie będzie się uruchamiać.

Różnice w stosunku do innych metod

Być może zastanawiasz się, jaki ma to związek z innymi technikami przekazywania wiadomości, takimi jak WebSockets, ShareWorkers, MessageChannel API i window.postMessage(). Broadcast Channel API nie zastępuje tych interfejsów API. Każda z nich służy określonemu celowi. Interfejs Broadcast Channel API ułatwia komunikację typu jeden do wielu między skryptami z tego samego źródła.

Przykłady użycia kanałów telewizyjnych:

  • Wykrywanie działań użytkowników na innych kartach
  • Otrzymuj informacje o tym, że użytkownik loguje się na konto w innym oknie lub na innej karcie.
  • Instruowanie pracownika, aby wykonała pewne czynności w tle
  • Otrzymuj informację, gdy usługa wykona określone działanie.
  • Gdy użytkownik przesyła zdjęcie do jednego okna, przekaż je na inne otwarte strony.

Przykład – strona, która widzi, że użytkownik się wylogowuje, 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>

W innym przykładzie załóżmy, że chcesz poinstruować skrypt service worker, że po zmianie ustawienia „pamięci podręcznej” w aplikacji użytkownik usunie treści zapisane w pamięci podręcznej. Możesz usunąć jego pamięć podręczną za pomocą polecenia window.caches, ale skrypt service worker może już zawierać narzędzie do tego celu. Możemy użyć interfejsu Broadcast Channel API, aby ponownie użyć tego kodu. Bez Broadcast Channel API trzeba byłoby zapętlić wyniki polecenia self.clients.matchAll() i wywołać metodę postMessage() na każdym kliencie, aby umożliwić komunikację pomiędzy mechanizmem Service Worker do wszystkich klientów (rzeczywisty kod, który to robi). Użycie kanału transmisji sprawia, że ten element to O(1), a nie O(N).

Przykład – poinstruuj skrypt service worker, aby usunął pamięć podręczną, ponownie korzystając z wewnętrznych metod narzędziowych.

W 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 z postMessage()

W przeciwieństwie do elementu postMessage() nie musisz już utrzymywać odwołania do elementu iframe ani instancji roboczej, aby się z nim 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 należy do tej samej domeny. Wiadomości na pewno pochodzą z tego samego pochodzenia, więc nie trzeba ich weryfikować tak jak wcześniej w przypadku usługi 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 „zasubskrybować” konkretny kanał, aby korzystać z bezpiecznej, dwukierunkowej komunikacji.

Różnica w stosunku do elementów SharedWorkers

Używaj BroadcastChannel w prostych przypadkach, gdy musisz wysłać wiadomość do kilku okien, kart lub instancji roboczych.

W bardziej skomplikowanych przypadkach, takich jak zarządzanie blokadami, współużytkowanym stanem, synchronizowanie zasobów między serwerem a wieloma klientami lub współdzielenie połączenia WebSocket z hostem zdalnym, najbardziej odpowiednie będą współdzielone zasoby robocze.

Różnica w stosunku do interfejsu MessageChannel API

Główna różnica między interfejsem Channel Messaging API a BroadcastChannel polega na tym, że ta druga opcja służy do wysyłania wiadomości do wielu słuchaczy (jeden do wielu). MessageChannel służy do bezpośredniej komunikacji między skryptami. Jest to też bardziej skomplikowane, ponieważ wymaga skonfigurowania kanałów z portami po obu stronach.

Wykrywanie funkcji i obsługa przeglądarek

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

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

Jeśli chodzi o kody polyfill, jest kilka:

Nie próbowałem/próbowałam tych rozwiązań, więc przebieg może być inny.

Zasoby