I'm building a web app that should fetch specific stock data for the user's chosen stock from Yahoo Finance and then send this data to a Node/Express server.
PROBLEM: Everything works, except that what is received by Node/Express are the original null-values in the JavaScript object and not the values received from Yahoo Finance.
The initial object variable definitions are first made in index.js:
const stockData = {
ticker: "",
name: "",
dateAdd: 0,
priceDateAdd: 0,
priceNow: 0,
movement: 0
};
The post method is defined:
const options = {
method: 'POST',
body: JSON.stringify(stockData),
headers: {
"Content-Type": "application/json"
}
Frontend, the user now chooses a ticker, and a request is sent to Yahoo Finance API to get real values for the stockData object for that ticker. No problems there. E.g. for stockData.priceDateAdd the browser console returns the value 67 for a sample stock.
A function now runs to send the fetched values from Yahoo Finance to Node/Express:
async function sendData() {
const response = await fetch('/add', options);
const serverData = await response.json();
console.log(serverData);
BUT ... the response from Node/Express shows the original null-values of the object, not the ones fetched from Yahho Finance, i.e., this is the response:
{
ticker: '',
name: '',
dateAdd: 0,
priceDateAdd: 0,
priceNow: 0,
movement: 0
}
I run a second browser console.log after the response from the server, and this continues to show the correct value of stockData.priceDateAdd as 67.
But for some reason the server receives the original null-values. Have spent hours and can't figure it out. Here's hoping for help.
After more trial and error I have answered my own question:
I moved the const definition inside the function that is being called to send the data, and this worked. So the code is now:
async function sendData() {
const options = {
method: 'POST',
body: JSON.stringify(stockData),
headers: {
"Content-Type": "application/json"
}
};
const response = await fetch('/add', options);
const serverData = await response.json();
console.log(serverData);
}
Related
I'm using the Vimeo API to upload videos and am trying to track the progress of the upload.
The documentation here is pretty straightforward:
https://developer.vimeo.com/api/upload/videos
However, I can't seem to figure out how to retrieve Upload-Length and Upload-Offset from the HEAD response.
I call the "uploadVideo" function below to upload the video to Vimeo (this function does as it should). I then call the "getProgress" function and this is where things go awry. I've tried many variations of this code, but none have worked.
async function uploadVideo(upload_link : string) {
const uploadResponse = await fetch(upload_link, {
method: 'PATCH',
headers: {
'Tus-Resumable': '1.0.0',
'Upload-Offset': '0',
'Content-Type': 'application/offset+octet-stream'
},
body: accepted
});
}
async function getProgress(upload_link : string) {
const progress = await fetch(upload_link, {
method: 'HEAD',
headers: {
'Tus-Resumable': '1.0.0',
'Accept': 'application/vnd.vimeo.*+json;version=3.4'
},
});
const currentProgress = await progress;
console.log(currentProgress);
// if (currentProgress.upload_length != currentProgress.upload_offset) {
// getProgress(upload_link)
// }
}
If I await progress.json(), I get a SyntaxError: Unexpected end of JSON input
I'm somewhat surprised that there are no up-to-date JavaScript examples of this process out there on the interwebs. Any assistance would be greatly appreciated.
Thank you for your time.
As #Clive pointed out above, to access the necessary headers, one would use:
uploadLength = progress.headers.get('upload-length');
uploadOffset = progress.headers.get('upload-offset');
This answers my specific question.
However, if you're only using the Vimeo API, you'll find that there's another challenge once this is complete. In the original code posted above, you'll never be able to track the progress of the upload with a HEAD request because the "upload-offset" value is always 0 until the initial PATCH request is completed, i.e. it's 0 until the PATCH request is complete and once it's complete it jumps directly to 100%.
To get around this issue, I decided to use "tus-js-client." So, if you've made it to where my code above leaves off, instead of using the above functions you could just pass the link (in this example, "upload_link") and the file (in this example, "accepted") to:
async function uploadVideo(upload_link : string) {
// Create the tus upload similar to the example from above
var upload = new tus.Upload(accepted, {
uploadUrl: upload_link,
onError: function(error) {
console.log("Failed because: " + error)
},
onProgress: function(bytesUploaded, bytesTotal) {
var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)
console.log(bytesUploaded, bytesTotal, percentage + "%")
},
onSuccess: function() {
console.log("Download %s from %s", upload.file.path, upload.url)
}
})
// Start the upload
upload.start()
}
And here's the server-side code to get the "upload_link":
export const actions: Actions = {
upload: async ({ request }) => {
const uploadFormData = await request.formData();
const accepted = uploadFormData.get('accepted-file') as File;
const response = await fetch(`https://api.vimeo.com/me/videos`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `bearer ${import.meta.env.VITE_VIMEO_ACCESS_TOKEN}`,
'Accept': 'application/vnd.vimeo.*+json;version=3.4'
},
body: JSON.stringify({
upload: {
"approach": "tus",
"size": accepted.size
}
})
});
const dataResponse = await response.json();
return {
upload: dataResponse.upload
}
}
}
This server response is returned to a client-side "handleSubmit" function, which in turn calls the "uploadVideo" function, like so uploadVideo(result.data.upload.upload_link).
I was initially using "vimeo-upload" to accomplish this. The problems with vimeo-upload are (1) it exposes your access token to the browser and (2) the code base is outdated. I'd advise to stay away from vimeo-upload at all costs!
For what it's worth, this is a SvelteKit implementation.
If you're using SvelteKit, best to not use an import.meta.env.VITE prefixed environment variable; it should be a "private" environment variable as shown here:
https://joyofcode.xyz/sveltekit-environment-variables
I had such a hard time figuring out how to do this. I hope that this example will help someone in the future.
I'm creating a web chat and I need to integrate an assistant using Twilio Autopilot, I use Autopilot's custom channels as follows:
I send a POST request to https://channels.autopilot.twilio.com/v2/{AccountSid}/{AssistantSid}/custom/webchat
I fill in these fields in the request data (Language=en-US, UserId=user123, Text=Something)
I add a Memory parameter in the Assistant URL
My backend code:
// Assistant URL Memory parameter
const context = JSON.stringify({
accountHolder: "foo bar",
balance: 123
}, null, "");
// Request params
const urlencoded = new URLSearchParams();
urlencoded.append("Language", "en-US");
urlencoded.append("UserId", message.Author);
urlencoded.append("Text", message.Body);
const requestConfig: AxiosRequestConfig = {
headers: {
"content-type": "application/x-www-form-urlencoded",
"accept": "application/json",
"authorization" : "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
},
method: "POST",
url: `https://channels.autopilot.twilio.com/v2/${accountSid}/${assistantSid}/custom/webchat?Memory=${context}`,
data: urlencoded
};
// Trigger the bot
const twilioResponse = this.httpService.request(requestConfig);
const result = await lastValueFrom(twilioResponse.pipe(map((response) => response.data)));
Twilio function:
exports.handler = async function(context, event, callback) {
console.log(event.Memory);
const userData = JSON.parse(event.Memory); // Memory does not contains my payload !
return callback(null, {
"actions": [
{
"say": `Hello ${userData.accountHolder}, your balance is ${userData.balance}`
},
{
"listen": true
}
]
});
};
My problem is, The Memory parameter is not sent to the function
I had a similar problem trying to pass the payload in and reached out to a Twilio support.
The answer I received was:
"As per the present update from our engineering team, they are not looking at implementing the Memory parameter on V2 endpoint as there are very less users consuming it. You will have to use V1 endpoint for Autopilot if you are looking to use URL Parameter "Memory" for your use case. "
Therefore the URL will need to look like so:
https://channels.autopilot.twilio.com/v1/ACxxx/UAxxx/custom/chat?Memory={"CarModel":"Diablo","CarMake":"Lamborghini","CarYear":"2019"}'
Twilio developer evangelist here.
I would have thought you might want to URL encode the JSON memory when you add it to the URL. Try this:
const context = encodeURIComponent(
JSON.stringify({
accountHolder: "foo bar",
balance: 123
}, null, "")
);
I'm running the below fetch request on an array of items, they all fail expect the last one. It doesn't matter if the number of items are 10, 100, only the last item in an array will succeed in the fetch request.
var issns = ['09295666', '08989621', '02365294', '03643107', '02365294', '00015970', '03038173'];
issns.forEach((issn) => {
goFetchBoy(issn);
});
async function goFetchBoy(issn) {
var postresponse = await fetch('https://proquestFake.com/api/1-0-0/issnsearch', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
isxn: isxn,
resultsPerPage: 20,
}),
});
var data = await postresponse.json();
console.log(data);
}
Same code when tried in ChromeExtension>background script:
Same code when tried in Devtools>console.log :
As can be seen in the screenshot from devtools, the array has a value.
Note: I also rewrote this code in Ajax.Jquery and had the exact same issue.
I am using react-adminframework, and I have written my own DataProvider. I am trying to accomplish that when an User is created, an instance of UserPossession is created as well. My code bellow accomplishes that, but react-admin Front-end just displays the warning message:
Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body
I checked the Network tab in Developer Tools and every request to server is correct, there is no error. Which leaves me confused and stuck with this, because I have no idea what that warning means or why is it even occuring.
My code is a part of convertDataRequestToHTTP constant and looks like this:
if (resource === 'User') {
url = `${apiUrl}/${resource}`;
options.body = params.data;
httpClient(url, {
method: 'POST',
body: JSON.stringify(options.body),
})
.then(response => (
url = `${apiUrl}/Location`,
options.method = 'POST',
options.body = JSON.stringify({
"odata.type": "HardwareDatabase.UserPossession",
"Name": response.json.Login,
"UserId": response.json.Id
}),
httpClient(url, {
method: options.method,
body: options.body
})
));
}
If you have any questions regarding the code I can clarify.
Thank you for any ideas in advance.
Since you are stating that this code snippet is a part of convertDataRequestToHTTP I might see the issue. httpClient cannot be used in this constant since it creates duplicit calls to API or in your case, this Warning. Correct way would be to only state the options constant.
url = `${apiUrl}/${resource}`;
options.body = JSON.stringifiy(params.data);
options.method = 'POST';
Later in the constant that converts response from OData to mandatory React Admin format, state the httpClient.
params.data = {
"odata.type": "HardwareDatabase.UserPossession",
"Name": response.json.Login,
"UserId": response.json.Id
};
httpClient(`${apiUrl}/Location`, {
method: 'POST',
body: JSON.stringify(params.data),
})
Unfortunately, the GET method for XMLHttpRequest and fetch don't support request bodies.
A temporary work around I found was to use the axios library, which does let you send GET with request bodies.
const res = await axios.get("/api/devices", {
data: { deviceName: 'name' }
})
I am making a GET request to a rails server, and the parameter should look like:
{"where"=>{"producer_id"=>["7"]}
I am making the request from the frontend application which is in Vue, and using Axios for making the request. I am making the request like this:
const data = await this.axios.get('http://localhost:3000/data.json', {
headers: {
'X-User-Token': this.$store.getters.authToken,
'X-User-Username': this.$store.getters.user.username
},
params: {
where: {
producer_id: data.producers
}
}
})
However, in the rails server output it shows that the params were sent like this:
{"where"=>"{\"producer_id\":[\"7\"]}"}
And I don't get the correct data back because of it.
How can I solve this? Why is the second level in params (the where object) being sent as a string?
Turns out that in this case the params have to be serialized https://github.com/axios/axios/issues/738
I used the paramsSerializer function as well to get over this
const data = await this.axios.get('http://localhost:3000/data.json', {
headers: {
'X-User-Token': this.$store.getters.authToken,
'X-User-Username': this.$store.getters.user.username
},
params: {
where: {
producer_id: data.producers
}
},
paramsSerializer: function (params) {
return jQuery.param(params)
}
})
EDIT:
I am now using qs instead of jQuery:
axios.defaults.paramsSerializer = (params) => {
return qs.stringify(params, {arrayFormat: 'brackets'})
}