Axios: onUploadProgress event gets only triggered after video is uploaded completely - javascript

I am trying to track the progress of video upload and accordingly display it to the user.
This is the axios request that uploads the video.
const config = {
onUploadProgress: function (progressEvent) {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.group("profileActions.uploadProfileVideoURL");
console.log("progressEvent.loaded: ", progressEvent.loaded);
console.log("progressEvent.total: ", progressEvent.total);
console.log("percentCompleted: ", percentCompleted);
console.groupEnd();
},
};
axios
.post("/api/profile/uploadProfileVideoURL", videoData, config)
The problem is that it only gets triggered when video is completely uploaded.
I have found this discussed in this Github Axios Issue:
Only 100% can be fired in case your upload files are too small, or
download/upload speed is too fast. Try to upload 10mb file maybe.
So I tried uploading a 100Mb video, and the event still gets triggered at then end of the upload.
Any idea why is this happening?
And can I fine-tune axios for the event to get triggered at certain progress values?

Your code needs added the FormData and some headers, like (MIME type of the file) for request. You can use this logic:
First - create a utility function for handling upload with progress like below:
import axios from "axios";
const Upload = (files, customHeader, cbProgress, cb) => {
const config = {
onUploadProgress: function (progressEvent) {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
cbProgress(percentCompleted);
console.log(percentCompleted);
},
headers: {
...customHeader,
// "Content-Type": files[0].type,
// "Content-Type": "multipart/form-data",
},
};
let data = new FormData();
data.append(<field_name>, files[0]); // Name of the field for uploading
console.log({ ...customHeader });
axios
.post(<url>, data, config)
.then((res) => cb(res))
.catch((err) => console.log(err));
};
export default Upload;
Short description of the above code:
customHeader: pass any headers to the config, For example: "Content-Type", "Authorization", etc...
cbProgress: This is a callback, When the function is invoked and in progress.
cb: This is a callback, When the function is invoked and upload is completed.
Note: FormData, Used to send the file and you must use it.
The FormData interface provides a way to easily construct a set of
key/value pairs representing form fields and their values, which can
then be easily sent using the XMLHttpRequest.send() method. It uses
the same format a form would use if the encoding type were set to
"multipart/form-data".
Finally - You can call this utility function anywhere you want like below:
const customHeader = {
"Content-Type": files[0] && files[0].type, // This is an example
"Content-Type": "multipart/form-data", // This is an example
};
Upload(
files,
customHeader,
(percent) => {
// ...
},
(res) => {
// ...
}
);

Related

React Share Dynamically generated QR code on social Media

