Empty image when uploading to presigned AWS S3 url in React Native - javascript

I'm trying to upload image to AWS S3 in my React Native(expo managed workflow), but in result the file is empty. I don't get any errors through the process. I've also tried to upload it using Uppy AWS plugin, but result is the same. Here is my code:
async function getUploadUrl(filename: string, type: string) {
const response = await fetch(GET_UPLOAD_URL(filename, type), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
return await response.json();
}
export default async function uploadImage(
file: Blob,
filename: string,
base64: string
) {
const uploadData = await getUploadUrl(filename, file.type);
const data = new FormData();
for (const [key, value] of Object.entries(uploadData.fields)) {
data.append(key, value as string);
}
data.append('file', Buffer.from(base64, 'base64'));
let res = await fetch(uploadData.url, {
method: 'post',
body: data,
headers: {
'Content-Type': 'multipart/form-data;',
},
});
I am using expo image picker to get the file. I've also tried to upload just Blob file insead of Buffer, but it doesn't work either
Here is how the file looks if i open it in browser https://prnt.sc/vOk5CI7lyPhu

If anyone also faced such problem, i managed to upload the file by uri like this:
formData.append('file', {
uri: uri,
type: 'image/jpeg',
name: filename,
});

Related

Trouble using JavaScript and the google drive API to convert a google slide into a pdf, and upload the pdf onto a folder

I'm new to JavaScript, and am trying to write some code that uses the google drive API (via the gapi client) to transform an existing slide into a pdf document, upload it to a specific folder, and return the pdf file id. This is all to be done in the browser, if possible.
I've already done this on python for another use case, and the code looks something like this:
import googleapiclient.http as client_methods
from io import BytesIO
...
data = drive.files().export(fileId=slideId, mimeType='application/pdf').execute()
body = {'name': fileName, 'mimeType': 'application/pdf', 'parents': [folderId]}
# wrapping the binary (data) file with BytesIO class
fh = io.BytesIO(data)
# creating the Media Io upload class for the file
media_body = client_methods.MediaIoBaseUpload(fh, mimetype='application/pdf')
pdfFileId = drive.files().create(body=body, media_body=media_body, supportsAllDrives=True).execute(['id'])
I've tried to replicate the same steps using JavaScript and my limited knowledge, and can successfully upload a pdf file into the desired folder, but the file shows as empty (doesn't even open in the drive).
I believe it might be due to the way I'm handling the binary data that I get from exporting the initial slide.
The last iteration of my JavaScript code is shown below (I have all the necessary permissions to use the gapi client):
async function createPdfFile() {
gapi.client.load("drive", "v3", function () {
// Set the MIME type for the exported file
const mimeType = "application/pdf";
// Set the file name for the exported PDF file
const fileName = "Trial upload.pdf";
// Export the Google Slides presentation as a PDF file
gapi.client.drive.files.export({
fileId,
mimeType
}).then(async function (response) {
// Get the binary data of the PDF file
const pdfData = await response.body;
const blob = await new Blob([pdfData], {type: 'application/pdf'})
const file = new File([blob], "presentation.pdf");
// Create a new file in the specified Google Drive folder with the PDF data
await gapi.client.drive.files.create({
name: fileName,
parents: [folderId],
mimeType: mimeType,
media: {mimeType: 'application/pdf', body: file},
supportsAllDrives: true
}).then(function (response) {
// Get the ID of the created PDF file
const pdfFileId = response.result.id;
console.log("PDF file created with ID: " + pdfFileId);
})
})
})
}
await createPdfFile()
As for the output, and as stated, it does create a pdf file, and logs the pdf file id, but the file itself is empty. I'd really appreciate it if someone could help me make sense of this (similar thread here, but can't replicate his success).
I believe your goal is as follows.
You want to convert Google Slides to PDF format using googleapis for Javascript.
Your access token can be exported and uploaded to Google Drive.
Issue and workaround:
When I tested your script, unfortunately, response.body from gapi.client.drive.files.export is binary data, and in this case, this cannot be correctly converted to the blob. And also, in the current stage, it seems that a file cannot be uploaded using gapi.client.drive.files.create. I thought that these might be the reason for your current issue.
From these situations, I would like to propose the flow for achieving your goal using fetch API. The modified script is as follows.
In this case, the access token is retrieved from the client like gapi.auth.getToken().access_token.
Modified script:
Please modify your script as follows.
From:
gapi.client.drive.files.export({
fileId,
mimeType
}).then(async function (response) {
// Get the binary data of the PDF file
const pdfData = await response.body;
const blob = await new Blob([pdfData], { type: 'application/pdf' })
const file = new File([blob], "presentation.pdf");
// Create a new file in the specified Google Drive folder with the PDF data
await gapi.client.drive.files.create({
name: fileName,
parents: [folderId],
mimeType: mimeType,
media: { mimeType: 'application/pdf', body: file },
supportsAllDrives: true
}).then(function (response) {
// Get the ID of the created PDF file
const pdfFileId = response.result.id;
console.log("PDF file created with ID: " + pdfFileId);
})
})
To:
gapi.client.drive.files.get({ fileId, fields: "exportLinks", supportsAllDrives: true }).then(function (response) {
const obj = JSON.parse(response.body);
if (Object.keys(obj).length == 0) throw new Error("This file cannot be converted to PDF format.");
const url = obj.exportLinks["application/pdf"];
if (!url) throw new Error("No exported URL.");
const accessToken = gapi.auth.getToken().access_token;
fetch(url, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + accessToken },
})
.then(res => res.blob())
.then(blob => {
const metadata = { name: fileName, parents: [folderId], mimeType };
const form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', blob);
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken },
body: form
})
.then(res => res.json())
.then(obj => console.log("PDF file created with ID: " + obj.id));
});
});
When this script is run, the export URL of PDF data is retrieved from the file ID. And, the PDF data is downloaded and uploaded to Google Drive.
Note:
In your script, fileId is not declared. Please be careful about this.
If the file size is more than 5 MB, please use the resumable upload.
Reference:
Upload file data
Added:
From your following reply,
?uploadType=multipart also returns a 404 type error
I'm worried about that in your situation, new FormData() might not be able to be used. If my understanding is correct, please test the following script. In this script, the request body of multipart/form-data is manually created.
Modified script:
gapi.client.drive.files.get({ fileId, fields: "exportLinks", supportsAllDrives: true }).then(function (response) {
const obj = JSON.parse(response.body);
if (Object.keys(obj).length == 0) throw new Error("This file cannot be converted to PDF format.");
const url = obj.exportLinks["application/pdf"];
if (!url) throw new Error("No exported URL.");
const accessToken = gapi.auth.getToken().access_token;
fetch(url, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + accessToken },
})
.then(res => res.blob())
.then(blob => {
const metadata = { name: fileName, parents: [folderId], mimeType };
const fr = new FileReader();
fr.onload = e => {
const data = e.target.result.split(",");
const req = "--xxxxxxxx\r\n" +
"Content-Type: application/json\r\n\r\n" +
JSON.stringify(metadata) + "\r\n" +
"--xxxxxxxx\r\n" +
"Content-Transfer-Encoding: base64\r\n\r\n" +
data[1] + "\r\n" +
"--xxxxxxxx--";
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken, "Content-Type": "multipart/related; boundary=xxxxxxxx" },
body: req
})
.then(res => res.json())
.then(obj => {
console.log("PDF file created with ID: " + obj.id)
});
}
fr.readAsDataURL(blob);
});
});
When I tested this script, no error occurs. I confirmed that the Google Slides file could be converted to a PDF file and the PDF file was uploaded to the specific folder.

