Basic error with accessing JSON data (JS functionality) - javascript

Background:
I'm trying the simple web push notifications example given in Google's documentation of the same (link: https://developers.google.com/web/fundamentals/push-notifications/subscribing-a-user)
I keep running into a SyntaxError: JSON.parse: unexpected character at line 2 column 1 of the JSON data error, which means I'm doing something fundamentally wrong. I'm a JS neophyte, can you help?
My simple function to subscribe user to push is simply:
function subscribeUserToPush() {
const pub_key = document.getElementById("notif_pub_key");
const service_worker_location = document.getElementById("sw_loc");
return navigator.serviceWorker.register(service_worker_location.value)
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(pub_key.value)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
sendSubscriptionToBackEnd(pushSubscription);
return pushSubscription;
});
}
And sendSubscriptionToBackEnd() essentially uses fetch like so:
function sendSubscriptionToBackEnd(subscription) {
const sub_obj = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'CSRFToken':get_cookie('csrftoken')
},
body: JSON.stringify(subscription)
}
return fetch('/subscription/save/', sub_obj)
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
This fails with the error SyntaxError: JSON.parse: unexpected character at line 2 column 1 of the JSON data.
Doing console.log(sub_obj) shows this object:
Object { method: "POST", headers: {…}, body: "{\"endpoint\":\"https://updates.push.services.mozilla.com/wpush/v2/gAAAAABcaErG3Zn6Urzn5Hfhpyjl0eJg_IVtcgZI-sQr5KGE0WEWt9mKjYb7YXU60wgJtj9gYusApIJnObN0Vvm7oJFRXhbehxtSFxqHLOhSt9MvbIg0tQancpNAcSZ3fWA89E-W6hu0x4dqzqnxqP9KeQ42MYZnelO_IK7Ao1cWlJ41w8wZSlc\",\"keys\":{\"auth\":\"AJfXcUMO3ciEZL1DdD2AbA\",\"p256dh\":\"BN84oKD3-vFqlJnLU4IY7qgmPeSG96un-DttKZnSJhrFMWwLrH2j1a0tTB_QLoq5oLCAQql6hLDJ1W4hgnFQQUs\"}}" }
Also doing console.log(response); right before return response.json(); displays:
Response { type: "basic", url: "http://127.0.0.1:8001/subscription/save/", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers, body: ReadableStream, bodyUsed: false }
What's the problem and how do I fix it?
Changing return response.json() to return response.text() and then doing a console log on responseData gives the entire HTML of the page. I end up with the error Error: Bad response from server.