I would like to know how to share a base64 string as an image over WhatsApp primarily.
I have a react app that generates a QR code and renders it using <img src={base64String} />. Now that img is wrapped around an <a><a/> tag with download attribute, it is working fine. But what I want to accomplish is to be able to have a share button that can share the image on WhatsApp.
I don't want to store it locally or involve file reading if possible because I had a lot of issues implementing it since the QR code will be discarded after download and need not be stored as a file. But if that is the right way I'm ok to work with it.
I tried creating a blob and sharing by using atob(), but that returned an error saying "string not encoded properly" which I didn't want to dig into since it was able to render it as an image and also download by which I assumed the base64 string was correct.
code for Generator:
const GenerateQRCode = (e) => {
e.preventDefault()
QRCode.toDataURL(value, {
margin: 2,
color: {
dark: '#000000',
light: '#ffffff'
},
width: 2000,
height:2000
}, (err, value) => {
if (err) return console.error(err)
console.log(value)
setQr(value)
console.log(data);
})
}
const handleShare =(e)=>{
}
code for that renders the image:
{qr && <>
<img src={qr} alt='' className='qrimg' id='can'/>
<a href={qr} download={`${state.name}.png`} ><button value="Download" >Download</button></a>
</>
}
<button type="" onClick={handleShare}></button>
So, I want that share button to send an image over WhatsApp. My previous attempts for handleShare were messy so I wanted to get fresh inputs or a Solution to do this the right way.
Finally success, that was a hard one! You have to upload the file as a Media into the API and then use that uploaded Media ID to send. Check out the code and replace "xxx" with your values.
Note: Your QR code must be a PNG for this to work. This is not a snippet because it doesn't make sense to run
<img src="data:image/png;base64,iVBORw0KGg...VERY_LONG_STRING_HERE...">
<script>
// Your auth token. Keep it scret, keep it safe.
const Authorization = "Bearer xxx"
const type = 'image/png' // Image type (cannot be gif due to API restriction!)
const baseUrl = "https://graph.facebook.com/v15.0/xxx" // Phone number of sender
const recipient = "xxx" // Phone number of recipient
/** This function creates a File object from an img element which has a base64 image */
function fileFromImg(img) {
var byteString = atob(img.src.split(',')[1]);
var arrayBuffer = new ArrayBuffer(byteString.length);
var arr = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteString.length; i++) {
arr[i] = byteString.charCodeAt(i);
}
const blob = new Blob([arrayBuffer], { type });
return new File([blob], "qr.png", { type })
}
/** This function will upload a File and call the given callback with the result */
function sendFile(f, callback) {
const url = baseUrl + "/media";
const formData = new FormData()
formData.append('file', f)
formData.append('type', type)
formData.append('messaging_product', "whatsapp")
fetch(url, {
method: 'POST',
body: formData,
headers: { Authorization }
})
.then(r => r.json())
.then(callback)
.catch(console.error)
}
/** This takes in a successful upload and sends that forward to receiver */
function handleResponse(response) {
// When it works response will look like {"id": "xxx"}
fetch(baseUrl + "/messages", {
method: 'POST',
body: JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": recipient,
"type": "image",
"image": response
}),
headers: { Authorization, "Content-Type": "application/json" }
})
.then(r => r.json())
.then(r => console.log(r))
.catch(console.error)
}
// I choose to trigger the sending when anything is clicked
document.addEventListener("click", () => {
const img = document.querySelector("img")
const file = fileFromImg(img)
sendFile(file, handleResponse)
})
</script>
The recipient will see this;

multipart/form-data not being automatically set with axios in React Native

When attempting to upload a file to Amazon S3 using axios, I have been encountering a very strange issue. Normally, in a web browser, when FormData has binary data in it, the Content-Type header automatically gets set to multipart/form-data; boundary=<some random string>. However, I have been completely unable to achieve that in React Native (testing on an iOS device). The Content-Type is automatically set to application/json, and thus not being detected as a correctly formatted body when uploading to Amazon S3. I have tried specifying a blob in the file parameter in FormData instead of the URI to the file as well to no avail. I have appended my code below, any advice would be very much appreciated.
const uploadFileToS3 = (
presignedPostData,
file) => {
// create a form obj
const formData = new FormData();
// append the fields in presignedPostData in formData
Object.keys(presignedPostData.fields).forEach(
key => {
formData.append(
key,
presignedPostData.fields[key],
);
},
);
// append the file and uplaod
const getBlob = async () => {
const img_url = previewPath;
let result = await fetch(img_url);
const blob = await result.blob();
formData.append('Content-Type', 'image/jpeg');
formData.append('file', {
uri: previewPath,
type: 'image/jpeg',
name: 'test.jpeg',
});
console.log(formData, 'wild');
// post the data on the s3 url
axios
.post(presignedPostData.url, formData)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error.response);
});
};
getBlob();
};

Send image to server through js fetch