ECONNRESET and CGI parser error when trying to upload file using axios post [duplicate]

I have an API endpoint that lets the client post their csv to our server then post it to someone else server. I have done our server part which save uploaded file to our server, but I can't get the other part done. I keep getting error { message: 'File not found', code: 400 } which may mean the file never reach the server. I'm using axios as an agent, does anyone know how to get this done? Thanks.
// file = uploaded file
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
Update
As axios doc states as below and the API I'm trying to call requires a file
// data is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', and 'PATCH'
// When no transformRequest is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
Is there any way to make axios send a file as a whole? Thanks.
The 2 oldest answers did not work for me. This, however, did the trick:
const FormData = require('form-data'); // npm install --save form-data
const form = new FormData();
form.append('file', fs.createReadStream(file.path));
const request_config = {
headers: {
'Authorization': `Bearer ${access_token}`,
...form.getHeaders()
}
};
return axios.post(url, form, request_config);
form.getHeaders() returns an Object with the content-type as well as the boundary.
For example:
{ "content-type": "multipart/form-data; boundary=-------------------0123456789" }
I'm thinking the createReadStream is your issue because its async. try this.
Since createReadStream extends the event emitter, we can "listen" for when it finishes/ends.
var newFile = fs.createReadStream(file.path);
// personally I'd function out the inner body here and just call
// to the function and pass in the newFile
newFile.on('end', function() {
const form_data = new FormData();
form_data.append("file", newFile, "filename.ext");
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
});
This is what you really need:
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios
.post(url, form_data, request_config);
In my case, fs.createReadStream(file.path) did not work.
I had to use buffer instead.
const form = new FormData();
form.append('file', fs.readFileSync(filePath), fileName);
const config = {
headers: {
Authorization: `Bearer ${auth.access_token}`,
...form.getHeaders(),
},
};
axios.post(api, form.getBuffer(), config);
I have made an interceptor you can connect to axios to handle this case in node: axios-form-data. Any feedback would be welcome.
npm i axios-form-data
example:
import axiosFormData from 'axios-form-data';
import axios from 'axios';
// connect axiosFormData interceptor to axios
axios.interceptors.request.use(axiosFormData);
// send request with a file in it, it automatically becomes form-data
const response = await axios.request({
method: 'POST',
url: 'http://httpbin.org/post',
data: {
nonfile: 'Non-file value',
// if there is at least one streamable value, the interceptor wraps the data into FormData
file: createReadStream('somefile'),
},
});
// response should show "files" with file content, "form" with other values
// and multipart/form-data with random boundary as request header
console.log(response.data);
I had a same issue, I had a "pdf-creator-service" for generate PDF document from html.
I use mustache template engine for create HTML document - https://www.npmjs.com/package/mustache
Mustache.render function returns html as a string what do I need to do to pass it to the pdf-generator-service ? So lets see my suggestion bellow
//...
async function getPdfDoc(props: {foo: string, bar: string}): Promise<Buffer> {
const temlateFile = readFileSync(joinPath(process.cwd(), 'file.html'))
mustache.render(temlateFile, props)
const readableStream = this.getReadableStreamFromString(htmlString)
const formData = new FormData() // from 'form-data'
formData.append('file', options.file, { filename: options.fileName })
const formHeaders = formData.getHeaders()
return await axios.send<Buffer>(
{
method: 'POST',
url: 'https://pdf-generator-service-url/pdf',
data: formData,
headers: {
...formHeaders,
},
responseType: 'arraybuffer', // ! important
},
)
}
getReadableStreamFromString(str: string): Readable {
const bufferHtmlString = Buffer.from(str)
const readableStream = new Readable() // from 'stream'
readableStream._read = () => null // workaround error
readableStream.push(bufferHtmlString)
readableStream.push(null) // mark end of stream
return readableStream
}
For anyone who wants to upload files from their local filesystem (actually from anywhere with the right streams architecture) with axios and doesn't want to use any external packages (like form-data).
Just create a readable stream and plug it right into axios request function like so:
await axios.put(
url,
fs.createReadStream(path_to_file)
)
Axios accepts data argument of type Stream in node context.
Works fine for me at least in Node v.16.13.1 and with axios v.0.27.2

