Before going into the details on how the big players out there do this, let’s take a look at the key points that you should pay attention to.
We have to do something very similar when integrating into different web applications. For this you can provide the following snippet to your clients:
What just happened here? Firstly, we created a new
script HTML element, then started to decorate it with attributes. This snippet should be placed at the end of the
The most important thing to notice here is the
async attribute. Imagine the following scenario: your service gets a huge amount of traffic, and it becomes slow. If the loading of your script does not happen asynchronously, you can block the entire webpage. By setting its value to
true we make sure that we will not block the loading of any other resources on the page.
But what should this file contain? Your entire application, or something different? The distribution part will try to answer this question.
The sacred global scope
Be a good guy, do not pollute the global scope even more.
As we have already discussed, your script will be injected into the unknown. This means, that it is very likely that libraries like jQuery, Backbone or Lodash/Underscore will be present in the page.
Be careful! You should never depend on these things, the developers of that site will not reach out to you and ask, if you are still using that thing. Even worse, they can use different versions of those libraries. So once again: never ever use them.
Also, lots of front end libraries can be found on NPM and used with Browserify/Webpack. (e.g. you can use jQuery this way without putting it into the global scope, or even worse, overwritting the one used by the site you are injected into).
Communication with a server
XMLHttpRequest cannot load http://example.org/apple. Origin https://example.com is not allowed by Access-Control-Allow-Origin.
Have you ever encountered this error message? It happened because the remote server refused to serve our request.
Enable CORS (Cross-Origin Resource Sharing)
The easiest way to do is to set the following headers in the response of the server:
Sure, you may want to limit who can reach your services – you can add domains instead of the asterisk.
The only thing you have to keep in mind when using CORS is the legacy support (if you have to deal with that). Internet Explorer browsers (8 and 9) does not have full CORS support:
- no custom HTTP headers
- content-type must be
To support these browsers you have to implement HTTP Method Overriding on both the client and the server. How does that work? It extracts the intended HTTP method from the
method querystring/parameter, then handle the actual request as it was a
Luckily, for the common frameworks like Express and Koa you can find solutions on NPM (for Express, for Koa).
Third-party cookies are called third-party, because they are placed on a different domain. Imagine the following scenario: your script is injected into
examplestore.com. You may want to track your users using your own domain,
whatanicewidget.com. In that case you will put a cookie on
What are the benefits of using a third-party cookie? You can recognise users from
whatastooore.com not just from
examplestore.com, because when making requests to your domain you will have the very same cookie.
When implementing an identifying mechanism for your application do not forget, that third-party cookies are not supported everywhere. Because of this reason you have to implement a fallback for the first-party cookie version.
This is the trickiest one. You can use localStorage (if available in the browser) to identify users. But be aware: the same-origin policy applies to localStorage as well, so visiting the same site using HTTP and HTTPS will result in different localStorage contents.
So how does localStorage help you? In short: you can use
window.postMessage to send messages between windows. So what you have to do is to include an external webpage into your site using an
iframe (using HTTPS), then communicate with it – that window will contain the localstorage, that will be the same, no matter from where the user visits that. A sample implementation can be found here: https://github.com/zendesk/cross-storage.
What what about caching? If you set the
max-age to a big number, then pushing out new versions may take a lot of time to progate to all the clients. If you set it to a small value, then the clients will frequently download it. We can do better!
The loader will be a really small file, basically what we created before, with a small exception: we include a revision number when loading the
So in this case your users have to load
loader.js file to their site, which will then load the
application.js, containing all the application logic. But why to do this? For the loader file we can set a small cache time, like an hour – it does not matter if this will be downloaded a lot, because it will not be bigger than 1KB. For the application itself we can set the cache time to eternity, it will be downloaded only once.