Event-driven cross-domain iFrame JavaScript

1 February 2015 5 minutes to read

I had an interesting SSO project with only one problem – marketing requirements and the complexity of integration. The daily workflow is more or less known: account login, logout, password change, email confirmation, and so on. Every developer has done it a hundred times. However, this project is created for Flash and JavaScript games. These games are located in different domains and support many languages (of course, you can replace the game example with any other).

Links

Demo GitHub

Solution seeds

I would like to skip an introduction of the cross-domain security restriction (a child iFrame is not allowed to share its own state with the parent window). Available solutions are window.location.hash listening or the Window.postMessage() method. The first solution doesn’t resolve the 'loss of a game state' problem. The latter is partially supported.

Digging into the internet uncovers a cross-browser workaround for the second solution which is useless in a nutshell. Nevertheless, we can simulate cross-origin communication with it.

DOM structure

Two iframes are needed. IFRAME1 for the easyXDM and IFRAME2 for the application itself(Pic. 1.1).

DOM structure
Pic. 1.1

The first iFrame should point to the proxy.html. This file is used to create the second iFrame and to pass the original URL to it. Finally, IFRAME2 allows us to use the window.parent object which is connected with IFRAME1 which is connected with our mother page via easyXDM bridge.

Cross-Origin

To implement the bridge functionality we have to create a piece of JavaScript code at the mother page. This piece must understand an incoming query and respond to it (like a channel). Apparently the most flexible way is to use events alongside a jQuery-like library.

  // mother
  function MyBridgeChannel() {
    this.listener = $({});
  }


  MyBridgeChannel.prototype.createInstance = function () {
    var that = this;
    return new easyXDM.Rpc(
      {
        // ...
      },
      {
        local: {
          close: function (cbSuccess, cbError) {
            that.listener.trigger('close', [cbSuccess, cbError]);
            return {result: true};
          }
        }
      }
    );
  };


  MyBridgeChannel.prototype.on = function (id, cb) {
    this.listener.on(id, function () {
      cb.apply(this, Array.prototype.slice.call(arguments, 1));
    });
    return this;
  };

Then, we should bind an event to the mother page.

  // mother
  var channel = new MyBridgeChannel();
  channel.on('close', function () {
    // find the modal window and close it
  });

The most famous task is to close an iFrame from within. Now we can do that like this:

  // iFrame #2
  $(function () {
    if (!window.parent || !window.parent.bridge) {
      return;
    }
    window.parent.bridge.close();
  });

Advanced

Responsive iframe in a modal window

An iFrame in a modal window can be responsive! Furthermore, it is possible to use any CSS frameworks like Bootstrap, Foundation and others.

Sadly, because of the bridge communication, we have the time interval between the propagation of a request and its response. This delay must include all DOM modifications. In other words we have to notify the opposite side about changes for the second time(Pic. 1.2).

Resize process
Pic. 1.2

The previous image can be transformed to a code like this:

  // iFrame #2
  $(function () {
    var body = $('body');
    setTimeout(function () { // the first timeout
      bridge.resize(body.width(), body.height());
      setTimeout(function () { // the second timeout
        bridge.resize(body.width(), body.height());
      }, 400);
    }, 30);
  });

Third-Party cookies sharing

To restore a session, the session ID or a cookie with the session ID must exist. The simplest solution you can choose is to use the default functionality provided by the backend language. Usually, we can call request.getSession(), requests.Session(), session_start(), etc., to start or restore it. These methods create a new cookie automatically.

But what if a customer has disabled third-party cookies? Or how can we inform the parent page about it? If these questions bother you, the only way I found is to transfer the session ID together with the iFrame2 URL. In this case, we can use the mother page as a database, allowing us to share some information and to store a cookie with the session ID.

Like this:

  // iFrame #2
  window.parent.bridge.cookie('sid', 'abcde123', {expires: 30});


  // mother
  MyBridge.prototype.cookie = function (name, val, params) {
    if ($.type($.cookie) === 'undefined') {
      throw new Error('jquery.cookie plugin is not found, please re-define this method');
    }
    return $.cookie(name, val, params);
  };

That is all.