I am changing my form submission to make it more fluid via using fetch.
In order to process the value of my input image:
<input name="perfil" type='file' id="imageUpload />
And then, in order to upload it to Amazon S3,
I do this in my views.py:
if request.method == "POST"
image = request.FILES['perfil']
im = Image.open(image)
output = BytesIO()
rgb_im = im.convert('RGB')
rgb_im.save(output, format='JPEG', quality=90)
output.seek(0)
s3.Bucket('bucketname').upload_fileobj(output, request.user.email + '.profileImage')
But now (because i'm trying to implement fetch), I am getting the image file like this:
fetch(`url`, {
method: 'POST',
body: JSON.stringify({
image: document.querySelector('#imageUpload').files[0],
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
"X-CSRFToken": getCookie('csrftoken')
}
})
}
The problem is that when I do request.body['image`] in the server (views.py), all I'm getting is this: "image":{}
And I don't know how to process this file in JS before I send it to the server (that will end up uploading it to amazon s3)
With this Example you can upload Images on a Server!
// Select your input type file and store it in a variable
const input = document.getElementById('fileinput');
// This will upload the file after having read it
const upload = (file) => {
fetch('http://www.example.net', { // Your POST endpoint
method: 'POST',
headers: {
// Content-Type may need to be completely **omitted**
// or you may need something
"Content-Type": "You will perhaps need to define a content-type here"
},
body: file // This is your file object
}).then(
response => response.json() // if the response is a JSON object
).then(
success => console.log(success) // Handle the success response object
).catch(
error => console.log(error) // Handle the error response object
);
};
// Event handler executed when a file is selected
const onSelectFile = () => upload(input.files[0]);
// Add a listener on your input
// It will be triggered when a file will be selected
input.addEventListener('change', onSelectFile, false);

I keep getting a Bad request error when trying to post Image to Cloudinary

i'm trying to upload an image to cloudinary using axios and react. I have two functions. One for appending the formData, and the other for making a request
// APPEND CLOUDINARY DETAILS
export function appendCloudinaryDetails(fileUrl){
let formData = new FormData();
formData.append("api_key", 'APIKEYHERE');
formData.append("api_secret", 'APISECRETHERE');
formData.append("file", 'fileUrl');
formData.append("cloud_name", fileUrl);
formData.append("upload_preset", "rhi2i6xg");
return formData;
}
// STORE THE DATA ON CLOUDINARY
export function storeOnCloudinary(formData){
return () => {
return axios.post("https://api.cloudinary.com/v1_1/my-company/image/upload", formData,{
headers: { "X-Requested-With": "XMLHttpRequest", "Allow-Control-Allow-Origin": "*" },
});
}
}
And this is how i use it:
// Append Cloudinary Details
let formData = appendCloudinaryDetails(this.state.backdropCroppedImageUrl);
// Store the backdrop on Cloudinary
this.props.storeOnCloudinary(formData).then(
response => {
...
}
);
But running this gives me a 400 error response. As shown below:
I don't know if this is a faithful copy of your code, or it was changed in order to hide sensitive information in the posted question, but according to it, you are appending to the formData the backdropCroppedImageUrl as the 'cloud_name' instead of setting it on 'file', and in 'file' you're setting a string.

How to send data correct axios Error: Multipart: Boundary not found

I don't know why I receive on server [Error: Multipart: Boundary not found]
and bundle.js:37628 POST http://localhost:8800/exporttocsv 500 (Internal Server Error)
When I make post through
<form action="/exporttocsv" method="POST" encType="multipart/form-data">
post works correctly, but through axios doesn't work.
Please help me fix the mistake
this my code
/--client
import axios from 'axios'
var formData = new FormData()
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
export const ipmortToCSV = (file) => dispatch => {
formData.append('file',file)
console.log(formData.getAll('data'))
axios.post('/exporttocsv', {
"UploadCommand": formData
},config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
//--server
const router = require('express').Router()
var csv = require('csv-express')
const controllers = require('../../controllers/exporttocsv')
var multer = require('multer')
var upload = multer({dest : 'exporttocsv/'})
router.get('/', (req, res) => {
controllers.exportToCsv(req,res)
})
router.post('/',upload.single('file'),(req,res) => {
//controllers.importToCsv(req,res)
})
module.exports = router
You can do this ...
Instantiate a new FormData instance.
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
let fd = new FormData();
fd.append('file',files[0])
return axios.post("http://localhost:5000/upload", fd, config)
Usingconcat and concat-stream
const concat = require("concat-stream")
const fd = new FormData()
fd.append("hello", "world")
fd.append("file", fs.createReadStream(file))
fd.pipe(concat(data => {
axios.post("/hello", data, {
headers: fd.getHeaders()
})
}))
Using promise
const promise = new Promise((resolve) => {
const fd = new FormData();
fd.append("hello", "world");
fd.append("file", fs.createReadStream(binaryFile));
fd.pipe(concat({ encoding: 'buffer' }, data => resolve({ data, headers: fd.getHeaders() })));
});
promise.then(({ data, headers }) => axios.post('/hello', data, { headers }));
I hope I've been useful! :)
References:
github.com - Can't get a .post with Content-Type...
github.com - Better solution using axios, form-data, fs
https://stackoverflow.com/a/47630754/3332734
I was struggling with this issue of multipart boundary not found with fetch api calling to a nestjs server. What I tried was to remove the
'Content-Type': 'multipart/form-data',
headers so that Fetch api automatically set the headers and it worked. Try it out
By default axios do not attach boundary to content type header. You have to do it manually:
axios.post(`${this.baseUrl}/${path}`, formData, {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
},
})
It is especially important if you talking to spring server.
In other case you will see exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
I was getting this problem with Axios via JavaScript because the content-type header was multipart-form-data but the boundary was missing.
Based on my research, a good way to handle it is to allow Axios to auto-detect the content type and set the headers correctly itself.
Here is an idea for how to accomplish this:
const formDataWithFiles = hasFiles ? new FormData() : undefined;
if (formDataWithFiles) {
// axios will automatically set the content-type to multipart/form-data if the
// data param is a FormData object
// otherwise, it will use application/json
// (study the Dev Tools > Network tab > XHR tab headers)
Object.keys(modifiedFields)
.forEach(field => formDataWithFiles.append(field, modifiedFields[field]));
}
const { data } = await axios({
method,
url: actionUrl,
data: hasFiles ? formDataWithFiles : modifiedFields,
headers: {
...axios.defaults.headers,
...headers,
},
});
return data;
The above code is in a generic handleSubmit function that can be called from anywhere in the client-side.
Here is the function signature:
const { data } = await this.submitForm({
actionUrl: this.actionUrl,
method: this.method,
modifiedFields: {
...this.modifiedUser,
},
hasFiles: true,
});
In the above code, there are two use cases. The first is the default case, where a normal payload is sent via a flat object. The second is the case when the form has files and you want multipart/form-data. In this case, we use the FormData Object as a vessel to instruct Axios to auto-detect the necessary headers and set the correct boundary.
If you do not specify the headers correctly, it is possible to receive an empty $request->all() Array in Laravel, or perhaps any server such as node.js.
The short answer to my answer is to use the FormData Object because it contains more information than a plain-old-JavaScript-object. With it, you can also access:
const formData = new FormData();
console.log('boundary:', formData._boundary);
As my annotation above hints towards, use the Dev Tools > Network tab > XHR tab to examine your request headers and make sure you have content-type application/json or application/x-www-form-urlencoded for regular form submits and multipart/form-data' if you are uploading a file.
For me the main reason was what the OP did; sending the data argument of axios.post as an object ({ key: formDataObj}) instead of just formDataObj directly as the arg.
For me add the following code to fixes it.
axios.post('/xxx/Upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
transformRequest: (data) => {
return data
},
})
Okay, I would like to share my solution as I struggled with this problem for almost a day. The issue was caused by an incorrect package version. Around 27.0.0, some changes were made to the form data sending, which resulted in issues with the boundaries. I'm not sure what version you folks are using, but it might be worth checking if this is the cause of your problem.
https://github.com/axios/axios/issues/4631

Categories

Resources