HTML5 Messaging

Dalam artikel ini saya akan membahas salah satu fitur HTML5 yaitu messaging. Fitur ini memungkinkan kita bikin dua atau lebih dokumen HTML yang terpisah, salah satu atau semuanya dimuat ke dalam iframe, tapi masih bisa berkomunikasi satu sama lain.

Apa aja yang perlu kita pake untuk implementasi fitur ini?

  • message event
  • postMessage API

Mungkin Anda pikir, “Hari gini masih pake iframe? Jadul.”

Eit .. jangan salah, iframe banyak gunanya terutama untuk bikin bagian kecil dari web page yang terisolasi dari dokumen induk sehingga nggak saling interferensi, contohnya iklan, embeddable widget, dll. Coba liat artikel sebelumnya tentang RegEx, contoh kode dari JSBin juga dimuat ke dalam iframe.

Same-Origin Messaging

Pertama kita coba implementasi fitur ini pake dua dokumen yang asalnya dari domain yang sama. Same-origin maksudnya URL dua file/dokumen tersebut pake protokol yang sama, domain yang sama, dan nomor port yang sama.

Di sini saya pake domain lokal bernama domain-a.com. Silakan liat dokumentasi server Anda gimana cara bikin domain lokal.

same-domain-messaging

index.htmlframe-a.htmlframe-b.html
<!DOCTYPE html>
<html>
<head>
	<title>Dokumen Induk</title>	
</head>
<body>
<iframe id="frame_a" src="frame-a.html" frameborder="0"></iframe>
<iframe id="frame_b" src="frame-b.html" frameborder="0"></iframe>
<script>
	
function handleMessage(e){
	var data = JSON.parse(e.data);
	console.group('[Main Doc] incoming message');
	console.log('data :',data);
	console.log('origin :',e.origin);
	console.log('sender :',e.source.location.href);
	console.groupEnd();	
}

function sendMessage(frameId,data){
	var frame = document.getElementById(frameId);
	frame.contentWindow.postMessage(JSON.stringify(data),document.location.origin);
}

	window.addEventListener('message',handleMessage,false);

</script>

</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
Domain A / Frame A
<script>
	
	function handleMessage(e){
		var data = e.data;
		console.group('[Frame A] incoming message');
		console.log('data :',data);
		console.log('origin :',e.origin);
		console.log('sender :',e.source.location.href);
		console.groupEnd();
	};

	window.addEventListener('message',handleMessage,false);

</script>

</body>
</html>
<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
Domain A / Frame B
<script>
	
	function handleMessage(e){
		var data = e.data;
		console.group('[Frame B] incoming message');
		console.log('data :',data);
		console.log('origin :',e.origin);
		console.log('sender :',e.source.location.href);
		console.groupEnd();
	};

	window.addEventListener('message',handleMessage,false);

</script>

</body>
</html>

Coba kita kirim message dari dokumen induk ke frame A & B lewat console.

sendMessage('frame_a',"Hello, it's me");

main-frame-a

main-frame-b

Kirim message dari iframe

Untuk kirim message dari dokumen induk ke iframe, kita pake iframe.contentWindow.postMessage(), sebaliknya untuk kirim message dari iframe ke dokumen induk, kita pake window.top.postMessage() atau window.parent.postMessage().

Tambahin kode berikut di frame-a.html & frame-b.html terus coba kirim message dari frame-a.html ke dokumen induk.

function sendMessage(target,data){
   var win;
   if(target === window.top){
      win = target;
   }else{
      //kirim message ke frame lain
      var frame = window.top.document.getElementById(target);
      win = frame.contentWindow;
   }
		
   win.postMessage(data,document.location.origin);
}

frame-a-main

Data yang bisa dikirim lewat message bisa bertipe object atau string, tapi karena Internet Explorer hanya mengijinkan data berbentuk string, sebaiknya kita konversi dulu objek yang ingin kita kirim ke dalam bentuk JSON string terus di sisi penerima kita konversi lagi jadi objek.

