Request doesn't work with URL from Response Body - javascript

Request doesn't work with the URL i get from response body. The URL prints fine in the console but working with it just doesn't work
request.post(`https://onesignal.com/api/v1/players/csv_export?app_id=${constants.ONESIGNAL_APPID}`, {
json: {
"extra_fields": ["country"]
}, headers: constants.AUTH_HEADER
}, (error, res, body) => {
if (error) {
reject(error)
return
}
console.log(body)
var csvURL = body.csv_file_url
console.log(csvURL)
request(csvURL) // <--- Request doesn't work with the csvURL from body
.pipe(fs.createWriteStream("./ji.csv.gz", { encoding: 'binary'}))
This just doesn't work! If i put the exact same printed URL string and put inside the request URL it works somehow, but if I take it from the response body directly it doesn't work.
This is the body output:
{
csv_file_url: 'https://onesignal.s3.amazonaws.com/csv_exports/theappid.csv.gz'
}
I also tried using axios instead of request but it still doesn't work
Update: Using node-fetch doesn't work either
async function getAllCountries() {
return new Promise((resolve, reject) => {
fetch(`https://onesignal.com/api/v1/players/csv_export?app_id=${constants.ONESIGNAL_APPID}`, {
method: 'post',
body: JSON.stringify({ extra_fields: ["country"] }),
headers: { 'Authorization': 'Basic ****', 'Content-Type': 'application/json' },
})
.then(res => res.json())
.then(json => {
const csvURL = json.csv_file_url
console.log(csvURL)
fetch(csvURL)
.then(
res => {
const dest = fs.createWriteStream("./ji.csv.gz");
res.body.pipe(dest);
res.body.on("end", () => {
gunzip('ji.csv.gz', 'ji.csv', async () => {
const usersCSVJSON = await csv().fromFile("ji.csv");
const onlyCountriesArray = usersCSVJSON.map((value) => { return value.country })
const singleCountries = new Set(onlyCountriesArray)
resolve([...singleCountries])
})
});
dest.on('error', (error)=> {
console("ERRORR")
})
})
})
})
}
```

First off, you don't need any special library, like Request or Axios, to do AJAX. All you need is the (built-in) fetch method (see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch for more info).
(NOTE: On Node you will need a library, eg. https://www.npmjs.com/package/node-fetch, since it sadly has no built-in fetch. But by using such a library you can then fetch the exact same way on both your client and server ... without a client library.)
Second ...
If i put the exact same printed URL string and put inside the request URL it works somehow, but if I take it from the response body directly it doesn't work.
This suggests that there is something in the response body you're not seeing. For instance, there might be a newline character, or some other special/invisible text character, at the start of the URL. If so, that could explain your problem.
To debug, I would recommend logging them side-by-side:
console.log(csvURL, '*PRINTED URL*')
to see if you can observe any difference.
Also you could try checking the .length of both strings, or doing a csvUrl.trim() to see if that helps. Ultimately though, your results are telling you that the two strings aren't the same ... because if they were, then your results would be also.

When using the url directly the file hasn't been created yet on the server so it didn't work. I needed to delay the function so now it works

Related

Expo json Unexpected End of Input

I have a pretty simple bit of javascript that sends a post command. As expected, I get a promise from the fetch command, but when trying to use the promise with response.json() I get an Unexpected end of input Syntax Error at the line indicated below. I get no issue when using response.text() and various other methods, it just seems to be .json() that breaks stuff. I am using React Native and this same code has worked fine with Node.js.
async function postData (url, data) {
const params = {
mode: 'no-cors',
method: 'POST',
headers:{
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
};
let response = await fetch(url,params);
var responseJson = await response.json();//This seems to be the problem
return response;
}
Here is some more code that may be helpful.
function postStuff() {
var url = "http://192.4.20.69:1337";
postData(url, data)
.then(res => {
console.log(res);
});
}
export default function App() {
console.log("App Executed");
return (
<View style={styles.container}>
<StatusBar style="auto" />
<Text>Hello!!</Text>
<Button
title="Post"
color={"#696969"}
accessibilityLabel="Click to send message"
onPress={postStuff}
/>
</View>
);
}
Any help figuring out why .json() is breaking this would be appreciated. I'd like to try sticking with using fetch but if I need to I can use one of the other ways of handling json but I think I may still run into the issue concerning .json().
Probably the issue lies in the server side response.
response.json() fail if no json is returned.
I suggest to you to check server response by adding a try catch around response.json() and print out response.text() in the catch .
It could also be due to the fact that your response is missing the required header content-type and fetch doesn't know how to parse it.
So you could try yo do something like this
async function postData (url, data) {
const params = {
mode: 'no-cors',
method: 'POST',
headers:{
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
};
let response = await fetch(url,params);
try{
return await response.json();//This seems to be the problem
}catch(e){
console.error(e);
const textResponse = await response.text();
return JSON.parse(textResponse)
}
return response;
}
Also I notice that you are returning response instead of responseJson that also could be the problem here

Refactor from fetch to await that can yield same result

So I moved over a non-reusable fetch request code snippet to my API:
let response = await fetch(visitURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + userJWT
},
body: JSON.stringify(endingVisit)
});
if (response.ok) {
let {visitId, createdAt} = await response.json();
const viewVisitDto = new ViewVisitDto(`${visitId}${createdAt}${visitorId}${doctorId}${oldPatientId}`);
return viewVisitDto;
} else {
throw new Error("deactivated!")
}
I was able to get this far:
axios.post(visitURL, {
headers,
body: JSON.stringify(visit)
}).then((response) => {
console.log(response);
}).catch((error) => {
console.log(error);
})
But does not exactly give me the visitId and createdAt from the response and I cannot use a response.ok nor a response.json(). Essentially I need to pull out that visitId and createdAt that should be coming back in the response.
I also tried just using node-fetch library, but although in VS code it seems to accept it, TypeScript is not happy with it even when I do install #types/node-fetch and even when I create a type definition file for it, my API just doesn't like it.
Guessing what you are after is
// don't know axios, but if it returns a promise await it
const dto = await axios.post(visitURL, {
headers,
body: JSON.stringify(visit)
}).then((response) => {
// parse response
return {resonse.visitId, resonse.createdAt}
}).then(({visitId, createdAt}) => {
// form dto (where are other vals)?
return new ViewVisitDto(`${visitId}${createdAt}${visitorId}${doctorId}${oldPatientId}`);
}).catch((error) => {
console.log(error);
})
However - you don't mention where doctorId and oldPatientId come from... You try providing more info, including output of the console.log's and the surrounding code

.env file access token working for API but not another in Next.js

I'm calling two DBs -- WordsAPI and Spotify API. For each DB there is an access token/db key, both of which I've stored in a .env file.
I'm fetch the data in identical ways in getStaticProps and it's working just fine for WordsAPI but not Spotify. In Postman, Spotify works just fine.
Here are the calls:
export async function getStaticProps(context) {
const wordsRes = await fetch(
`https://wordsapiv1.p.rapidapi.com/words/${context.params.word}/definitions`,
{
method: "GET",
headers: {
"x-rapidapi-key": process.env.NEXT_PUBLIC_DB_KEY,
"x-rapidapi-host": "wordsapiv1.p.rapidapi.com",
},
}
)
.then((response) => {
return response;
})
.catch((err) => {
console.error(err);
});
const songsRes = await fetch(
`https://api.spotify.com/v1/search?q=${context.params.word}&type=track`,
{
method: "GET",
headers: {
authorization: "Bearer " + process.env.NEXT_PUBLIC_SPOTIFY_ACCESS_TOKEN,
},
}
)
.then((response) => {
return response;
})
.catch((err) => {
console.error(err);
});
const wordsData = await wordsRes.json();
const songsData = await songsRes.json();
return {
props: {
wordsData,
songsData,
},
};
}
the .env file is as simple as follows:
NEXT_PUBLIC_SPOTIFY_ACCESS_TOKEN=BQDcgHT2IfAXsXXX_XXX_XXX
NEXT_PUBLIC_DB_KEY=a2c92537d6mshXXX
I heard that leaving the key at the bottom of the file without an extra line can mess it up, but switching them around didn't change anything. I've also tried various combinations of switching to string format in each file, switching the header key to "authorization", using string interpolation etc. Also tried naming my file .env.local (as was recommended for Next.js), but nothing works.
Most of the solutions on SO seem to be about .env files not working at all, which isn't the case here.
I have reviewed the code everything looks fine in general. It would help if you restarted the server whenever you made a new file, I believe. Try restating the server. It should work.

