🍨

postMessageを使って、ブラウザ上でオリジン間のデータ共有を実施

2023/03/13に公開

やりたいこと

ブラウザ上で、あるページ(仮にhttps://src.jp/src.htmlとする)から異なるオリジンのページ(仮にhttps://popup.jp/popup.htmlとする)を別ウインドウで開き、その後2つのページ間でデータ共有をしたい。

課題

オリジン(ドメイン)が違うのでセキュリティ上の制約でLocal StorageやCookieの共有は出来ません。もちろんdomの操作も出来ません。
API作ってサーバー側で処理すればやりようがあるけど、面倒くさいのでやりたくないです。フロントエンドだけで完結させたいです。

解決策

Window.postMessage()を使いました。
https://developer.mozilla.org/ja/docs/Web/API/Window/postMessage

window.postMessage() は、 Window オブジェクト間で安全にオリジン間通信を可能にするためのメソッドです。例えば、ポップアップとそれを表示したページの間や、iframe とそれが埋め込まれたページの間での通信に使うことができます。

とのことで、まさにやりたいことを実現する為の関数が用意されていました。

余談

postMessageの世の中のメジャーな活用事例としては、
Youtubeをiframeで埋め込んだ時にプレイヤーを制御するのに使われるみたいです。
なるほどと思いました。

postMessageの使い方

送る側

こんな感じでデータを送れます。

(送信先のWindow).postMessage({
	action: 'データを送るよ!',
	message: 'メッセージだよ!',
}, 'http://(送信先のドメイン):(port)')
  • 第一引数は任意のオブジェクトです。送信元と送信先で合意して、フォーマットを決めてください。
  • 第二引数は、送信先のオリジンです。*も使えますがお勧めはしないです。
  • (送信先のWindow)は下記のように取れます。
    • 自分が開いたwindow(親→子にデータを送る時)
      • window.open(openURL)の戻り値
    • 自分を開いたwindow(子→親にデータを送る時)
      • window.opener
    • 受け取ったメッセージに返信する時
      • event.source

受け取る側

こんな感じでデータを受け取れます。

window.addEventListener('message', (event) => {
	if (event.origin !== 'http:(送信元のドメイン:(port)')
		return;
	if (event.data.action == 'データを送るよ!') {
		console.log(event.data.message);
	}
});
  • window.addEventListenerを呼び出して、第一引数にmessage、第二引数にメッセージを受け取った時に動く関数を書きます。
  • メッセージを受け取ったら、最初に送信元のオリジンをチェックしてください。
    • チェックが漏れると怪しいサイトのiframeに埋め込まれて不審なデータを送り込まれた場合でも、受け入れて処理が動いてしまうので要注意です。
  • dataに送信されたオブジェクトが入っているので、送信元と送信先で合意したフォーマットに従って、中身を取り出してください。

サンプル実装

クロスオリジンのサンプルを実現する為に複数のドメインを用意するは面倒くさかったので、
http://127.0.0.1:8080/src.htmlからhttp://localhost:8080/popup.htmlをポップアップで開いて、その後データを送りつけるサンプルを書いてみました。
両方同じじゃねーかと思うかもしれませんが、127.0.0.1とlocalhostは別オリジン扱いになるのでサンプルとして適切です。

サンプルの処理フロー

こんな感じのフローで書きました。

最初は4と5の処理を書かずに実装したのですが、popupを開く処理とデータを送信する処理が非同期動くので画面が開く前にデータを送りつけるという状態になりました。この為4と5をハンドシェイク的な処理として追加しています。親→子のデータ送信のサンプルを作るつもりが、結果的に相互通信になりました。

サンプルコード

https://github.com/k-ibaraki/postmessage-sample/blob/2023-06-23/src.html

https://github.com/k-ibaraki/postmessage-sample/blob/2023-06-23/popup.html

動かしてみた

全角50万文字を送信してみました。 ※gifアニメです↓

最後に

久しぶりにバニラのjavascript+htmlで書いた。

2023/06/23追記

サンプルを少し改善して、ソースをGitHubに置きました。

改善点

  • 通信相手のチェックを正規表現を用いるようにしました。
  • popup側からのデータ送信先の指定をreferrerから取得するように変更しました。

改善の理由としては、実際のアプリに組み込んだ際は、リダイレクトされたりURLにwwwの有無などのブレが発生します。ドメインの完全一致を求めるとかなり実用性が落ちてしまうので、正規表現で条件を指定できるようにしました。

NCDCエンジニアブログ

Discussion