Friday, March 16, 2012

JSONP with Dart

A few people have asked how to handle JSONP in Dart. Turns out, this is basically possible, and I'd appreciate feedback on this technique.

JSONP is a trick to get around the lack of CORS headers in your favorite API. CORS is the modern way to get around the single origin policy, however even Google still doesn't support CORS on many of their APIs. The web developer community has come up with JSONP as a hack until all browsers and all APIs support CORS.

Option 1: If you always deploy to JavaScript

This method only works if you are always deploying to JavaScript and are not deploying or testing on Dartium (Chromium with an embedded Dart VM). Also, this feels hacky.

Add the JSONP callback to your main page as a small JavaScript method.

<script type="text/javascript">
function callbackForJsonpApi(data) {
  dartCallback(JSON.stringify(data));
}
</script>

This simple method converts the data from the server into a big JSON string. It passes this string to dartCallback, which happens to come from the JavaScript generated by your Dart program.

Your Dart code should look like this:

#import('dart:html');
#import('dart:json');

dartCallback(String data) {
  var obj = JSON.parse(data);
  print(obj['responseData']);
}

void main() {
  // this pulls in the function to ensure
  // it's compiled into the resulting JavaScript
  var f = dartCallback;
  
  Element script = new Element.tag("script");
  script.src = "https://ajax.googleapis.com/ajax/services/search/news?v=1.0&q=barack%20obama&callback=callbackForJsonpApi";
  document.body.elements.add(script);
}

This tricky hack takes advantage of the fact that the Dart top level functions are translated to JavaScript functions with the same names.

Pros: Simple to understand.
Cons: Doesn't work in Dartium, may not work in the future (if the JavaScript compiler starts to munge names).

Option 2: If you deploy to Dartium and JavaScript

Dartium is Chromium with an embedded Dart VM. Dartium is able to run native Dart code, without any compilation to JavaScript. If you are developing and testing on Dartium (as many of us do), you will need another option for emulating JSONP.

First, add this small snippet of JavaScript to your main page.

<script type="text/javascript">
function callbackForJsonpApi(s) {
  window.postMessage(JSON.stringify(s), '*');
}
</script>

Notice the use of postMessage() to communicate the JSON string data. Using postMessage is how you can send data between the JavaScript world and the Dart world in Dartium.

#import('dart:html');
#import('dart:json');

dataReceived(MessageEvent e) {
  var data = JSON.parse(e.data);
  print(data['responseData']);
}

void main() {
  // listen for the postMessage from the main page
  window.on.message.add(dataReceived);
  
  Element script = new Element.tag("script");
  script.src = "https://ajax.googleapis.com/ajax/services/search/news?v=1.0&q=barack%20obama&callback=callbackForJsonpApi";
  document.body.elements.add(script);
}

The Dart app listens for messages and delegates to dataReceived. Once parsed from the JSON string into an object of Maps and Lists, the Dart program can now operate on the data sent from the JSONP service.

Pros: This option works in both Dartium and when compiled to JavaScript.
Cons: More complicated, possibly slower due to sending large strings over postMessage.

Summary

Accessing a web service that uses JSONP with Dart is possible. For maximum compatibility, use postMessage to communicate between the JavaScript callback and the Dart application.

Please add your comments about this technique, and let us know which JSONP services are you mashing up with Dart.

Thanks for trying Dart!


Post a Comment

Disclaimer

I'm probably required to say that the views expressed in this blog are my own, and do not necessarily reflect those of my employer. Also, except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the BSD License.