Setting up your API to accept HTML5 postMessage calls.

What is postMessage?

postMessage is a new HTML5 Javascript API for allowing scripts on different domains to communicate with each other safely.

Mozilla’s Developer Docs offers more information:

https://developer.mozilla.org/en/DOM/window.postMessage

Why should my API support postMessage?

HTML5 allows us to build powerful apps that leverage the cloud for getting and putting data without the need of a backend of our own. These apps will still want to use your APIs and will need a way to POST data to you.

For example, I created a heatmapping tool that allows a visitor to paste lat/lon coordinates in a textarea and using the canvas element, a heatmap is generated atop a google map. I wanted this heatmap to be shareable with a simple url, but I did not have a server backend for storing the coordinate data. Passing this data via the querystring would not work either as there could be thousands of coordinates used in the heatmap. My solution was to send the image data (base64 data) of the canvas element to a photo sharing site, in this case, Imgur.com. The shortcut code returned from Imgur would be what is passed in the url when you share. For example:

http://www.heatmapjs.com/?Fpqjh*70*4*55.27911529201561,-136.142578125*1020*603*0.7*#000000

Loads this image from Imgur:

Media_httpiimgurcomfp_afegb

However, the Imgur API only allows me to POST the image data to it. Considering my app is strictly HTML, JS, and CSS *, I could not POST this image data to Imgur due to cross domain policies.

So what I did was create a small Google App Engine app that supports postMessage, and that my heatmap app could use to communicate with Imgur. This is simply a middle man and is only needed until API providers support postMessage in their apps.

If you have an application that has APIs then you realize the power of opening up data or functionality to developers. postMessage is simply a way to allow more access to your existing API from a new breed of web applications.

* Note: Yes, the app is hosted at heatmapjs.com, so it technically has a backend but it was originally developed for a competition that focused on stand-alone HTML apps (I was just happy with it afterwards and wanted to keep it available). Also, considering the various “web app stores” that are being worked on, developers can submit packaged files that are installed to the browser and are strictly HTML/JS/CSS, which further points to a need for postMessage.

A quick rundown of how postMessage works

  • A developer has a web page with a hidden iframe on it.
    • Additionally, a new window created by Javascript can be used in place of the iframe.
  • The iframe is pointed to a special page on your (the API provider) site.
  • The developer’s code can post messages to the iframe.
  • These messages are received by the JS you have on your page (the one that is loaded in the iframe).
  • You process the data and send back a response.
  • The developer gets a response back from the iframe.

How to accept postMessage calls

This is the simplest way to get started that I have found. If you have other suggestions, please let me know and I can update the article.

The first thing we need is to give developers a page to post messages to. They will load this page to a hidden iframe in their app. There’s no reason for it to be visible, it’s only used for communication.

This page will be located at ourservice.com/v2/postMessage.html and look like this:

<html>
 <head>
   <!– using jquery for convenience –>
   <script src=”http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js”></script&gt;
 </head>
 <body>
   <script>

     // This is the function that will be called when
     // a postMessage call is received.
     function receiveMessage(e){
       // Normally, here we would want to check the origin of the sender.
       // However, we would like for anyone to use our api service.
       // Without checking the origin of the sender, we will want to make
       // sure the data we are receiving is valid and not malicious.

       // e is the postMessage event that trigger this function.
       // e.data is the params of the post call and is a string.
       var data = JSON.parse(e.data);

       // We will ask the user (shown later) what part of our
       // API they are using in this call. This allows us to
       // use this one page for all our post api calls.
       var url = data.api_url;

       // now that we have our params and know where to send it
       // we can post to our own API on behalf of the developer.
       $.post(url, data, function(response){
         
         // We have posted to ourselves and our api will respond
         // normally, but we need to pass this response back to
         // the html app, so we postMessage back to them:
         e.source.postMessage(response, e.origin);

       });

     }

     // Finally, we add a listener to trigger the function
     // above when this page (via an iframe) is posted to.
     if(typeof window.addEventListener != ‘undefined’){
       window.addEventListener(‘message’, receiveMessage, false);
     }else if(typeof window.attachEvent != ‘undefined’){
       window.attachEvent(‘onmessage’, receiveMessage);
     }

   </script>
 </body>
</html>

Now we have a way to accept calls, do something with data (POST it to ourselves), and respond back. All your programming work is done. We need to let the developer know about the new API feature.

Adapting your existing APIs

Consider you have the following API call documentation available to developers:

API: /upload
URL: http://api.ourservice.com/v2/upload
Description: Uploads an image.
Method: POST

