1

I'm doing the JavaScript challenges at FreeCodeCamp. One of them is to create a web page that retrieves and displays weather information.

First, I tried to use several providers (e. g. OpenWeatherMap, WeatherUnderground), which use the HTTP protocol to return weather data. It didn't work because of the mixed content error.

Next, I switched to a provider, which delivers the data via HTTPS. I got rid of the mixed content problem, but got another one:

XMLHttpRequest cannot load https://api.weatherbit.io/v1.0/current?lat=55.7767723&lon=37.6090795&units=S&key=XXXXXXXX. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://s.codepen.io' is therefore not allowed access. The response had HTTP status code 403.

I tried to implement CORS according to this tutorial:

function createCORSRequest(method, url) {
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr) {

    // Check if the XMLHttpRequest object has a "withCredentials" property.
    // "withCredentials" only exists on XMLHTTPRequest2 objects.
    xhr.open(method, url, true);

  } else if (typeof XDomainRequest != "undefined") {

    // Otherwise, check if XDomainRequest.
    // XDomainRequest only exists in IE, and is IE's way of making CORS requests.
    xhr = new XDomainRequest();
    xhr.open(method, url);

  } else {

    // Otherwise, CORS is not supported by the browser.
    xhr = null;

  }
  return xhr;
}

[...]

var url = "https://api.weatherbit.io/v1.0/current?lat=" + position.coords.latitude + "&lon=" + position.coords.longitude + "&units=S&key=XXXXXXXX";
var xhr = createCORSRequest('GET', url);
if (xhr) {
    xhr.onload = function() {
      var responseText = xhr.responseText;
      console.log("Response: " + responseText);
    };

    xhr.onerror = function() {
      console.log('There was an error!');
    };
    xhr.send();
}

When I call xhr.send() I still get the error.

How can I fix it?

Note: I'm looking for a solution that will run in CodePen.

Update 1 (23.03.2017 11:35 MSK): I tried to implement sideshowbarker's answer and modified the code like this:

function getCurrent(json){
     console.log("getCurrent called");
     console.log(json.data.temp);
     console.log(json.data.precip);
}

function updateWeather() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(function(position) {
      var url = "https://api.weatherbit.io/v1.0/current?callback=getCurrent&lat=" + position.coords.latitude + "&lon=" + position.coords.longitude + "&units=S&key=XXXXXXXXX";
      console.log("url: " + url);
      $.get(url, function(val){});
    });
  } else {
    console.log("Cannot obtain location data");
  };
}

updateWeather();

The result:

Screenshot with error messages

Update 2 (23.03.2017 13:29 MSK): This one works.

 function getCurrent(json) {
   console.log("getCurrent called");
   console.log(JSON.stringify(json));
   // TODO: Update the UI here
 }

 function updateWeather() {
   if (navigator.geolocation) {
     navigator.geolocation.getCurrentPosition(function(position) {
       var url = "https://api.darksky.net/forecast/XXXXXXXXX/37.8267,-122.4233?callback=getCurrent";

       var script = document.createElement('script');
       script.src = url;

       document.getElementsByTagName('head')[0].appendChild(script);

     });
   }
 }

 updateWeather();
Souleste
  • 1,887
  • 1
  • 12
  • 39
Glory to Russia
  • 17,289
  • 56
  • 182
  • 325
  • Your update isn’t using the solution I proposed. Instead it’s using jQuery $.get(…), which is functionally the same as just using native XHR. If you want to use jQuery for this, consider instead using $.getJSON(), which will do some behind-the-scenes magic to deal with it properly as a JSONP request-response, without using XHR. See the docs at https://api.jquery.com/jquery.getjson/ – sideshowbarker Mar 23 '17 at 08:49
  • How can I call the URL without jQuery? – Glory to Russia Mar 23 '17 at 08:52
  • Without jQuery you need to create a `script` element in your code, build the URL the same way you’re doing now, add a `src` attribute to the `script` element with that URL, then append that `script` element to your document. See https://stackoverflow.com/questions/6132796/how-to-make-a-jsonp-request-from-javascript-without-jquery and https://plainjs.com/javascript/ajax/jsonp-ajax-requests-50/ for guidance on that – sideshowbarker Mar 23 '17 at 08:58

2 Answers2

1

The Weatherbit.io API doesn’t support cross-origin requests made from XHR.

Instead the API requires you make the requests using a script element with a JSONP callback:

<script>
    function getCurrent(json){
        console.log(json.data.temp)
        console.log(json.data.precip)
    }
</script>

<script
src="https://api.weatherbit.io/v1.0/current?callback=getCurrent&lat=NNN&lon=NNN&units=S&key=XX"></script>

Of course you likely want to have your code inject that script element with the URL and params.

That’s method of injecting the script element with a JSONP callback is the only direct method they support for using their API from a web app.

There’s no way your code will work if it instead makes the request to their API using XHR; they don’t send the necessary Access-Control-Allow-Origin response header, and so because that’s missing, your browser won’t let your client-side JavaScript access the response cross-origin.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS explains why.

The only way you could use XHR to work with their API is if you set up a CORS proxy using code from https://github.com/Rob--W/cors-anywhere/ or something similar—or if you send your request through an public CORS proxy like https://github.com/Rob--W/cors-anywhere/ (which you don’t want to do because that’d give the owner of that service access to your Weatherbit.io API key).

The way the proxy works is that instead of using weatherbit.io URL, you use a proxy URL like https://cors-anywhere.herokuapp.com/https://api.weatherbit.io/v1.0/current…, and the proxy sends it on to weatherbit.io, gets the response back, then adds the Access-Control-Allow-Origin response header to the response it hands back to your code and that the browser sees.

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
0

I was in the process of completing the Weather App in FCC and came across the same issue. I was able to get it to work with the following line:

$.getJSON("https://api.weatherbit.io/v1.0/current?lat=##&lon=##&key=##", function(data){};

For whatever reason, it wouldn't work with just "http://", I had to change it to "https://" in order for it to work.

Not sure if that helps anyone in the future.