Edited at

Web Messaging API を使ってみる

More than 3 years have passed since last update.


目的

HTML5 に Web Messaging API というのがある。サーバを介さずブラウザ間で通信ができるインターフェイスが提供されている。 調査がてら把握したことを整理。


概要と用途

Web Messaging API はブラウザの任意のドキュメント間での通信をサポートするインターフェイス。現在、postMessageMessageChannel という API が用意されている。どちらも任意の window オブジェクト同士で、任意のタイミングでデータの受送信をさせることが可能。

よく使われる用途として、iframe で別ドメインのコンテンツを読み込み、そのドメインと通信させる。 例えば、Facebook のいいねボタンは、 iframe で facebook.com のドメインを埋め込み通信させ、その中の cookie の値を取得してそのブラウザが保持するユーザ情報を表示しているみたい。また、Facebook や Google+ をはじめ OAuth に関わる SDK の JavaScript にもこの Web Messaging API がよく使われている印象。

本記事では Web Messaging をサポートするモジュール postMessageMessageChannel をざっくり把握する。


postMessage


概要


  • 異なるオリジンの間での通信を安全に(オリジンを確認することで)行うことを目的とした API。

  • モダンブラウザ全てに対応している。(参考)

  • 通信したい各々の window に "message" イベントを束縛させ、任意のタイミングでデータを送信できる。データを受信した側は、そのデータとオリジンの URL を取得できる。


仕様


送信

<データを送信する主体(window オブジェクト)>.postMessage(<データ(任意の型)>, <送信先のドメイン(String)>);


受信

<データを受信する主体(window オブジェクト)>.addEventListener('message', <ハンドラ関数(Function)>, false);

ハンドラ関数の引数 event に含まれる値は以下。

プロパティ
詳細

event.data
送信元から受信したデータ

event.origin
送信元のオリジンの URL

event.source
送信元への window オブジェクトの参照


サンプルコード

ドメイン A のdomainA.html にドメイン B の domainB.html を埋め込む。

ドメイン A の domainA.html


domainA.html

<iframe id="domainBIframe" src="<ドメイン B>/domainB.html">

<script src="domainA.js"></script>


domainA.js

// domeinB.html を埋め込む iframe DOM の ID

var domainBIframe = document.getElementById('domainBIframe');

/**
* この関数は任意のトリガでドメイン B にデータを送信する。
*/

var SendMsgToDomainB = function() {
var sendData = <送信するデータの文字列>;
// iframe の DOM の contentWindow プロパティに対して postMessage で送信する。
if (domainBIframe.contentWindow) {
// `postMessage ` を使ってデータを送信。
domainBIframe.contentWindow.postMessage(sendData, <ドメイン B>);
}
};

/**
* この関数は "message" イベント発火時に処理される。
*/

var receiveMessage = function(event) {

// 引数のオリジンの URL のチェック
if (event.origin !== <ドメイン B>) {
console.warn('INVALID ORIGIN');
return;
}

// 受け取ったデータの出力。
console.info(event.data);
};

// DomainA の window に "message" イベントリスナーを束縛。
window.addEventListener('message', receiveMessage, false);


ドメイン B の domainB.html


domainB.html

<script src="domainB.js"></script>



domainB.js

/**

* この関数は任意のトリガでドメイン A にデータを送信する。
*/

var SendMsgToDomainA = function() {
var sendData = <送信するデータの文字列>;
// postMessage を使ってデータを送信。この window はドメイン B の window を指す。この window は iframe で埋め込むことを想定し、window.parent は iframe 外のドメイン A の window。
window.parent.postMessage(sendData, <ドメイン A>);
};

/**
* この関数は "message" イベント発火時に処理される。
*/

var receiveMessage = function(event) {

// 引数のオリジンの URL のチェック
if (event.origin !== <ドメイン A>) {
console.warn('INVALID ORIGIN');
return;
}

// 受け取ったデータの出力。
console.info(event.data);
};

// ドメイン B の window に "message" イベントリスナーを束縛。
window.addEventListener('message', receiveMessage, false);



MessageChannel


