Problem fetching external API returning JSON - javascript

I'm having issues fetching an external API who should return JSON.
The API doesn't have CORS enabled so I'm trying to use fetch with the option mode: "no-cors".
I tried to use jsonp but doesn't work at all.
So here's the piece of code:
fetch(APIURL, {
mode: "no-cors",
}).then(response => {
console.log(response)
return response.json();
}).then(data => {
console.log(data);
}).catch(err => {
console.log(err)
});
The catch statement returns this SyntaxError: "JSON.parse: unexpected end of data at line 1 column 1 of the JSON data"
Here's the result of console.log(response)
bodyUsed: true
headers: Headers
<prototype>: HeadersPrototype { append: append(), delete: delete(), get: get(), … }
ok: false
redirected: false
status: 0
statusText: ""
type: "opaque"
url: ""
<prototype>: ResponsePrototype { clone: clone(), arrayBuffer: arrayBuffer(), blob: blob(), … }
But in the network tab I can see the JSON response that I want to use so I find it weird that I can see it in there so I assume the problem is on my end. I tried validating the JSON output in a validator and it's a valid JSON.
Any Ideas?
Thanks.

Under normal circumstances, you cannot read data from a third party site due to the Same Origin Policy.
CORS allows the third party site to grant your JavaScript permission to read the data.
The no-cors setting is a means for your JavaScript to say "I do not want to do anything that requires permission from CORS". This lets you make a request to send data without being able to read the response (the benefit is that it avoids throwing an error message all over the developer console telling you that you can't read the data you aren't trying to read).
Since you need to read the data, you cannot use no-cors.
Since the site doesn't provide permission with CORS, you cannot read the data directly with client-side code.
Use a proxy.

Related

How to fetch on barcode API barcode.monster

I am using Quagga JS scanner and trying to fetch the barcode resulted from operation with the barcode.monster API.
But i'm getting an error called "opaque" and have status 0.
Any idea what is it?
Website : https://barcode.monster/api/
Quagga.onDetected(function(data) {
// //Once barcode detecterd, fetch from barcode.monster
fetch('https://barcode.monster/api/' + data.codeResult.code, {
mode: 'no-cors',
headers: {
'Content-Type': 'application/json',
}})
.then(response => console.log(response))
.catch((error) => {
console.error('Error:', error);
});
console.log("This is the scanner output: " + data.codeResult.code);
Quagga.stop();
})
If i try response.json() I will get an error which says Unexpected end of input on the line where the json method is located.
Also, if i try with text() method, I get a blank result.
So far I was playing with the link and did not figure out. Studied some Fetch documentation, because I'm a beginner.
Response {type: "opaque", url: "", redirected: false, status: 0, ok: false, …}
body: null
bodyUsed: false
headers: Headers {}
ok: false
redirected: false
status: 0
statusText: ""
type: "opaque"
url: ""
The above text is the response...
But i'm getting an error called "opaque"...
opaque is not an error, it's a type of response. This is because you're calling the endpoint with mode: no-cors. This behavior is well-documented, and discussed at length in this SO thread. From the linked MDN page (emphasis mine):
no-cors — Prevents the method from being anything other than HEAD, GET or POST, and the headers from being anything other than simple headers. If any ServiceWorkers intercept these requests, they may not add or override any headers except for those that are simple headers. In addition, JavaScript may not access any properties of the resulting Response. This ensures that ServiceWorkers do not affect the semantics of the Web and prevents security and privacy issues arising from leaking data across domains.
As there doesn't seem to be any CORS-related security headers on the endpoint in question (at least during my cursory analysis of it), you should be able to change your script to use cors mode (unless you really, really need it specifically set to another value, and understand the implications of the mode you need) and the response should flow through normally.

Console log raw data of a failed Axios get request

