BroadcastChannel API - ウェブ用のメッセージバス

BroadcastChannel API を使用すると、同一オリジンのスクリプトで他のブラウジング コンテキストにメッセージを送信できます。これは、ウィンドウ/タブ、iframe、Web Worker、Service Worker 間での pub/sub セマンティクスを可能にするシンプルなメッセージバスと見なすことができます。

API の基本

Broadcast Channel API は、ブラウジング コンテキスト間の通信を容易にするシンプルな API です。つまり、ウィンドウ/タブ、iframe、Web Worker、Service Worker 間で通信します。特定のチャンネルに投稿されたメッセージは、そのチャンネルのすべてのリスナーに配信されます。

BroadcastChannel コンストラクタは単一のパラメータ(チャンネルの名前)を受け取ります。名前はチャンネルを識別するもので、ブラウジング コンテキスト全体で保持されます。

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

メッセージを送信する

メッセージは、文字列または構造化クローン アルゴリズムでサポートされているもの(文字列、オブジェクト、配列、Blob、ArrayBuffer、Map)にすることができます。

- Blob または File を送信する

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

チャンネルが自身にブロードキャストされることはありません。したがって、同じチャネルの postMessage() と同じページに onmessage リスナーがある場合、その message イベントは発生しません。

他の手法との違い

ここで、WebSocket、SharedWorker、MessageChannel APIwindow.postMessage() などのメッセージ パスの他の手法との関係について疑問に思われるかもしれません。Broadcast Channel API はこれらの API に代わるものではありません。それぞれに目的があります。Broadcast Channel API は、同じオリジンのスクリプト間で簡単に 1 対多の通信を行うためのものです。

ブロードキャスト チャンネルのユースケース:

  • 他のタブでのユーザー操作を検出する
  • ユーザーが別のウィンドウまたはタブでアカウントにログインしたとき。
  • ワーカーにバックグラウンド処理を実行するよう指示する
  • サービスがアクションの実行を完了したタイミングを把握する。
  • ユーザーが 1 つのウィンドウで写真をアップロードすると、開いている他のページにその写真を渡します。

- ユーザーがログアウトしたとき(同じサイトの別のタブからログアウトした場合も)を認識するページ:

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

別の例として、ユーザーがアプリで「オフライン ストレージ設定」を変更した後に、キャッシュに保存されたコンテンツを削除するようにサービス ワーカーに指示する場合を考えてみましょう。window.caches を使用してキャッシュを削除することもできますが、サービス ワーカーにすでにこの処理を行うユーティリティが含まれている場合があります。Broadcast Channel API を使用すると、そのコードを再利用できます。Broadcast Channel API がなければ、サービス ワーカーからすべてのクライアントへの通信を実現するために、self.clients.matchAll() の結果をループして各クライアントで postMessage() を呼び出す必要があります(実際に行うコード)。ブロードキャスト チャネルを使用すると、O(N) ではなく O(1) になります。

- Service Worker に内部ユーティリティ メソッドを再利用してキャッシュを削除するよう指示します。

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

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

postMessage() との違い

postMessage() とは異なり、iframe またはワーカーと通信するために、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() を使用すると、オリジン間で通信することもできます。Broadcast Channel API は同一オリジンです。メッセージは同じ送信元から送信されるため、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);
};

特定のチャネルを「サブスクライブ」するだけで、安全な双方向通信を実現できます。

SharedWorkers との違い

複数のウィンドウ/タブまたはワーカーにメッセージを送信する必要がある単純なケースには、BroadcastChannel を使用します。

ロックの管理、共有状態、サーバー間と複数のクライアント間でのリソースの同期、リモートホストとの WebSocket 接続の共有など、より高度なユースケースの場合は、共有ワーカーが最も適切なソリューションです。

MessageChannel API との違い

Channel Messaging APIBroadcastChannel の主な違いは、後者は複数のリスナー(1 対多)にメッセージをディスパッチする手段である点です。MessageChannel は、スクリプト間の直接の 1 対 1 通信を目的としています。また、より複雑で、両端にポートを持つチャネルを設定する必要があります。

機能の検出とブラウザのサポート

現在、Chrome 54、Firefox 38、Opera 41 が Broadcast Channel API をサポートしています。

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

ポリフィルには以下のようなものがあります。

私自身は試していないため、結果は異なる場合があります。

リソース