how to add file with formData in axios request using node.js [duplicate]

I have an API endpoint that lets the client post their csv to our server then post it to someone else server. I have done our server part which save uploaded file to our server, but I can't get the other part done. I keep getting error { message: 'File not found', code: 400 } which may mean the file never reach the server. I'm using axios as an agent, does anyone know how to get this done? Thanks.
// file = uploaded file
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
Update
As axios doc states as below and the API I'm trying to call requires a file
// data is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', and 'PATCH'
// When no transformRequest is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
Is there any way to make axios send a file as a whole? Thanks.
The 2 oldest answers did not work for me. This, however, did the trick:
const FormData = require('form-data'); // npm install --save form-data
const form = new FormData();
form.append('file', fs.createReadStream(file.path));
const request_config = {
headers: {
'Authorization': `Bearer ${access_token}`,
...form.getHeaders()
}
};
return axios.post(url, form, request_config);
form.getHeaders() returns an Object with the content-type as well as the boundary.
For example:
{ "content-type": "multipart/form-data; boundary=-------------------0123456789" }
I'm thinking the createReadStream is your issue because its async. try this.
Since createReadStream extends the event emitter, we can "listen" for when it finishes/ends.
var newFile = fs.createReadStream(file.path);
// personally I'd function out the inner body here and just call
// to the function and pass in the newFile
newFile.on('end', function() {
const form_data = new FormData();
form_data.append("file", newFile, "filename.ext");
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
});
This is what you really need:
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios
.post(url, form_data, request_config);
In my case, fs.createReadStream(file.path) did not work.
I had to use buffer instead.
const form = new FormData();
form.append('file', fs.readFileSync(filePath), fileName);
const config = {
headers: {
Authorization: `Bearer ${auth.access_token}`,
...form.getHeaders(),
},
};
axios.post(api, form.getBuffer(), config);
I have made an interceptor you can connect to axios to handle this case in node: axios-form-data. Any feedback would be welcome.
npm i axios-form-data
example:
import axiosFormData from 'axios-form-data';
import axios from 'axios';
// connect axiosFormData interceptor to axios
axios.interceptors.request.use(axiosFormData);
// send request with a file in it, it automatically becomes form-data
const response = await axios.request({
method: 'POST',
url: 'http://httpbin.org/post',
data: {
nonfile: 'Non-file value',
// if there is at least one streamable value, the interceptor wraps the data into FormData
file: createReadStream('somefile'),
},
});
// response should show "files" with file content, "form" with other values
// and multipart/form-data with random boundary as request header
console.log(response.data);
I had a same issue, I had a "pdf-creator-service" for generate PDF document from html.
I use mustache template engine for create HTML document - https://www.npmjs.com/package/mustache
Mustache.render function returns html as a string what do I need to do to pass it to the pdf-generator-service ? So lets see my suggestion bellow
//...
async function getPdfDoc(props: {foo: string, bar: string}): Promise<Buffer> {
const temlateFile = readFileSync(joinPath(process.cwd(), 'file.html'))
mustache.render(temlateFile, props)
const readableStream = this.getReadableStreamFromString(htmlString)
const formData = new FormData() // from 'form-data'
formData.append('file', options.file, { filename: options.fileName })
const formHeaders = formData.getHeaders()
return await axios.send<Buffer>(
{
method: 'POST',
url: 'https://pdf-generator-service-url/pdf',
data: formData,
headers: {
...formHeaders,
},
responseType: 'arraybuffer', // ! important
},
)
}
getReadableStreamFromString(str: string): Readable {
const bufferHtmlString = Buffer.from(str)
const readableStream = new Readable() // from 'stream'
readableStream._read = () => null // workaround error
readableStream.push(bufferHtmlString)
readableStream.push(null) // mark end of stream
return readableStream
}
For anyone who wants to upload files from their local filesystem (actually from anywhere with the right streams architecture) with axios and doesn't want to use any external packages (like form-data).
Just create a readable stream and plug it right into axios request function like so:
await axios.put(
url,
fs.createReadStream(path_to_file)
)
Axios accepts data argument of type Stream in node context.
Works fine for me at least in Node v.16.13.1 and with axios v.0.27.2

