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.
Related
I am using laravel with stripe payment element. I am trying to show the saved cards for the customers that we already have. I have followed the stripe docs and found how I can show it on checkout. But the problem is that I am not getting the saved cards for the customer. And instead I am facing an error on my console as:
When authenticating with an ephemeral key, you must set the Stripe-Version header to an explicit API version, such as 2020-08-27
I have checked and changed lot of versions from here:
$ephemeralKey = \Stripe\EphemeralKey::create(
['customer' => "$user->stripe_customer_id"],
['stripe_version' => '2019-11-05']
);
I changed the version to different version that I can see on my stripe dashboard:
This is my Js Initialize function:
// Fetches a payment intent and captures the client secret
async function initialize() {
// Customize the appearance of Elements using the Appearance API.
const appearance = { /* ... */ };
// Enable the skeleton loader UI for the optimal loading experience.
const loader = 'auto';
const { clientSecret, customerOptions } = await fetch("{{ route("user-create-stripe-element-payment") }}", {
method: "POST",
headers: {
"Content-Type" : "application/json",
"accept" : "application/json",
'X-CSRF-TOKEN': "{{ csrf_token() }}",
'stripe_version':"2019-11-05"
},
body: JSON.stringify({ totalCharge:total }),
}).then((r) => r.json());
elements = stripe.elements({
clientSecret,
appearance,
loader,
customerOptions
});
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
And I am also using the betas which is given in the documentation:
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
});
But this error is not going away. And its not even populating the Payment element.
Please help me debug this or if someone has any suggestion to check what is going on here.
Thanks in advance.
You are not providing an API version in your JS here
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
});
change the above code to
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
apiVersion: 'Your Version Here'
});
In your case, it should be something like this
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
apiVersion: '2019-11-05'
});
You can read more here. https://stripe.com/docs/api/versioning?lang=node
It is for nodejs but the API version override will work in the same way.
I'm trying to make an API call from my Vue3 app. The prepared API has an endpoint like http://localhost:8888/api/dtconfigsearch, where one needs to pass a json payload like { "Modelname": "MyFancyModel"} to get the full dataset with the given modelname. Pure get functions without a payload / a body do work from my Vue3 project to the golang backend, but I'm having problems with passing a payload to the backend.
Test with curl -> ok
$ curl -XGET localhost:8888/api/dtconfigsearch -d '{"Modelname" : "MyFancyModel" }'
{"ID":4,"Modelname":"MyFancyModel","ModelId":"96ee6e80-8d4a-b59a-3524-ced3187ce7144000","OutputTopic":"json/fancyoutput"}
$
This is the expected output.
Test with javascript ok
Source file index.js:
const axios = require('axios');
function makeGetRequest() {
axios.get(
'http://localhost:8888/api/dtconfigsearch',
{
data: { Modelname : "MyFancyModel" },
headers: {
'Content-type' : 'application/json'
}
}
)
.then(resp => {
console.log(resp.data)
})
.catch(err => {
console.log(err)
})
}
makeGetRequest()
Output
$ node index.js
{
ID: 4,
Modelname: 'MyFancyModel',
ModelId: '96ee6e80-8d4a-b59a-3524-ced3187ce7144000',
OutputTopic: 'json/fancyoutput'
}
$
Here, I also get the desired output.
Test within Vue fails :-(
Source in the Vue one file component:
onSelection(event) {
let searchPattern = { Modelname : event.target.value }
console.log(event.target.value)
console.log("searchPattern = " + searchPattern)
axios.get("http://localhost:8888/api/dtconfigsearch",
{
data : { Modelname : "Windshield"},
headers: {
'Content-type' : 'application/json',
'Access-Control-Allow-Origin': '*'
}
})
.then(response => {
console.log(response.data)
})
.catch(err => {
console.log(err)
alert("Model with name " + event.target.value + " not found in database")
})
},
Output in browser:
In the image you can see in the terminal log on the right side that the backend is not receiving the body of the API call. However, in the browser information of the call there is content in the config.data part of the object tree, which is the payload / the body. The only thing that bothers me that it is not a json object, but stringified json, although it was entered as json object. According to the documentation, the parameter name (data) in the call should be correct to hold the body content of the api call.
I've tried different header information, looked if it could be a CORS issue, what it isn't to my opinion, exchanged key data with body, used axios instead of axios.get and adapted parameter, all without success. The version of the axios library is 0.27, identical for Vue and vanilla javascript. After checking successfully in javascript, I was sure that it would work the same way in Vue, but it didn't.
Now I'm lost and have no further ideas how to make it work. Maybe someone of you had similar issues and could give me a hint? I'd be very grateful for some tipps!!
I'm baffled what I'm doing wrong in my code. The GET call gets resolved, but when I try to do a POST call to the same server I get an error. My POST endpoint works fine with Postman.
apiConnection.js
function get(data){
return axios.get("http://localhost:8080/api/questions",
{
params:data.payload
}, {
headers: {
'accept': 'application/json',
}
})
}
function post(data){
console.log(data.payload) //my payload is received here
return axios.post("http://localhost:8080/api/answer", {
params:data.payload
}, {
headers: {
'accept': 'application/json',
}
}
)
}
export { get, post }
Here is the error I get in the console
And here is how I make the call in react
index.js
GET (Receives response normally)
import { get, post } from "apiConnection.js"
...
componentDidMount(){
const data = {
payload: {
linkId: getSlug()
}
}
get(data).then((result) => {
this.setState({reportId: result.data.report.id});
})
}
POST (Throws error)
vote(userVote){
const data = {
payload: {
reportId: this.state.reportId,
}
}
post(data).then((result)=>{
this.state.questions[this.state.currentQuestion].vote = userVote
});
}
I have found the culprit of the issue but if someone can add more information about it, it might be helpful for others.
In my question, for brevity, I changed the request URL from imported constants to hardcoded links.
In my code, I have a variable for both GET and POST
return axios.post(apiEndpoints[data.ep], data.payload)
I import the endpoint variables like so
import * as apiEndpoints from './apiEndpoints';
apiEndpoints.js
const server = 'http://localhost:8080/'
const api_version = 'api/'
const base_url = server+api_version;
export const EP_QUESTIONS = base_url+'questions';
export const EP_ANSWER = base_url+'answer';
For some unknown reason EP_ANSWER throws the error even though I'm not making a typo when I define data.ep (data.ep has EP_ANSWER, which
I checked a million times)
The solution was to just change EP_ANSWER to EP_ANS and everything worked as expected.
No idea why this is the case. It might be some global variable or a reserved word.
Just came across this and noted #Ando's response.
So, knowing that I first tried a hard coded URL, it worked.
I then successfully did url.toString() and it worked.
Not sure why but Javascript seems to treat a an object string differently than a true string.
I seriously confused how to solve this multipart boundary when using Axios, react.js and multipart/formdata. I already stuck for 2 weeks to try to solve this but I feel like I getting closer to solved it but it still stuck no matter what solution I try.
I read and trysome solution from this topic:
how-to-post-multipart-formdata-using-fetch-in-react-native
how-to-send-multipart-form-data-with-antd-upload-react
how-to-send-a-multipart-form-data-from-react-js-with-an-image
this is my create Order function in orderAction.js :
function createOrder(data) {
return dispatch => {
let apiEndpoint = 'order';
let payload = new FormData();
// payload.append('orderImage', data.orderImage);
// console.log("Cek Image : ", data.orderImage);
for (const file of data.orderImage) {
payload.append('orderImage', file)
}
payload.append('userId', data.userId);
payload.append('materialId', data.materialId);
// payload.append('materialId', '5d79930c8c4a882f44b1b0fb');
payload.append('color', data.color);
payload.append('description', data.description);
payload.append('quantity', data.quantity);
payload.append('city', data.city);
payload.append('detailAddress', data.detailAddress);
console.log("Cek Data : ", payload);
fetch(config.baseUrl + apiEndpoint, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
'Content-Type' : 'multipart/form-data; boundary=----WebKitFormBoundaryHl8DZV3dBSj0qBVe'
},
body: payload
})
// orderService.post(apiEndpoint, payload)
// .then(res => {
// if(res.data.status === 200) {
// alert(res.data.Message);
// dispatch(createOrderSuccess(res.data));
// history.push('/user-order');
// } else {
// dispatch(createOrderFailed());
// alert(res.data.Message);
// }
// })
};
}
can someone help me to solve this? I'm quite confused with this problem
Edit 1
after try using #narasimha solution finally I got rid the multipart boundary but I got weird behaviour where The data succesfully got encoded like this:
but When I trying check the response the photoUrl return null or `` like this:
and when I try using insomnia or postman it successfully generated the photoUrl like this:
where did I wrong in here?
I had the same problem yesterday. The problem was with content type. I had used the same content type header as you are using. The thing is I removed content type and allowed the fetch () API to handle it automatically. It worked!!
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' }
})