Required Parameters:

  • api_key – Your api key
  • data – base64 data
  • format – the image format

Optional Parameters:

  • name – the name of the image
  • description – the description of the image

Which will allow the developer to upload an image to your application.

Our postMessage.html doesn’t currently point to a specific API endpoint. This is on purpose so we do not have to create a postMessage.html file for each API endpoint in our app. We simply ask the developer to send an additional parameter specifying which call we are making. So in addition to our Required and Optional params, we now have a section about postMessage params:

postMessage Parameters:

  • api_url – set to “/v2/upload”
  • http://api.ourservice.com –  the targetOrigin (the 2nd param in the postMessage JS call)

Give developers an example of how to use your new postMessage API

Now it is up to the developers out there to use your postMessage alternative. Here is a quick example showing developers how to do just that:

<html>
 <head>
   <!– using jquery for convenience –>
   <script src=”http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js”></script&gt;
 </head>
 <body>
   
   <!– various elements: canvas + inputs –>
   <canvas id=”image” width=”300” height=”300”></canvas><br/>
   <input id=”name” type=”text” value=””/><br/>
   <textarea id=”description” cols=”40” rows=”4”></textarea><br/>
   <input id=”save” type=”button” value=”save”/><br/>
   
   <!– the iframe for communication –>
   &lt;iframe src=”http://api.ourservice.com/v2/postMessage.html&#8221; style=”display:none;”></iframe>
   
   <script>

     var app = {
       
       // When the save button is clicked, this function is called
       // to post the data to the api.
       save_image: function(){
         
         // We need to define the params we will be passing:
         var data = JSON.stringify({
           “api_key”: “d0e8d367be0cf44d11f927d4b06e5784”,
           “data”: $(“#image”)[0].toDataURL(“image/png”).split(“base64,”)[1],
           “format”: “png”,
           “name”: $(“#name”).val(),
           “description”: $(“#description”).val(),
           // The above items are the normal params needed by your
           // existing API, however, our postMessage page needs one
           // additional parameter:
           “api_url”: “/v2/upload”
         });
         
         // Now that our data is gathered, we use the postMessage
         // javascript call, passing the data and the url we are
         // posting to.
         $(‘iframe’)[0].contentWindow.postMessage(data, “http://api.ourservice.com”);
         
       },

       // When postMessage as completed, it should call this function
       save_image_complete: function(e){

         // For security, we want to ditch this response if the
         // origin is not the same as where we posted
         if (e.origin !== “http://api.ourservice.com”) return;

         // Otherwise, we can be sure the data is from the source
         // we specified, so turn the string into JSON (assuming
         // your service returns JSON).
         var data = JSON.parse(e.data);

         // Most api calls will let you know if everything went ok,
         // consider our service returns a “status” property either
         // set to “success” or “failure”…
         if(data.status == “success”){
           // The call was successful and the developer can
           // use your returned data as needed.
           console.log(“image saved”);
         }else{
           // The call failed, the developer should look at the
           // errors your call sent back and adjust.
           console.log(“error saving image”);
         }
         
       }
       
     };
     
     // Stuff to do when the doc is ready
     $(document).ready(function(){
       
       // We need to add a listener to the iframe as well, to “hear”
       // responses. When we get one, send it to the save_image_complete
       // function in our app.
       if(typeof window.addEventListener != ‘undefined’){
         window.addEventListener(‘message’, app.save_image_complete, false);
       }else if(typeof window.attachEvent != ‘undefined’){
         window.attachEvent(‘onmessage’, app.save_image_complete);
       }

       // Give the save button functionality…
       $(“#save”).click(function(){
         app.save_image();
       });
       
     });

   </script>
 </body>
</html>

What happens above is when the save button is clicked, the developers app will post the params collected to the iframe, which hosts the postMessage page from your site. Your site gets the messages, and posts it to yourself, gets results, and then passes those back to the developers application.

It’s that simple to add postMessage functionality to your application.

Finally

This hasn’t been heavily tested in the wild, but I’m working with Imgur.com to add this functionality to their site, so as we run into issues, I will update this post. So far, though, with the exception of changing API endpoints, there have been no issues following the instructions above.

With that, there are a few questions I am still looking into:

  • API keys are now in the JS code, easily viewable. How can we overcome this need of API keys for an HTML5 only / no-backend app?
  • Checking the origin of the sender that is POSTing data to you is encouraged, but I do not see how the postMessage call can work in this scenario where we want to allow many apps to use this.
Advertisements