How to post image and array together with react native to backend?

I am trying to send an image file and data (object or array) to a backend with react native. I tried using fetch and the code below and got it to send an image to backend. But my target is to send the image and data together. How can I achieve this?
uploadPhoto = async (response) => {
const formData = new FormData();
formData.append('fileToUpload', {
uri: response.path,
name: 'image',
type: response.mime,
imgPath: response.path
});
const infos={ad:'onur',soyad:'cicek'};
try {
const rest = await fetch('https://www.birdpx.com/mobile/fotografyukle/'+this.state.user.Id, {
method: 'POST',
headers: { 'Accept': 'application/json', 'Content-type': 'multipart/form-data' },
body: formData
});
const gelenVeri = await rest.text();
let convertedVeri=JSON.parse(gelenVeri);
console.log(convertedVeri);
return false
} catch (err) {
console.log(err)
}
};
I need to post const infos={ad:'onur',soyad:'cicek'}; and the image.
You can pass it in formData itself
formData.append('other_infos', infos);
Here other_infos will be key that required to extract from backend, you can append n number of parameters in FormData
You can do it by adding other data to the FormData.
like in the example
formData.append('infos', infos);

Is there a way to upload blob url image to imgur?

I try to upload the image to imgur by api via javascript.
I used the base64 code first, but I think the storage is too big.
I changed the type of the file to blob.
but the post request status is 400.
data:{
error: "Invalid URL ("blob:http//...")",
method: "POST",
request: "/3/image"
}
or
data:{
error: "Invalid URL (Array)",
method: "POST",
request: "/3/image"
}
as following my code:
async dataCallBack(file){
//file = "blob http..."
var imageFile = new File([file], "giftpack.jpg");
let response = await imgurPost(imageFile); //or file
console.log(response);
const result = response.data.data;
const link = result.link;
this.setState({url: link});
}
imgurPost(file){
return axios.post('https://api.imgur.com/3/image',
{
image: file,
type: 'URL',
album: '0gECV',
}
,
{
headers:{
'Authorization': 'Client-ID {{clientId}}',
'Authorization': 'Bearer {{accessToken}}'
}
}).catch(e=>{
console.log(e.response);
})
}
Is there the way to use blob type?
A blob URL only works locally (and temporarily), so you can't send a blob with that.
You can use FormData.
var fd = new FormData();
fd.append('image', file);
fd.append('type', 'URL');
fd.append('album', '0gECV');
(You can find examples of using FormData with your specific XHR helper elsewhere.)

Categories

Resources