概要


  • データの送受信を postMessage のように window 単位で行うのではなく、任意の個数の生成が可能な MessageChannel オブジェクトを使用して通信する。

  • webkit 系ブラウザのみ対応。(参考)


  • MessageChannel のインスタンスを生成すると、インスタンスのプロパティに port1、port2 という MessagePort オブジェクトが生成される。この 2 つのオブジェクトを任意のオリジンに置き、各々に "message" イベントを束縛させることでペアリングする。ペアリング後、任意のタイミングでデータを受送信する。 postMessage と違い、ペアリングしたオリジン同士でのみ通信するので、データ受信時にオリジンの URL を受け取らない。


API


初期化

var channel = new MessageChannel()

channel インスタンスに含まれる値(メソッド)は以下。

プロパティ
詳細

channel.port1
channel.port2 と通信させる対象の MessagePort オブジェクト

channel.port2
channel.port1 と通信させる対象の MessagePort オブジェクト


送信

<channel.port1 または channel.port2>.postMessage(<データ(任意の型)>, <送信先のドメイン(String)>)


受信

<channel.port1 または channel.port2>.addEventListener('message', <ハンドラ関数(Function)>, false);

ハンドラ関数の引数 event に含まれる値は以下。

プロパティ
詳細

event.data
送信元から受信したデータ

event.origin
送信元のオリジンの URL

event.source
送信元への window オブジェクトの参照


サンプルコード

ドメイン A の domainA.html にドメイン B の domainB.html を埋め込む。

ドメイン A の domainA.html


domainA.html

<iframe id="domainBIframe" src="<ドメイン B>/domainB.html">

<script src="domainA.js"></script>


domainA.js

// domeinB.html を埋め込む iframe DOM の ID

var domainBIframe = document.getElementById('domainBIframe');
var port = null;

/**
* この関数は任意のトリガでドメイン B にデータを送信する。
*/

var SendMsgToInner = function() {
var sendData = <送信するデータの文字列>;
// port オブジェクトに対して postMessage で送信する。
if (port) {
// postMessage を使ってデータを送信。
port.postMessage(sendData);
}
};

/**
* この関数は "message" イベント発火時に処理される。
*/

var receiveMessage = function(event) {
console.info(event);
};

/**
* ペアリング完了後の通信のイベントハンドラ
*/

var paringToDomainA = function(event) {
port = event.ports[0];
console.info(event);
port.addEventListener('message', receiveMessage, false);
};

// ペアリングするために初回のみ、ドメイン B と通信する。
window.addEventListener('message', paringToDomainA, false);


ドメイン B の domainB.html


domainB.html

<script src="domainB.js"></script>



domainB.js

var parentOrigin = '<ドメイン A>';

// MessageChannel のインスタンスを生成する。
var channel = new MessageChannel;
// インスタンスの port1 をドメイン B、port2 をドメイン A のメッセージ受送信をする主体オブジェクトに割り当てる。
var port = channel.port1;
window.parent.postMessage('接続開始', [channel.port2], parentOrigin);

/**
* この関数は任意のトリガでドメイン A にデータを送信する。
*/

var SendMsgToDomainA = function () {
var sendData = <送信するデータの文字列>;
port.postMessage(sendData);
};

/**
* この関数は "message" イベント発火時に処理される。
*/

var receiveMessage = function(event) {
console.info(event.data);
};

// ドメイン B の window に "message" イベントリスナーを束縛。
port.addEventListener('message', receiveMessage, false);



まとめ(感想)

postMessageMessageChannel どちらも、任意のオブジェクトに "message" イベントを張っておき、任意のオブジェクトに対してデータを受送信している。

ペアリングが成立したら通信時にデータ送信元のオリジンをチェックしなくて済むので MessageChannel の方が実装は楽そうだが、こちらは webkit 系しか対応していない為、 postMessage を使った実例が多い印象。

MessageChennel は、任意のオブジェクト同士でペアリングさせて通信できるので、 iframe を使ったクロスドメイン間の通信に限らない用途としても使えそう。


References

https://developer.mozilla.org/docs/DOM/window.postMessage

http://msdn.microsoft.com/en-us/library/windows/apps/hh441303.aspx