Jadi kode Javascript di dokumen induk, frame a, frame b kita ganti jadi begini (tambahin JSON.parse() & JSON.stringify():

index.htmlframe-a.html & frame-b.html
function handleMessage(e){
	var data = JSON.parse(e.data);
	console.group('[Main Doc] incoming message');
	console.log('data :',data);
	console.log('origin :',e.origin);
	console.log('sender :',e.source.location.href);
	console.groupEnd();	
}

function sendMessage(frameId,data){
	var frame = document.getElementById(frameId);
	frame.contentWindow.postMessage(JSON.stringify(data),document.location.origin);
}
function handleMessage(e){
	var data = JSON.parse(e.data);
	console.group('[Frame B] incoming message');
	console.log('data :',data);
	console.log('origin :',e.origin);
	console.log('sender :',e.source.location.href);
	console.groupEnd();
};

function sendMessage(target,data){
	var win;
	if(target === window.top){
		win = target;
	}else{
		//kirim message ke frame lain
		var frame = window.top.document.getElementById(target);
		win = frame.contentWindow;
	}
	
	win.postMessage(JSON.stringify(data),document.location.origin);
}

Cross-Origin Messaging

Cross-origin maksudnya adalah dua dokumen ada di domain lain (termasuk sub-domain) atau domain yang sama tapi port nya lain atau beda protokol (satu http, yang lain https).

Coba kita bikin domain lain, domain-b.com & domain-c.com. Tau kan caranya? Terus bikin file index.html utk domain-a.com & domain-b.com, kopi isi frame_a.html.

Kita coba kirim message dari frame_b (domain-b.com) ke dokumen induk (domain-a.com).

sendMessage(window.top,"Hello from domain b");

crossdomain failed

Tuh kan gagal. Kenapa? Karena domain-nya lain.

Cross-domain Security Sandbox

Sebuah dokumen (termasuk skrip di dalamnya) ga bisa langsung mengakses dokumen yang asalnya dari domain lain. Jadi di dokumen induk kita ga bisa bikin skrip kaya gini:

var frame_b = document.getElementById('frame_b');
var frame_b_doc = frame_b.contentWindow.document; //error

x-domain-security

Di dalam iframe frame_b sama juga. Kita ga bisa bikin skrip serupa.

window.top.document // error juga
window.top.location // error juga

Kita modifikasi kode index.html di semua domain jadi begini:

  1. Kita pake karakter asterisk (*) sebagai argumen kedua dari postMessage()
  2. Untuk kirim message dari iframe, selalu kirim ke window.top atau window.parent (tergantung keperluan)
Dokumen indukdomain-b.com/index.htmldomain-c.com/index.html
<!DOCTYPE html>
<html>
<head>
	<title>Dokumen Induk</title>
</head>
<body>

<iframe id="frame_b" src="//domain-b.com/index.html" frameborder="0"></iframe>

<iframe id="frame_c" src="//domain-c.com/index.html" frameborder="0"></iframe>

<script>
	
function handleMessage(e){
	var data = JSON.parse(e.data);
	console.group('[Main Doc] incoming message');
	console.log('data :',data);
	console.log('origin :',e.origin);
	console.groupEnd();	
}

function sendMessage(frameId,data){
	var frame = document.getElementById(frameId);
	frame.contentWindow.postMessage(JSON.stringify(data),'*');
}

	window.addEventListener('message',handleMessage,false);

</script>

</body>
</html>
<!DOCTYPE html>
<html>
<head>
	<title>Domain B</title>
</head>
<body>
<p><em>domain-b.com/index.html</em></p>
<script>
	
	function handleMessage(e){
		var data = JSON.parse(e.data);
		console.group('[Domain B] incoming message');
		console.log('data :',data);
		console.log('origin :',e.origin);
		console.groupEnd();
	};

	function sendMessage(data){
		window.top.postMessage(JSON.stringify(data),'*');
	}
	
	window.addEventListener('message',handleMessage,false);

</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
	<title>Domain C</title>
</head>
<body>
<p><em>domain-c.com/index.html</em></p>
<script>
	
	function handleMessage(e){
		var data = JSON.parse(e.data);
		console.group('[Domain C] incoming message');
		console.log('data :',data);
		console.log('origin :',e.origin);
		console.groupEnd();
	};

	function sendMessage(data){
		window.top.postMessage(JSON.stringify(data),'*');
	}
	
	window.addEventListener('message',handleMessage,false);

</script>
</body>
</html>

Karena argumen asterisk memungkinkan sebuah dokumen memproses message dari domain lain, dalam aplikasi beneran kita harus cek dari mana sebuah message berasal. Ini untuk menghindari serangan XSS ( cross-site scripting ). Salah satu caranya, cek atribut origin dari MessageEvent sebelum kita proses datanya.

if(event.origin === 'http://domain-a.com' || event.origin === 'http//domain-b.com'){
  //proses event.data
}

Quiz: Coba modifikasi kode di atas supaya kita bisa kirim message dari domain-b.com untuk domain-c.com dan sebaliknya.

Sekian.

Also in this category ...