Building API end point in Firebase Functions that receives values and calculates them

i am trying to build a function/endpoint that receives some values, calculates them, and returns a pass/fail and a message, hopefully it works like this:
export const processLoanRequest = functions.https.onRequest(async (request, response) => {
// Receive values
// determine if values they pass a criteria
// returns true (pass) or false (fail), as well as a message that says why
})
I tried a few things to see if I could work with it off the bat, but I don't think I even came close, like this one:
export const processLoanRequest = functions.https.onRequest((request, response) => {
console.log({ request, response });
const obj = {
name: 'the object',
param: 'object param',
};
response.json(obj);
});
The function I wrote in my React app to post a request is copied from MDN Web Docs -- Using Fetch and looks like:
// Example POST method implementation:
async function postData(url = '', data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST',
mode: 'no-cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: JSON.stringify(data),
});
return response.json();
}
const handleSubmit = values => {
console.log(values);
// postLoanInfo(values).then(data => console.log(data));
postData(
'https://us-central1-autoloan-24e0d.cloudfunctions.net/processLoanRequest',
{ ...values }
)
.then(data => {
console.log(data); // JSON data parsed by `data.json()` call
})
.catch(err => console.log(err));
};
With those I always ended up with an error message that says:
SyntaxError: Unexpected end of input
at LoanForm.js:35
at s (runtime.js:63)
at Generator._invoke (runtime.js:293)
at Generator.next (runtime.js:118)
at r (asyncToGenerator.js:3)
at u (asyncToGenerator.js:25)
From the catch(err...), and line 35 is return response.json(); in postData(..). What happens to the body when the request is posted? How is it received at the end point and how do I access it from there?
The values object looks like:
{purchasePrice: 12000, autoMake: "kia", autoModel: "soul", yearlyIncome: 50000, creditScore: 680}
Thanks!
Update:
Turns out that I had to implement Rafael's solution below, as well as response.set('Access-Control-Allow-Headers', 'Content-Type'); to get it to work. I am not very experienced so I did not think to look under the network tab in the dev tools, which gave indicated what had to change.
Update2:
response.set('Access-Control-Allow-Headers', 'Content-Type'); is only needed if you're posting a request that has headers (i.e. has a body that contains values). It isn't necessary for the above example since I'm not posting anything and I'm only getting the value of obj in return.
Answering as Community Wiki as this is based on #RobertNubel's comment.
The error you are getting means that the response body is not valid JSON, which is likely to be caused by some other error in the background. You can open the browser's network inspector on your React site to monitor the API call as it's made to get more info on what's happening.
As you can see on this community question, this issue is likely to be caused by lack of CORS configuration on your response at the cloud function, in order to fix that, all you have to do is add a couple of extra lines to your functions as follows:
export const processLoanRequest = functions.https.onRequest((request, response) => {
console.log({ request, response });
const obj = {
name: 'the object',
param: 'object param',
};
response.set('Access-Control-Allow-Origin', "*")
response.set('Access-Control-Allow-Methods', 'GET, POST')
response.set('Access-Control-Allow-Headers', 'Content-Type')
response.json(obj);
});