The main issue was that CSRFToken was mislabeled when being set in sendSubscriptionToBackEnd. It should have been X-CSRFToken. I.e.
function sendSubscriptionToBackEnd(subscription) {
const sub_obj = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken':get_cookie('csrftoken'),
},
body: JSON.stringify(subscription)
}
return fetch('/subscription/save/', sub_obj)
.then(function(response) {
console.log(response);
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
// response from the server
console.log(responseData)
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
So why was this leading to return response.json(); failing?
Because the project in question routes requests that fail csrf checks to a default view - one which doesn't return a json response at all. Mystery solved!

Related

How to handle 429 errors with fetch

I am trying to retrieve data from my own rest api. On the backend side I have express-rate-limit in use. Now I am trying to handle 429 error when it occurs. I want to display something to the user like "
Too many requests, please try again later.
How can I read the statusText of the error?
try {
const data = await fetch("http://localhost:5000/login", {
method: "POST",
body: JSON.stringify(
{
email
password
}
),
headers: {
"Accept": "application/json",
'Content-Type': 'application/json'
}
})
const result = await data.json()
if (data.status !== 200) {
throw new Error(result.detail.message)
}
console.log(result)
} catch (e) {
console.log(e) //shows: There was an error SyntaxError
}

Response.redirect() is not a function in vanilla JS?

I am trying to redirect to another page after I receive a response from the post fetch, but as the title says it doesn't work.
These are the functions:
// send/post json
async function postData(json_data, api_path) {
const response = await fetch(api_path, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: json_data,
redirect: 'follow'
});
console.log("postData response: ", response);
return response;
}
// send JSON data to server on /api/${destination}
function saveSettings(form, destination) {
let json_data = toJSONstring(form);
let res;
console.log(json_data);
postData(json_data, `/api/${destination}`)
.then((response) => {
res = response;
if (!response.ok) {
throw new Error(`HTTP error, status = ${response.status}`);
}
return response.text();
}).then(text => {
if (destination === 'network/post') {
connected = false;
updatingToast(`You are no longer connected to the device !`, false);
updatingToast(`Please navigate to ${text}`, true, text);
}
console.log('res: ', res);
res.redirect(res.status, res.url);
});
}
Every console.log(); returns Response {type: 'basic', url: 'http://192.168.0.100/dashboard', redirected: true, status: 200, ok: true, …}
If I place response.redirect(response.status, response.url); in the first then() I get the same error.
So, does response.redirect exist in Vanilla JS ?
I don't want to use window.location.href or any other similar option because it bypasses HTTP Authentication header.
I see that you have the 'follow' argument given in the fetch.
You can check the if the response is being redirected using the code below. If it was not redirected you can simply change the window location and also force a redirect.
if (res.redirected) {
window.location.href = res.url;
}
EDIT:
After doing a bit more research into the redirect method I saw that you need to switch the URL and status variables, see: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect

How to handle the promise of a fetch without nested callbacks?

I guess that fetch returns a promise. But how do I handle it nicely? The code below does not quite work. I get {message: "Internal server error custom: TypeError: Cannot read property 'then' of undefined"}.
exports.handler = (event, context, callback) => {
try {
getDiscourseId(username, callback).then((userId) => {
callback(null, {
statusCode: 200,
headers: {},
body: JSON.stringify({
userId: userId
})
});
});
} catch (error) {
callback(null, {
statusCode: 500,
headers: {},
body: JSON.stringify({
message: "Internal server error custom: " + error
})
});
}
};
function getDiscourseId(username) {
console.log({username: username, discourseApiKey: discourseApiKey, discourseApiUser: discourseApiUser})
fetch(`https://${domain}/users/${username}.json?api_key=${discourseApiKey}&api_username=${discourseApiUser}`, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
method: 'GET',
})
.then(response => {
return response.json();
})
.then(data => {
if (data) {
return data.user.id;
}
})
.catch(err => {
return {err: err};
});
}
You're getting that error because your getDiscourseId function does not return a value.
If you add the keyword return in front of your fetch(...) call, you should start making some headway.
You'll probably also want to remove the .catch from inside getDiscourseId and instead add it to the end of the call to getDiscourseId inside your handler:
exports.handler = (event, context, callback) => {
getDiscourseId(username)
.then((userId) => {
callback(null, {
statusCode: 200,
headers: {},
body: JSON.stringify({
userId: userId
})
});
})
.catch(error => {
callback(null, {
statusCode: 500,
headers: {},
body: JSON.stringify({
message: "Internal server error custom: " + error
})
});
});
};
function getDiscourseId(username) {
console.log({username: username, discourseApiKey: discourseApiKey, discourseApiUser: discourseApiUser})
return fetch(`https://${domain}/users/${username}.json?api_key=${discourseApiKey}&api_username=${discourseApiUser}`, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
method: 'GET',
})
.then(response => {
if (!response.ok) { // h/t TJ Crowder
throw new Error("Failed with HTTP code " + response.status);
}
return response.json();
})
.then(data => {
if (data) {
return data.user.id;
}
});
}
EDIT: TJ Crowder is correct that you probably want to treat 4xx and 5xx responses as full-fledged errors. I shamelessly stole his example code from his blog and added it to the above.
Stop when you return the response.json(). This returns a promise, for which .then can be used.
You are returning the userId which .then cannot be used for.
If you stop at the return response.json(), you can use the '.then' statement that you already have (data.user.id => ...).

React and NodeJS: How can i use received data from Server on Client?

