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
API、window.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 API と BroadcastChannel
の主な違いは、後者は複数のリスナー(1 対多)にメッセージをディスパッチする手段である点です。MessageChannel
は、スクリプト間の直接の 1 対 1 通信を目的としています。また、より複雑で、両端にポートを持つチャネルを設定する必要があります。
機能の検出とブラウザのサポート
現在、Chrome 54、Firefox 38、Opera 41 が Broadcast Channel API をサポートしています。
if ('BroadcastChannel' in self) {
// BroadcastChannel API supported!
}
ポリフィルには以下のようなものがあります。
- https://gist.github.com/alexis89x/041a8e20a9193f3c47fb
- https://gist.github.com/inexorabletash/52f437d1451d12145264
私自身は試していないため、結果は異なる場合があります。