はじめに
過去、ネットワーク経由でのリアルタイム通信を行いたい時、よく MQTT や WebSocket を使って行っていました(それに関し、Qiita の記事も書いたりしてました)。
今回の記事の内容は、PC内の異なるページ間でリアルタイム通信を行おうとしたとき「サーバを経由しない方法で何か良さそうなやり方があるかな?」と思って調べた内容です。具体的には、調査して出てきた以下の記事の内容を一部抜粋したり翻訳したり、というのが主な内容です。
●4 Ways to Communicate Across Browser Tabs in Realtime | by Dilantha Prasanjith | Bits and Pieces
https://blog.bitsrc.io/4-ways-to-communicate-across-browser-tabs-in-realtime-e4f5f6cbedca
余談
上記の記事にたどり着く前、p5.js のライブラリをいろいろ見ていた時に、以下のローカルでのメッセージ通信というものを見かけました。その仕組みを見てみると Service worker を使ったもののようでした。
●bmoren/p5.localmessage: p5.localmessage provides an interface to send messages locally from one sketch to another with a service worker for multi-window sketching!
https://github.com/bmoren/p5.localmessage
それで、Service worker と他の関連しそうなキーワードで検索していて、冒頭の記事にたどり着いたというのが今回の話のはじまりです。
ブラウザのタブ間でのリアルタイム通信を行う 4つの方法
4つの方法の概要
まずは、冒頭の記事に書かれた 4つのキーワードを列挙してみます。
- Local Storage Events
- Broadcast Channel API
- Service Worker Post Message
- Window Post Message
MDN のサイトで、上記に関するページもひっぱってきて、以下に補足付で列挙してみました。
- storage イベント: ストレージエリアが変更された時に発生、 Web Storage API に関連
- Broadcast Channel API: 閲覧コンテキスト(ウィンドウ、タブ、フレーム、iframe)間で、同じオリジンを使用して簡単に通信可能
- Client.postMessage(): サービスワーカーがクライアント (Window, Worker, SharedWorker) にメッセージを送信
- window.postMessage: Window オブジェクト間で安全にクロスドメイン通信を可能にするためのメソッド
ここから冒頭の記事の中のソースコードの部分を見ていきます。
それぞれの方法に注意書きがあったりもしますが、そのあたりは一部のみ抜粋して記載してみます。
【具体的なソース】 Local Storage Events
ここでは、Local Storage を使った仕組みの部分を見ていきます。
2つのタブがあるうち一方のタブで以下を実行して、Local Storage にデータを保存します。
window.localStorage.setItem("loggedIn", "true");
そして、もう一方のタブでは以下のイベントリスナーでイベントを検出し、上記で保存されたデータの読み出しも行う流れのようです。
window.addEventListener('storage', (event) => {
if (event.storageArea != localStorage) return;
if (event.key === 'loggedIn') {
// Do something with event.newValue
}
});
【具体的なソース】 Broadcast Channel API
ここでは、Broadcast Channel API を使った仕組みの部分を見ていきます。
2つのタブがあるうち一方のタブで、チャンネルの作成とメッセージ送信を行います。
const channel = new BroadcastChannel('app-data');
channel.postMessage(data);
そして、もう一方のタブでは以下のチャンネル作成・イベントリスナーでのイベントを検出を行って、メッセージを受信する流れのようです。
const channel = new BroadcastChannel('app-data');
channel.addEventListener ('message', (event) => {
console.log(event.data);
});
【具体的なソース】 Service Worker Post Message
ここでは、Service Worker を使った仕組みの部分を見ていきます。
Service Worker によるメッセージ送信の処理は、以下となるようです。
navigator.serviceWorker.controller.postMessage({
broadcast: data
});
そして、受信側の処理は以下のようになります。
addEventListener('message', async (event) => {
if ('boadcast' in event.data ) {
const allClients = await clients.matchAll();
for (const client of allClients) {
client.postMessage(event.broadcast);
}
}
});
元のサイトでは、この方法については「Service Worker API の知識が必要になる」という学習コストの面についてコメントされています。他の方法がうまく活用できるようであれば、その他の方法を用いるほうがシンプルに実行できる、という感じのようです。
【具体的なソース】 Window Post Message
ここでは、Window Post Message を使った仕組みの部分を見ていきます。
「One of the traditional ways」と書かれていて、以前からある王道という感じです。
メッセージの送信側の処理は以下となります。
targetWindow.postMessage(message, targetOrigin)
受信側の処理は以下のとおりです。
window.addEventListener("message", (event) => {
if (event.origin !== "http://localhost:8080")
return;
// Do something
}, false);
この方法について「クロスオリジンでの通信が可能であるものの、今回のタブ間通信の用途では制限をかけている」という記載があります。
上記のソースコードで言うと if (event.origin !== "http://localhost:8080")
の部分で、異なるオリジン同士での通信に制限をかけている、というものです。
おわりに
今回は概要を記載しただけになりましたが、今後は特定のものを試してみて、できればそれの記事化もしてみようと思います。
まずは試すとしたら、自分は以下を選んでみようかと思っています。
- Broadcast Channel API
- Window Post Message