Axios - DELETE Request With Request Body and Headers?

I'm using Axios while programming in ReactJS and I pretend to send a DELETE request to my server.
To do so I need the headers:
headers: {
'Authorization': ...
}
and the body is composed of
var payload = {
"username": ..
}
I've been searching in the inter webs and only found that the DELETE method requires a "param" and accepts no "data".
I've been trying to send it like so:
axios.delete(URL, payload, header);
or even
axios.delete(URL, {params: payload}, header);
But nothing seems to work...
Can someone tell me if it's possible (I presume it is) to send a DELETE request with both headers and body and how to do so?
So after a number of tries, I found it working.
Please follow the order sequence it's very important else it won't work
axios.delete(URL, {
headers: {
Authorization: authorizationToken
},
data: {
source: source
}
});
axios.delete does supports both request body and headers.
It accepts two parameters: url and optional config. You can use config.data to set the request body and headers as follows:
axios.delete(url, { data: { foo: "bar" }, headers: { "Authorization": "***" } });
See here - https://github.com/axios/axios/issues/897
Here is a brief summary of the formats required to send various http verbs with axios:
GET: Two ways
First method
axios.get('/user?ID=12345')
.then(function (response) {
// Do something
})
Second method
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
// Do something
})
The two above are equivalent. Observe the params keyword in the second method.
POST and PATCH
axios.post('any-url', payload).then(
// payload is the body of the request
// Do something
)
axios.patch('any-url', payload).then(
// payload is the body of the request
// Do something
)
DELETE
axios.delete('url', { data: payload }).then(
// Observe the data keyword this time. Very important
// payload is the request body
// Do something
)
Key take aways
get requests optionally need a params key to properly set query parameters
delete requests with a body need it to be set under a data key
axios.delete is passed a url and an optional configuration.
axios.delete(url[, config])
The fields available to the configuration can include the headers.
This makes it so that the API call can be written as:
const headers = {
'Authorization': 'Bearer paperboy'
}
const data = {
foo: 'bar'
}
axios.delete('https://foo.svc/resource', {headers, data})
For those who tried everything above and still don't see the payload with the request - make sure you have:
"axios": "^0.21.1" (not 0.20.0)
Then, the above solutions work
axios.delete("URL", {
headers: {
Authorization: `Bearer ${token}`,
},
data: {
var1: "var1",
var2: "var2"
},
})
You can access the payload with
req.body.var1, req.body.var2
Here's the issue:
https://github.com/axios/axios/issues/3335
For Delete, you will need to do as per the following
axios.delete("/<your endpoint>", { data:<"payload object">})
It worked for me.
I had the same issue I solved it like that:
axios.delete(url, {data:{username:"user", password:"pass"}, headers:{Authorization: "token"}})
Actually, axios.delete supports a request body.
It accepts two parameters: a URL and an optional config. That is...
axios.delete(url: string, config?: AxiosRequestConfig | undefined)
You can do the following to set the response body for the delete request:
let config = {
headers: {
Authorization: authToken
},
data: { //! Take note of the `data` keyword. This is the request body.
key: value,
... //! more `key: value` pairs as desired.
}
}
axios.delete(url, config)
I hope this helps someone!
If we have:
myData = { field1: val1, field2: val2 }
We could transform the data (JSON) into a string then send it, as a parameter, toward the backend:
axios.delete("http://localhost:[YOUR PORT]/api/delete/" + JSON.stringify(myData),
{ headers: { 'authorization': localStorage.getItem('token') } }
)
In the server side, we get our object back:
app.delete("/api/delete/:dataFromFrontEnd", requireAuth, (req, res) => {
// we could get our object back:
const myData = JSON.parse(req.params.dataFromFrontEnd)
})
Note: the answer from "x4wiz" on Feb 14 at 15:49 is more accurate to the question than mine! My solution is without the "body" (it could be helpful in some situation...)
Update: my solution is NOT working when the object has the weight of 540 Bytes (15*UUIDv4) and more (please, check the documentation for the exact value). The solution of "x4wiz" (and many others above) is way better. So, why not delete my answer? Because, it works, but mostly, it brings me most of my Stackoverflow's reputation ;-)
i found a way that's works:
axios
.delete(URL, {
params: { id: 'IDDataBase'},
headers: {
token: 'TOKEN',
},
})
.then(function (response) {
})
.catch(function (error) {
console.log(error);
});
I hope this work for you too.
To send an HTTP DELETE with some headers via axios I've done this:
const deleteUrl = "http//foo.bar.baz";
const httpReqHeaders = {
'Authorization': token,
'Content-Type': 'application/json'
};
// check the structure here: https://github.com/axios/axios#request-config
const axiosConfigObject = {headers: httpReqHeaders};
axios.delete(deleteUrl, axiosConfigObject);
The axios syntax for different HTTP verbs (GET, POST, PUT, DELETE) is tricky because sometimes the 2nd parameter is supposed to be the HTTP body, some other times (when it might not be needed) you just pass the headers as the 2nd parameter.
However let's say you need to send an HTTP POST request without an HTTP body, then you need to pass undefined as the 2nd parameter.
Bare in mind that according to the definition of the configuration object (https://github.com/axios/axios#request-config) you can still pass an HTTP body in the HTTP call via the data field when calling axios.delete, however for the HTTP DELETE verb it will be ignored.
This confusion between the 2nd parameter being sometimes the HTTP body and some other time the whole config object for axios is due to how the HTTP rules have been implemented. Sometimes an HTTP body is not needed for an HTTP call to be considered valid.
For Axios DELETE Request, you need to include request payload and headers like this under one JSON object:
axios.delete(URL, {
headers: {
'Authorization': ...
},
data: {
"username": ...
}
})
Why can't I do it easily as I do similar to POST requests?
Looking at the Axios documentation, we see that the methods for .get, .post... have a different signature:
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
Notice how only post, patch and put have the data parameter. This is because these methods are the ones that usually include a body.
Looking at RFC7231, we see that a DELETE request is not expected to have a body; if you include a body, what it will mean is not defined in the spec, and servers are not expected to understand it.
A payload within a DELETE request message has no defined semantics; sending a payload body on a DELETE request might cause some existing implementations to reject the request.
(From the 5th paragraph here).
In this case, if you are also in control of the server, you could decide to accept this body in the request and give it whatever semantics you want. May be you are working with somebody else's server, and they expect this body.
Because DELETE requests with bodies are not defined in the specs, and because they're not common, Axios didn't include them in those method aliases. But, because they're possible, you can do it, just takes a bit more effort.
I'd argue that it would be more conventional to include the information on the url, so you'd do:
axios.delete(
`https://example.com/user/${encodeURIComponent(username}`,
{ headers: ... }
)
or, if you want to be able to delete the user using different criteria (sometimes by username, or by email, or by id...)
axios.delete(
`https://example.com/user?username=${encodeURIComponent(username)}`,
{ headers: ... }
)
Not realated to axios but might help people tackle the problem they are looking for. PHP doesn't parse post data when preforming a delete call. Axios delete can send body content with a request.
example:
//post example
let url = 'http://local.test/test/test.php';
let formData = new FormData();
formData.append('asdf', 'asdf');
formData.append('test', 'test');
axios({
url: url,
method: 'post',
data: formData,
}).then(function (response) {
console.log(response);
})
result: $_POST Array
(
[asdf] => asdf
[test] => test
)
// delete example
axios({
url: url,
method: 'delete',
data: formData,
}).then(function (response) {
console.log(response);
})
result: $_POST Array
(
)
to get post data on delete call in php use:
file_get_contents('php://input');
axios.post('/myentity/839', {
_method: 'DELETE'
})
.then( response => {
//handle success
})
.catch( error => {
//handle failure
});
Thanks to:
https://www.mikehealy.com.au/deleting-with-axios-and-laravel/
I encountered the same problem...
I solved it by creating a custom axios instance. and using that to make a authenticated delete request..
const token = localStorage.getItem('token');
const request = axios.create({
headers: {
Authorization: token
}
});
await request.delete('<your route>, { data: { <your data> }});
I tried all of the above which did not work for me. I ended up just going with PUT (inspiration found here) and just changed my server side logic to perform a delete on this url call. (django rest framework function override).
e.g.
.put(`http://127.0.0.1:8006/api/updatetoken/20`, bayst)
.then((response) => response.data)
.catch((error) => { throw error.response.data; });
Use {data: {key: value}} JSON object, the example code snippet is given below:
// Frontend Code
axios.delete(`URL`, {
data: {id: "abcd", info: "abcd"},
})
.then(res => {
console.log(res);
});
// Backend Code (express.js)
app.delete("URL", (req, res) => {
const id = req.body.id;
const info = req.body.info;
db.query("DELETE FROM abc_table WHERE id=? AND info=?;", [id, info],
(err, result) => {
if (err) console.log(err);
else res.send(result);
}
);
});
Axios DELETE request does supports similar what POST request does, but comes in different formats.
DELETE request payload sample code:
axios.delete(url, { data: { hello: "world" }, headers: { "Authorization": "Bearer_token_here" } });
POST request payload sample code:
axios.post(url, { hello: "world" }, { headers: { "Authorization": "Bearer_token_here" } });
Noticed that { hello: "world" } is configured in different ways, but both performs same functions.
this code is generated from post man and it's perfectly work for delete api request with body.
var data = JSON.stringify({"profile":"false","cover":"true"});
var config = {
method: 'delete',
url: 'https://api.fox.com/dev/user/image',
headers: {
'Authorization': 'Bearer token',
},
data : data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});

Categories