I would like to make an Axios get request to a private server running Flask.
In case there is an internal error in the back-end it returns an response object and an error code.:
response_object = {
"Success": False,
'error': err.message
}
return response_object, 400
The served response_object should be accessible the front-end (React.js).
axios
.get(`http://127.0.0.1:5000/data`, {
data: null,
headers: { "Content-Type": "application/json" }
})
.then(response => {
console.log(response);
})
.catch(function(error) {
console.log(error.toJSON());
});
I would expect the error to include the response object. If the URL is accessed manually in the browser the error data is visible. If there is no error in the back-end the get requests works properly.
After googling for some time I found some issues that might relate to the mentioned problem. (That is why empty data is passed in the a get request).
https://github.com/axios/axios/issues/86
https://github.com/axios/axios/issues/86
Please note that I am self taught so I might miss something obvious here. Thank you all and all the best.
I'll copy/paste my comment here so other can easily see the answer for the question
if it's a 400 status code that it's throwing (you can confirm by using the Network tab in your browser), it will fall into the catch block, the only concern is the toJSON() call... just do a simple console.log(error.message) to check if you ever get there...
I leave you a simple example so you see the catch in action
more information:
in Axios, the response text is in response.data
if you are receiving a JSON, the response.data will be automatically parsed
you do not need to pass data: null as that's the default behavior, and it's not used in a GET call (you won't pass a body in a GET call)

How to use an API with opaque response?

I tried to use this Cat Facts API like so:
const URL = "https://catfact.ninja/fact?limit=1" // In browser, this displays the JSON
fetch(URL).then(response=> {
console.log(response);
return response.json();
}
);
but I got
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://catfact.ninja/fact?limit=1. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
TypeError: NetworkError when attempting to fetch resource.
so after trying with
fetch(URL, {mode:'no-cors'})
.then(response=> {
console.log(response);
return response.json();
}
);
I now get
Response { type: "opaque", url: "", redirected: false, status: 0, ok: false, statusText: "", headers: Headers, body: null, bodyUsed: false }
SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data
I understand from here that I won't be able to use this API as intended. But if so, what is the purpose of it and how is it intended to be used (this info does not account for the issue)?
An opaque response is one you cannot see the content of. They aren't useful in of themselves.
Setting mode: 'no-cors' is a declaration that you don't need to read the response (or do anything else that requires CORS permission).
For example, the JavaScript might be sending analytics data to be recorded by a server.
The benefit of no-cors mode is that it lets you send the data without getting exceptions reported in the JS console (which would (a) look bad if anyone opened it and (b) flood the console with junk that makes it hard to find real errors).
If you need to access the response, don't set mode: 'no-cors'. If it is a cross origin request then you will need to use some other technique to bypass the Same Origin Policy.
Aside: "Access-Control-Allow-Origin": "*" is a response header. Do not put it on a request. It will do nothing useful and might turn a simple request into a preflighted request.
Adding {mode: 'no-cors'} is not a catch-all for CORS errors. You might be better off using a CORS Proxy.
This question might also be of use to you.
Alternatively, and depending on your needs, using a different API could be the easiest solution. I was able to return a cat fact from "https://meowfacts.herokuapp.com/". See below.
const URL = "https://meowfacts.herokuapp.com/"
async function getCatFact() {
const response = await fetch(URL)
console.log(await response.json())
}
getCatFact()

How do I do a POST request using the Google Chrome Puppeteer library?

Good Day All,
I'm trying to do a POST request using the puppeteer headless chrome library. I can't seem to get the below code to work.
// Get csrf token
let token = await page.evaluate(() => document.querySelector('[name="CSRFToken"]').value);
let postResponse = await page.evaluate(async(token, cookies) => {
let response = fetch("/loyalty/points", {
method : 'POST',
cookie : cookies,
postData : 'CSRFToken=' + token,
}).then(response => response.text()).catch(error => console.log(error));
return response;
});
console.log('Final response');
console.log(postResponse);
I keep on getting an error that the CSRF token has not been set or that the cookie is invalid.
My question is, am I using the correct method in puppeteer to do a POST? If so, is there any way for me to do some debugging that I can see the actual POST request that was sent?
I appreciate any advice or help. Thanks
You are not creating a request body: hence the error. The postData attribute you set on the request object is not any known attribute, so it won't be set on the request either, meaning that the server will never see your CSRF token. You should look into the MDN docs on fetch().
I believe you should be all good by simply replacing postData with body, but it's hard to know without access to your endpoint. For all we know it might require special headers.
Given that you only post normal form data (which is implied by your key=value code), I would also start using the FormData objects provided by your browser to avoid manual coding of implementation details.
const formData = new FormData();
formData.append("CSRFToken", token);
const response = fetch("/loyalty/points", {
method : 'POST',
cookie : cookies,
body : formData,
headers : {
'cookie' : cookies,
/* other headers you need, possibly content-type (see below) */
},
}).then(response => response.text()).catch(error => console.log(error));
return response;
});
Caveat: using the FormData API will always set the content-type of the data to multipart/form-data. If your server for some reason doesn't support that encoding, and you need to use application/x-www-form-urlencoded (see here for difference),
you can't blindly change the Content-Type: you also need to url encode the content.
For debugging I would simply use a normal Chrome instance to see this. You should be able to run the code there and see the network requests in DevTools (where it would be immediately noticeable that you POST an empty request).

How to get data from window.fetch() response?

I'm trying to use window.fetch() to get json from the server, but can't get the data from the response.
I have this code:
let url =
'https://api.flightstats.com/flex/schedules/rest/v1/json/from/SYD/to/MEL/departing/2016/3/28?appId=f4b1b6c9&appKey=59bd8f0274f2ae88aebd2c1db7794f7f';
let request = new Request (url, {
method: 'GET',
mode: 'no-cors'
});
fetch(request)
.then(function(response){
console.log(response)
});
It seems that this request is successfull, I see status 200
and response body with json in network tab - status and response. But in console.log I dont see json object - console log image
I cant understand why I dont see json in console.log
The host site you are requesting from does not appear to support CORS. As such, you can't use fetch() to make a cross origin request and get the data back. If, you change your fetch() request to mode: 'cors', the debug console will show that the host site does not offer CORS headers to allow the browser to show you the result of the request.
When you are using mode: 'no-cors', the browser is hiding the result from you (because you don't have permission to see it) and you can see the response is tagged as opaque.
In a little poking around on the api.flightstats.com site, I did see that it supports JSONP which will allow you to work around the lack of CORS support issue and successfully complete a cross origin request.
For simplicity of showing that it can work, I used jQuery to just prove that a JSONP request can be made. Here's that code in a working snippet. Note I changed the URL from /json/ to /jsonp/ and specific dataType: "jsonp" in the jQuery request. This causes jQuery to add the callback=xxxxx query parameter and to fetch the response via that corresponding script (the JSONP method).
var url =
'https://api.flightstats.com/flex/schedules/rest/v1/jsonp/from/SYD/to/MEL/departing/2016/3/28?appId=f4b1b6c9&appKey=59bd8f0274f2ae88aebd2c1db7794f7f';
$.ajax(url, {dataType: "jsonp"}).then(function(response) {
log(response);
}, function(err) {
log("$.ajax() error:")
log(err);
})
<script src="http://files.the-friend-family.com/log.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
If you take a look at the documentation of the Fetch API; you'll notice that the API offers various methods to extract the data:
arrayBuffer()
blob()
json()
text()
formData()
Assuming the response is valid JSON (which I've noticed it doesn't seem to appear), you can use the response.json() function to retrieve the response data. This also uses a Promise mechanism, as for everything with the Fetch API.
response.json().then(function(data) {
console.log(data);
});

Categories

Resources