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
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).
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).
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.