So far I've only seen tutorials for postmessage where one window sends a single kind of message, and the other window interprets the message in only a single way.
What if I want to have many different kinds of interactions between windows, can postmessage handle that?
Is that going against the grain of what postmessage is supposed to do?
For example, what if I wanted to be able to send custom callbacks back and forth, etc?
The postMessage() function is asynchronous, meaning it will return immediately. So you can not do synchronous communication with it. In your example, the posted message will vanish in the void, because there is no listener for the message event at the time the postMessage() function is executed.
You can assign it to a variable, pass it around, use call or apply on it, bind it, and so on. Note however that the scope of the function is dynamic (i.e. any non-local variables in the function must be already defined in your iframe window).
A malicious site can change the location of the window without your knowledge, and therefore it can intercept the data sent using postMessage .
You can send objects, arrays, numbers, strings, booleans, null, undefined, Date objects and Uint8Array objects. However, functions and prototype chains of objects will not be sent. These restrictions are the same as the browser's postMessage : click here for more details.
There are a couple of ways to pass a multi-part message on to a postMessage
handler. The first (and less "clean" way) is to use a delimiter character, then pass your data through a string.
Let's say we wanted to pass a user ID, an action, and the users name. The string would look like this:
54|do_logout|chris
Within the postMessage
handler, the passed data can be split
(docs) on the |
character, then each segment of the message can be used as needed.
Another route, instead of manually creating/splitting a string, is to use JSON (docs) to convert an object into a string on one side, and use JSON to convert back to an object in the handler.
var pass_data = {
'name':'Chris',
'id':54,
'action':'do_logout'
};
target_window.postMessage(JSON.stringify(pass_data), "http://www.example.net");
... then in the handler:
function (event) {
var pass_data = JSON.parse(event.data);
}
Be sure to test, though, as the JSON
object is not provided on all user agents, especially older ones. There are many (many, many) third-party libraries out there to shim JSON support, so don't let the lack of complete adoption scare you away - JSON is definitely a safe "moving forward" standard.
Wouldn't it be nicer if we could just pass that object straightaway? Well, staring in Firefox 6 (source), the data you pass to a postmessage handler may be an object. The object will be serialized, so there are some concerns on that front, but:
var pass_data = {
'name':'Chris',
'id':54,
'action':'do_logout'
};
target_window.postMessage(pass_data, "http://www.example.net");
A little nicer, eh? Unfortunately, current versions of IE will only deal with strings. I was not able to find any discussion on future plans regarding postMessage
for IE 10. Further, there is a known bug in IE 8/9 which breaks postMessage
for anything other than frames. (source).
Getting in to a specific aspect of your question - callbacks. Unless you're able to pass the callback by function name, there isn't a way to pass a function; no anonymous functions for you. This is related to the way the data is actually passed on to the handler. In practice, there "is not" support for objects as data, behind the scenes the browser is turning your passed object into a string (serialization).
All that said, then, you should understand that passing an object is exactly the same as using JSON to stringify
an object before passing, only in the former case the browser is doing its own serialization (and subsequent unserialization), whereas with the latter route it is up to you to serialize/unserialize.
The take-away points here:
Documentation and References
window.postMessage
: https://developer.mozilla.org/en/DOM/window.postMessage
String.split
: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split
JSON.stringify
: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/JSON/stringify
JSON.parse
: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/JSON/parse
There is a nice plugin I've found on npm called "silver-bullet". It does postMessage with callbacks and uses eventEmitter to get specific events as well. Its very nice.
But to implement this I would do something like...
phostMessage(iframe, someObj, callback);
You have to do this:
Here's a very basic demonstration of that:
var callbacks = {};
// when receiving messages
window.addEventListener('message', function(ev) {
// todo: add origin check
if (!ev.data)
return;
var message;
try {
message = JSON.parse(ev.data);
} catch (ex) {
console.error(ex);
}
// ignore messages not having a callback ID
if (!message || !message.callbackId)
return;
// we are the sender getting the callback
if (callbacks[message.callbackId]) {
callbacks[message.callbackId](message);
delete callbacks[message.callbackId];
return;
}
// we are the receiver so we respond with the callback ID
// todo: restrict who can receive message (last param)
iframe.contentWindow.postMessage(JSON.stringify(message), '*');
});
// when sending messages
function phostMessage(iframe, obj, callback) {
obj.eventId = Math.random();
callbacks[obj.eventId] = callback;
// todo: restrict who can receive message (last param)
iframe.contentWindow.postMessage(JSON.stringify(obj), '*');
}
I take this concept a bit further and use a message handler lookup where the message has the desired handler function name to evoke and pass a message to. The message handler takes a callback as well that when completed fires the callback. The callback just has the simple logic of calling the native post message again sending back its received callback id.
So the last line of code for the message event handling would be:
if (messageHandler[message.handler])
messageHandler[message.handler](message, function() {
iframe.contentWindow.postMessage(JSON.stringify(message), '*');
});
else
iframe.contentWindow.postMessage(JSON.stringify(message), '*');
which allows asynchronous stuff to happen.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With