I want to use received data from server on client . I use a NodeJS Server with NextJS and React.
I use this function on the server:
function addEmailToMailChimp(email, callback) {
var options = {
method: 'POST',
url: 'https://XXX.api.mailchimp.com/3.0/lists/XXX/members',
headers:
{
'Postman-Token': 'XXX',
'Cache-Control': 'no-cache',
Authorization: 'Basic XXX',
'Content-Type': 'application/json'
},
body: { email_address: email, status: 'subscribed' },
json: true
};
request(options, callback);
}
The function will be run from this point:
server.post('/', (req, res) => {
addEmailToMailChimp(req.body.email, (error, response, body) => {
// This is the callback function which is passed to `addEmailToMailChimp`
try {
var respObj = {}; //Initial response object
if (response.statusCode === 200) {
respObj = { success: `Subscribed using ${req.body.email}!`, message: JSON.parse(response.body) };
} else {
respObj = { error: `Error trying to subscribe ${req.body.email}. Please try again.`, message: JSON.parse(response.body) };
}
res.send(respObj);
} catch (err) {
var respErrorObj = { error: 'There was an error with your request', message: err.message };
res.send(respErrorObj);
}
});
})
The try method is used to verify that an email address could be successfully saved to MailChimp. An appropriate message is sent to the client.
On the Client-Side, i use this function to receive and display the data from the server:
handleSubmit() {
const email = this.state.email;
this.setState({email: ""});
fetch('/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({email:email}),
}).then(res => {
if(res.data.success) {
//If the response from MailChimp is good...
toaster.success('Subscribed!', res.data.success);
this.setState({ email: '' });
} else {
//Handle the bad MailChimp response...
toaster.warning('Unable to subscribe!', res.data.error);
}
}).catch(error => {
//This catch block returns an error if Node API returns an error
toaster.danger('Error. Please try again later.', error.message);
});
}
The problem: The email address is saved successfully at MailChimp, but the message is always displayed: 'Error. Please try again later.'from the .catch area. When i log the error from the catch area i get this:
TypeError: Cannot read property 'success' of undefined
Where is my mistake? I have little experience in Node.js environments. I would be very grateful if you could show me concrete solutions. Thank you for your replies.
With fetch theres no data property on the response. You have to call res.json() and return that promise. From there the response body will be read and deserialized.
handleSubmit() {
const email = this.state.email;
this.setState({email: ""});
fetch('/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({email:email}),
})
.then(res => {
console.log(res); //to make sure the expected object is returned
if(res.data.success) {
//If the response from MailChimp is good...
toaster.success('Subscribed!', res.data.success);
this.setState({ email: '' });
} else {
//Handle the bad MailChimp response...
toaster.warning('Unable to subscribe!', res.data.error);
}
}).catch(error => {
//This catch block returns an error if Node API returns an error
toaster.danger('Error. Please try again later.', error.message);
});
}
Two things you need to change:
Call and wait for res.json() to get the response body as json object.
The result of 1. is your 'data' object that you can use directly
handleSubmit() {
//...
fetch('/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({email:email}),
})
.then(res => res.json())
.then(data => {
if(data.success) {
//...
toaster.success('Subscribed!', data.success);
} else {
toaster.warning('Unable to subscribe!', data.error);
}
}).catch(error => {
//...
});
}

Handling non JSON response with a Body.json() promise

I'm trying to create a scheme to intercept and handle requests from an API middleware, however, for whatever reason I'm unable to properly handle non JSON responses from my API endpoint. The following snippet works just fine for server responses formatted in JSON however say an user has an invalid token, the server returns a simple Unauthorized Access response that I'm unable to handle even though I am supplying an error callback to the json() promise. The Unauthorized Access response message is lost in the following scheme.
const callAPI = () => { fetch('http://127.0.0.1:5000/auth/', {
method: 'GET',
headers: {
'credentials': 'include',
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic bXlKaGJHY2lPaUpJVXpJMU5pSXNJbVY0Y0NJNk1UUTVPRE15TVRNeU5pd2lhV0YwSWpveE5EazRNak0wT1RJMmZRLmV5SnBaQ0k2TVgwLllFdWdKNF9YM0NlWlcyR2l0SGtOZGdTNkpsRDhyRE9vZ2lkNGVvaVhiMEU6'
}
});
};
return callAPI().then(res => {
return res.json().then(responseJSON => {
if(responseJSON.status === 200){
return dispatch({
type: type[1],
data: responseJSON,
message: success
});
} else if(responseJSON.status === 401) {
return dispatch({
type: type[2],
message: responseJSON.message
});
}
return Promise.resolve(json);
}, (err) => {
console.log(err.toString(), ' an error occured');
});
}, err => {
console.log('An error occured. Please try again.');
});
Try using text method of Body: res.text().
Try to wrap your response handling code in a try...catch block like this:
return callAPI().then(res => {
try {
return res.json().then(responseJSON => {
[...]
catch(e) {
console.error(e);
}
});
Body.json() throws when the body is actually not JSON. Therefore, you should check if the body contains JSON before you call json() on it. See https://developer.mozilla.org/en-US/docs/Web/API/Response.

Categories