JavaScript Widget Development Best Practices (Part 4: Cross-Domain Communication)
In the former part of the series we’ve seen how to revalidate the cache and load our widget code using a self updating bootloader script.
Now it’s time to pass initialization parameters to our widget, and request some state data from the widget API server.
Let’s recap:
Our lovely publisher is including a async loading snippet to their pages. Once the script loads, any additional dependencies are loaded too. And then our widget’s initialization flow starts.
Configuration Parameters
A thing almost all of the widgets have in common is a set of initialization parameters, which give some flexibility over the widget’s initial construction and which may also be used to identify the publisher.
For the sake of our example, let’s assume that we need to pass around a publisher ID to our api bootloader.
We, as widget provider, can use this publisher ID to send different data to different publishers, track API usage of various publishers, throttle/limit/block the API responses depending on the type of the publisher. All of these and more can be possible by simply passing a publisher ID to the server, during widget’s initialization.
Note that a simple ID, or a simple API token is not enough to identify a publisher, since anyone can copy the initialization script and inject it to their site.
You can use this ID in conjunction with Referrer header to indicate that your request is coming from the intended publisher. There are other metrics you can use like setting third party token cookies for additional verification, but from experience I can say that creating an application that just works is far more important than working on a problem that you don’t actually have.
We will be working on session, persistence, authentication, and authorization in the upcoming articles too. Stay tuned
![]()
There are different ways of sending parameters to our API layer. Let’s analyze a few:
Passing Widget Configuration Through the Querystring
We simply send the publisher id through the query string in the src attribute of the bootloader script.
Once the bootloader script loads, we parse that URL inside the script to get the publisher ID and then send it to the server to get initial state data:
<script>
(function() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = 'http://api.widget.www/api.v.0.1.js?pubId=123456'
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}());
</script>
Here’s a better way to implement this:
<script>
(function() {
var pubId = '123456';
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = 'http://api.widget.www/api.v.0.1.js?pubId=' + pubId;
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}());
</script>
If we use this approach, we will be able to see the publisher id on the server’s HTTP logs, since it’s transferred as a part of the URL.
Passing Widget Configuration Through the Hash Fragment
Another option is to pass the parameters through the hash fragment:
<script>
(function() {
var pubId = '123456';
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = '//api.widget.www/api.v.0.1.js?#pubId=' + pubId;
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}());
</script>
Please note that we are using protocol relative URLs too. This will help us avoid browser’s warning when the publisher’s pages are served over HTTPS.
Keeping all your assets within the same protocol prevents the dreaded “This Page Contains Both Secure and Non-Secure Items” warning message in IE.
Other than that, we just send the publisher ID through the hash fragment, instead of the query string. When we do that, we won’t see the parameters in the server logs, since the hash fragments are not transmitted to the server during page requests.
Passing Widget Configuration Through HTM5 Data Attributes
We can also use the new kid on the block HTML5 data- attributes to pass our initialization parameters to the widget script:
<script data-wd-pub-id="123456">
(function() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = '//api.widget.www/api.v.0.1.js'
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}());
</script>
We then simply parse DOM to get a script element with data-wd-pub-id, and use the value of this attribute in widget initialization.
Note that we prefix our attibute with -wd and use data-wd-pub-id instead of data-pub-id.
Always keep in mind that you may not be the only widget provider that the publisher uses.
You don’t own the publisher’s website.
Prefix everything that’s publicly accessible (like global variables, attributes, CSS class names…) with your widget namespace, so that it does not gets overridden, or it does not conflict with the publisher’s or another widget provider’s configuration or styling data on the page.
Passing Widget Configuration Through Global Variables
Another option is to pass the configuration data as global variables. Note that we are also prefixing our variable with _wd (our widget’s namespace) to avoid conflicts.
<script>
var _wd_pubId = 1234;
(function() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = '//api.widget.www/api.v.0.1.js'
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}());
</script>
Or it’s even better to use a common _wd namespace, to enable group a bunch of initialization parameters together:
<script>
if (!window._wd) {
window._wd = {
pubId : '123456'
};
(function() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = '//api.widget.www/api.v.0.1.js'
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}());
}
</script>
In the above code there is another checks for additional safety:
First, we check for window._wd namespace and do not do anything if it already exists. If such a namespace exists, then it means that our widget bootloader has been included before, so there’s no need to enter the same initialization flow once again.
If there’s no such window._wd namespace, then we create one and asynchronously append our bootloader script as we’ve done before.
We also have slightly changed our bootloader, adding two guard clauses:
// api.v.0.1.js
...
/*
* Ready States
*/
var kLoaded = 1;
var kLoadingDependencies = 2;
var kLoadedDependencies = 3;
var kBeginProcessQueue = 4;
var kBeginRender = 5;
var kComplete = 6;
...
// Publisher has forgotten to provide initialization data.
if (!window[kWidgetAlias]) {
log('Widget namespace cannot be found; exiting.');
return;
}
// To avoid re-defining everything if the bootloader is included in
// more than one place in the publisher's website.
if (window[kWidgetAlias][kReadyState]) {
log('Widget has already been loaded; exiting.');
return;
}
The first “if” checks for the existence of window._wd namespace, if it does not exist, then the publisher has forgotten to include their pubId parameter, so the initialization cannot continue.
The second one checks whether the widget has been loaded. So even the publisher does not include the if (!window._wd) condition on their script, the initialization flow will not proceeed, because another script will have already done the initialization.
Getting the Widget Configuration
The next thing is to get the widget configuration. That’s relatively easy, since it’s a globally accessible variable, rather than being a data attribute, hash fragment, or query string. The latter three will require traversing the DOM and doing regular expression matches. It’s also a relatively easy task, but providing a global configuration data is faster and cleaner:
...
/*
* Parameter Names
*/
var kPublisherId = 'pubId';
var kRandom = 'r';
var kVersion = 'v';
...
/*
* Globals
*/
var kO2Alias = '_wd_o2';
var kWidgetAlias = '_wd';
var kWidgetQueueAlias = '_wdq'
...
/*
* Get widget configuration from DOM.
*/
function getConfiguration() {
log('o->getConfiguration()');
var result = {};
result[kPublisherId] = window[kWidgetAlias][kPublisherId];
return result;
}
Send Configuration Data to Server
Here’s where we left last time, on the widget’s initialization flow:
/*
* Initialize after loading prerequisites.
*/
function initialize() {
log('o->initialize()');
setReadyState(kLoadedDependencies);
if (!window.o2) {return;}
window.o2.noConflict(kO2Alias);
o2 = window[kO2Alias];
var config = getConfiguration();
loadInitialState(config, processPostInitialization);
}
The initialize method is called once the additional dependencies are loaded by our bootloader.
Let’s see what those dependencies are:
...
/*
* Load necessary o2.js components in noConflict mode.
*/
function loadDependencies(callback) {
log('o->loadDependencies(');
log(callback);
log(')');
setReadyState(kLoadingDependencies);
loadScripts(kO2Root, [
'o2.meta.js',
'o2.core.js',
'o2.string.core.js',
'o2.jsonp.core.js'
], callback);
}
checkForUpdates(versionTimestamp);
loadDependencies(initialize);
}(this, this.document, true));
So we simply load a couple of required o2.js modules, and then call initialize function when they are all done.
Then we exit the initialization flow if we fail to load the o2.js framework for some reason:
...
if (!window.o2) {return;}
...
Since we don’t own the publisher’s DOM, we don’t also know whether the publisher has included a different version of o2.js framework on his web page. If there is such a case, our o2.js methods may conflict with that of the publisher’s.
To avoid this we load o2.js onto window._wd_o2 namespace, in noConflict mode.
This essentially:
- Loads the current framework into a new namespace;
- Overwrites window.o2, from the cached version (the version that the publisher is using);
- And returns a reference to the new namespace.
Here’s the actual code from the o2.js source:
// o2.core.js
exports.noConflict = def(me, 'noConflict', function(newName) {
var name = newName || [myName, ((new Date()).getTime() +
Math.random() * (1 << kGuidShift)).toString(kGuidRadix
).replace(kDecimalPoint, kEmpty)].join(kEmpty);
window[name] = myself;
window[myName] = window._o2_cached;
return window[name];
});
Then we cache the new namespace in a local o2 alias for ease of reference:
...
o2 = window[kO2Alias];
...
Then we get the configuration and load initial state from the API server:
...
var config = getConfiguration();
loadInitialState(config, processPostInitialization);
Communicating with the Server
Since our api server is on a different domain than the publisher’s website, we are back into the cross domain boundary passing problem.
There are different approaches to create a cross domain channel from the publisher to the widget API server.
These can be listed as:
- Cross Origin Resource Sharing
- Using flash as a proxy
- Using iframe hash fragments
- Using window.name as a communication proxy
- Using HTML5 Cross Frame Messaging API
- Klein Bottle Technique
- Using a Web Proxy for Cross Domain Communication
- JSON With Padding (JSONP)
I’m sure there are even crazier techniques, that don’t come to my mind right now. We, web hackers, are really creative in finding loopholes and bending security limitations
.
Each of the above techniques have their own pros and cons.
And once you manage to pass the cross domain boundary, there are other beasts waiting for your exploration
(which we will talk about in the followup articles)
.
If you are targeting relatively newer versions of browsers (IE10+, Firefox 3.5+, Chrome 3+, Safari4+, Opera12+), or if you are targeting mobile devices specifically, then you can safely use cross origin resource sharing as a modern way to do cross-domain communication. Otherwise you will need to provide a couple of fallback mechanisms.
Cross-Domain Communication is a hard problem to tackle with. There are also several libraries that choose an aproproate communication channel depending on the current user agent’s capabilities. The most popular one is EasyXDM. It’s a well-established library which big guys like LinkedIn, twitter, and disqus use for their cross-domain communication needs.
For the sake of our argument, we will implement our solution using JSONP, which can run in almost any user agent without needing further modifications.
Drawbacks of JSONP
However there are several limitations of JSONP. To name a few:
- The size of the data you can send is limited (around 2000 characters);
- JSONP only works with GET requests, you cannot do POST, PUT, or DELETE requests with it;
- There’s no error handling in JSONP, either you request succeeeds, or you do not get a response back. The only thing
you can do is wait for a long enough timeout (like 10 seconds) and raise an error if there’s still no response from the server; - JSONP requests are vulnerable to CRSF attacks, so you should take additional security precautions if you are using JSONP as a cross-domain proxy.
Having said all these, let’s see how we communicate with our server using JSONP:
/*
* Loads initial widget state from the server.
*/
function loadInitialState(config, callback) {
log('o->loadInitialState(');
log(config);
log(callback);
log(')');
o2.Jsonp.get(
o2.String.concat(kApiRoot, kParamsPath),
config,
callback
);
}
We simply do a JSONP request to http://api.widget.www/api/v.0.1/params.
Since we’ve included o2.js JSONP module as a dependency, doing a JSONP request can be done in just a single line of code.
And here’s what happens on the server:
/**
*
*/
app.get(v_0_1(route).PARAMS, function(req, res) {
var callback = req.param(parameter.CALLBACK);
var publisherId = req.param(parameter.PUBLISHER_ID);
// very primitive access control.
if (publisherId !== '123456') {
res.send(statusCode.NO_DATA);
return;
}
var result = {
data : 'Hello World. Hello Stars. Hello Universe!'
};
res.send(
callback + '(' + JSON.stringify(result) + ');'
);
});
We just send a dummy JSON data to the client.
We will be doing more meaningful things, like rendering a login form, in the following article.
Read the Source Luke
If you want to play with what we have done so far, you can see the code at this github history snapshot.
Conclusion
In this article we’ve observed different ways to provide initialization parameters to our widget.
We’ve also learned varios approaches on cross-domain communication; and we implemented a JSONP tunnel as our cross domain proxy.
We mentioned various drawbacks of using a JSONP proxy, pointing to more modern alternatives such as CORS.
In the next article of the series, we’ll finally start creating something we can interact with. We’ll create an initial login screen, and work with authentication and authorization.
Until then, feel free to share your comments and suggestions.

