Stripe.js’s history is an interesting example of how the web works in practice: technologies being repurposed for unexpected use-cases. I thought it’d be interesting to illustrate how this played out.
Initially, when we were building Stripe.js, we implemented network calls using iframes. Iframes were, of course, never intended for use with cross-domain requests. However, HTML5 added support for postMessage, which enables two cooperating pages to communicate. This isn't quite enough, though—IE6, as usual, doesn’t support postMessage. It turns out that you can still make the iframe hack work by using a non-obvious shared channel: the iframe’s
src property—and, in particular, the anchor fragment.
This is what the first version of Stripe.js did. What it lacked in elegance it made up for in compatibility. Still, it meant that we had to serve our iframe code from
api.stripe.com, which was somewhat inelegant. The underlying code was hacky and awkward to maintain.
The standards of the web progress slowly, but they do progress. Back in 2005, a couple of people from Tellme Networks wrote a W3C working group note with the catchy title of Authorizing Read Access to XML Content Using the <?access-control?> Processing Instruction 1.0, which introduced a concept of access control declarations to XML and HTTP. This went through a few versions over the years (by 2007, it was Access Control for Cross-site Requests). Today, this has become CORS, or Cross-Origin Resource Sharing.
Supporting CORS is pretty simple—a matter of adding a few HTTP headers. Browsers will automatically prefix Ajax requests to third party servers with a OPTIONS request, verify that the CORS headers are present and valid, and then send the actual request.
Unfortunately, that’s not quite enough for Stripe.js. IE6 and IE7 both lack CORS support, while IE8 and IE9 have broken implementations. IE10 is the only version with a non-buggy CORS implementation. Obviously, compatibility is paramount for Stripe.js — we want to support all major browsers, right down to IE6—and so we needed to look elsewhere.
And so we return to using web technologies in unintended ways: JSONP. JSONP is a really neat and simple hack, and works in pretty much every browser under the sun. It involves creating a
<script> tag that loads an API endpoint, and which in turn returns some JSON wrapped in a function call.
We decided to use JSONP for the Stripe.js rewrite. Adding support to our API took a few steps. First, we had to ensure that any responses to requests with
callback query parameters were wrapped in a JSONP callback. Next, we had to make sure that JSONP responses always returned a 200 HTTP status code, with the real status code present in the response body. Lastly, since JSONP only supports GET requests, but our API uses a variety of request methods, we had to implement HTTP method override support with a
_method query parameter. Rather than clutter our API code, we implemented all of this as middleware atop the API logic itself.
With this in place, we rewrote the Stripe.js client library in CoffeeScript, and conducted a huge amount of testing in every browser we support.
So, at the end of the day, what are the advantages of this new release?
First off, Stripe.js is now about half its previous size, which saves time and bandwidth for our users. It now works when loaded with
file:// URLs, which was a frequent complaint of those hosting development locally.
On the Stripe end, we were able to eliminate a lot of complexity and code required to support iframe tunneling. All in all, a pretty good refactor.