How to handle cross-protocol, cross-domain issues when fetching JSON with ajax via HTTP GET

Alex JonssonBlogs, Case Studies

Screen Shot 2016-04-28 at 12.03.56
Tweet about this on TwitterShare on FacebookShare on LinkedInShare on Google+

Screen Shot 2016-04-28 at 10.11.42There has been several questions on the forum after a preamble article on the subject, and also on our new Gitter channel on problems when fetching JSON objects from the web. Your code may either work in a browser but not in the Evothings Viewer or any other Corodova app, or the other way around when you run files in your regular browser (always look in the console for hints). By padding the JSON call to be served in a script format (jsonp), you can get across the domain barrier, but it won’t help much when you’re on a https page, attempting to call an unsecure http resource.

Therefore, we need a simple way to investigate firstly if we’re in a Cordova app, and then either use a regular ajax call using jQuery, or by calling the Cordova HTTP plug-in, with which you can fetch the resource, whatever is in myURL in this case, outside of the web container. This plug-in is installed per default in the Evothings Viewer, while there are certain limitation as compared to XMLHttpRequest; lack or support for reading return headers, error responses and cookies are not supported as they are in regular ajax calls.

So here we go, and after including the jQuery libraries….

<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.js"></script>

..look if there is a Cordova object, window.cordova, in place.

function getJSON() {
   if (window.cordova) {
      // do something cordova style
   }
   else {
      // fallback to web methods
   }

So, let’s say that there was a Cordova object, then you could invoke the CordovaHTTP plug-in…

cordovaHTTP.get(
   myURL,
   function (response) {
      if (response) {
         sensor.data = JSON.parse(response.data);
         // do something useful with the data
      }
   },
   function (error) {
      console.log(JSON.stringify(error));
   }
);

…and the fall-back, which works in any browser across domains, but not across protocols. Calling an unsecure HTTP resource this way in Evothings Studio will fail, as we follow the industry’s requirement for secure end-to-end services. While if your resource is based on the HTTPS protocol, it’ll work just fine and you don’t need to use the CordovaHTTP plug-in at all.

console.log('Not using Cordova, fallback to AJAX via jquery');
 $.ajax({
    url: myURL,
    jsonp: "callback",
    cache: true,
    dataType: "jsonp",
    data: {
       page: 1
    },
    success: function(response) {
       if (response && response[0]){
         sensor.data = response;
           // do something useful with that data
       }
    }
 });

So, as a summary, here is the code for this example in one go. You can also find the code as a html file if you prefer (right-click, you know). It is a working example with live data, from one of our projects, the Dome of Visions, gathering data on activities, on location in Stockholm, Sweden.

Good luck with your mobile, hybrid development!

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no,
        shrink-to-fit=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" />
    <title>Getting data from a json API</title>
    <link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.css" />
    <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
    <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.js"></script>

    <!-- This following script, cordova.js, hooks up the web container with the installed plug-ins, 
    and is already included into Evothings Viewer (i.e. you don't need to add this script file to your 
    project folder).  -->
    <script src="cordova.js"></script>

    <script>
    // Redirect console.log to Evothings Workbench, so you can see data coming in under 'Tools'. 
    // If you're not using Evothings, you can skip this part.
    if (window.hyper && window.hyper.log) { hyper.log = console.log }
    </script>
</head>

<body>
<script>

// Create an empty object as a global, to store data
var sensor = {};

// Where the sensor data is stored. This data comes from http://domeofvisions.se
var baseURL = 'http://backup.evothings.com:8082/output/';

// A subscriber's key (Five other keys also availble at http://smartspaces.r1.kth.se:8082)
sensor.key = "BQa4EqqbgxfMgpBQ8XwNhvP82Dj";

// assembly of the URL, getting the last day's worth of data in json format
myURL = baseURL + sensor.key + '.json?gt[timestamp]=now-1day&page=1'

// A bitmap image describing in general where this specific sensor is located
sensor.image = "https://evothings.com/demos/dome_pics/IMG_1758.JPG";

// Function to retrieve data, placing it in a "response" object
function getJSON() {
    if (window.cordova){
        console.log('Using Apache Cordova HTTP GET function');
        cordovaHTTP.get(
            myURL,
            function (response) {
                if (response) {
                    sensor.data = JSON.parse(response.data)[0];
                    sensor.fullData = JSON.parse(response.data);
                    printData();
                }
            },
            function (error){
                console.log(JSON.stringify(error));
            });
    }    
    else {
        console.log('Not using Cordova, fallback to AJAX via jquery');
        $.ajax({
            url: myURL,
            jsonp: "callback",
            cache: true,
            dataType: "jsonp",
            data: {
                page: 1
            },
            success: function(response){
                if (response && response[0]){
                    sensor.data = response[0];
                    sensor.fullData = response;
                    printData();
                }
            }
        });
    }
}

function printData(){
    if (sensor && sensor.data) {
        // Display the info.
        html = '<h1>Sensor Data</h1>'
        + '<br /><div id="time">Time  ' + sensor.data.timestamp + '</div>'
        + '<div id="hum">Humidity ' + sensor.data.h + ' % (rel)</div>'
        + '<div id="temp">Temperature ' + sensor.data.t + ' celcius</div>'
        + '<img src="' + sensor.image + '" />'
        } 
    else {
        html = '<h1>Sensor Data</h1>'
        + '<br />Sorry, sensor data not available right now :(</br>'
        + '<img src="' + sensor.image + '" />'
    }
    document.getElementById("printHere").innerHTML= html;
}


</script><button onclick="history.back()">Back in browser history</button><br />

<button onClick="getJSON();">Retrieve some sensor data</button>
<div id="printHere"></div>
</body>
</html>