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

Eric Bidelman 氏

BroadcastChannel API を使用すると、同一オリジン スクリプトから他のブラウジング コンテキストにメッセージを送信できます。これは、ウィンドウ/タブ、iframe、ウェブワーカー、Service Worker 間で Pub/Sub セマンティクスを実現するシンプルなメッセージバスと考えることができます。

API の基本

Broadcast Channel API は、ブラウジング コンテキスト間の通信を容易にするシンプルな API です。つまり、ウィンドウ/タブ、iframe、ウェブ ワーカー、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();

メッセージを送信する

メッセージには、文字列など、構造化クローン アルゴリズムでサポートされている任意のもの(String、Object、Arrays、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>

別の例として、ユーザーがアプリで「オフライン ストレージの設定」を変更した後にキャッシュに保存されたコンテンツを削除するように Service Worker に指示するとします。window.caches を使用してキャッシュを削除することもできますが、Service Worker にこれを行うユーティリティがすでに含まれている場合があります。そのコードは Broadcast Channel API で再利用できます。Broadcast Channel API を使用しない場合は、Service Worker からすべてのクライアントへの通信を実現するために、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 やワーカーと通信するために参照を維持する必要がなくなりました。

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

特定のチャンネルに「チャンネル登録」するだけで、安全で双方向の通信ができるようになります。

SharedWorker との違い

複数のウィンドウ/タブ、またはワーカーにメッセージを送信する必要がある単純なケースには、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!
}

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

試したことがなかったので、走行距離は異なる場合があります。

関連情報