20

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?

johnnietheblack
  • 12,290
  • 27
  • 86
  • 127
  • Not sure what you mean; you're free to examine a received message and conditionally invoke different code, etc, so ... – Pointy Dec 12 '11 at 19:57
  • It's jsut that so far, I haven't seen any example's of that, and from what I've read, postmessage can't send objects or anything, which means that i'd have to dissect the string...just doesn't seem very clean. – johnnietheblack Dec 12 '11 at 20:00

4 Answers4

61

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:

  • postMessage still has limited cross-browser support
  • The trend for newer versions of standards-compliant browsers is to allow passage of objects in addition to strings
  • The passed object will be serialized, so no function references allowed
  • The widest support "in the wild" is for string-only data, which means you'll have to stick with strings and "pack" your data as demonstrated above if you want to support a wide variety of user agents
  • Internet Explorer will ruin every plan you ever make (including family holidays)

Documentation and References

Chris Baker
  • 46,402
  • 12
  • 93
  • 112
  • 5
    Great answer! Wish I would +10 for the family holidays comment – johnnietheblack Dec 12 '11 at 20:05
  • 2
    You can always perform JSON encode and decode on either side of the message. – Pointy Dec 12 '11 at 20:08
  • 1
    Even with JSON serialization, you cannot pass function references (which I believe the OP was after) since they can't be serialized. This is the case both if you JSON encode the object (for IE) or just pass the object (for Firefox >6). If you try to pass a function in Firefox, you get the error `Error: The object could not be cloned.` :( Too bad, but maybe this is safer. – Chris Baker Dec 12 '11 at 20:15
  • Well strictly speaking you can never pass functions with JSON, at least not in any direct way. – Pointy Dec 12 '11 at 20:39
  • 1
    Excellent, detailed response. Two thumbs up! – Hatchmaster Aug 16 '12 at 07:53
  • Regarding callbacks, the caller can pass to the target a json (stringified) like this: `{..., onSuccess:"functionNameOnSuccess", onError: "functionNameOnError"}` and then the target can answer to the caller another message, like: `{callbackMsg: "functionNameOnSuccess('this is a response')"}` Then the caller just needs to do something like: `eval(json.callbackMsg)` That would probably work, and it's something as close as a callback I can think of. – Joao Sousa May 02 '13 at 11:04
  • Newer versions of `postMessage` can also transfer the actual objects, but adding the objects to release as a third parameter. This means that the sending window cannot access the objects anymore, but provides for fast "zero-copy" message passing. – user239558 Jan 01 '14 at 16:48
3

Callbacks with postMessage: very possible and very useful

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:

  1. You need a common callback ID passed between frames communicating.
  2. The sender creates a unique callback ID on each message and stores it in a callback lookup hash to find the callback after sending.
  3. The receiver of the message only ensures the callback ID is sent back.
  4. All frames communicating use the same JS library for 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.

King Friday
  • 19,950
  • 9
  • 78
  • 78
  • There are better and more popular libraries available for postMessage abstraction nowadays. Check out [jschannel](https://github.com/mozilla/jschannel) or [postmate](https://github.com/dollarshaveclub/postmate) – Radek Matěj May 02 '18 at 08:21
  • 1
    @RadekMatěj agreed. This was here to educate, nothing more. Good suggestion. – King Friday May 02 '18 at 14:14
1

I recently faced the same problem. After hours of searching i came across post-robot. It's developed by paypal and solved most of my problems including having a callback for postMessage.

It also supports passing functions inside the payload.

You can check out the introduction here Introducing post-robot

Atishay Jain
  • 1,197
  • 10
  • 19
0

One pretty easy way to trigger callbacks without passing any actual code would be:

Target

var callbacks = {
  myCallback: function() { doSomething(); }
};
window.addEventListener('message', function (ev) {
  // origin checking etc
  callbacks[ev.data]();
}, false);

Source

target.postMessage('myCallback', 'http://www.example.com');
Jacob Rask
  • 17,543
  • 7
  • 35
  • 35
  • 5
    That doesn't really solve the problem. This is just a callback on the recipient side. The question is about passing callback as part of the message so that you can react for example to a successful execution of the request. – Radek Matěj May 02 '18 at 08:16