Why do I get a blank pdf when uploaded on S3? - javascript

I upload a pdf blob object to S3
const params = {
Bucket: "poster-print-bucket",
Key: Date.now().toString() + ".pdf",
Body: blob,
contentType: "application/pdf",
};
const uploaded = await S3.upload(params).promise();
When I open a url which is i.e https://poster-print-bucket.s3.ca-central-1.amazonaws.com/1633526785678.pdf
It downloads me a blank pdf
I thought maybe my blob is corrupted or something but I managed to upload same blob to firebase storage just fine.
btw I'm using nextjs api/upload-poster route
What's happening?

I spent more time fixing this issue than I would like to admit.
Here is the solution:
Frontend (converting blob to base64 before sending to backend):
function toBase64(blob) {
const reader = new FileReader();
return new Promise((res, rej) => {
reader.readAsDataURL(blob);
reader.onload = function () {
res(reader.result);
};
});
}
toBase64(currentBlob)
.then((blob) => {
return axios
.post("/api/upload-poster", blob, {
headers: {
"Content-Type": "application/pdf",
},
})
.then(({ data }) => data.uploaded.Location);
})
Backend:
const base64 = req.body;
const base64Data = Buffer.from(base64.replace(/^data:application\/\w+;base64,/, ""), "base64");
const params = {
Bucket: "poster-print-bucket",
Key: nanoid() + ".pdf",
Body: base64Data,
ContentEncoding: "base64",
contentType: "application/pdf",
};
const uploaded = await S3.upload(params).promise();
Why this all song and dance is required? Can it be something easier?

Using the AWS SDK v3 (up-to-date at the time of this post), you could use PutObjectCommand which accepts a Uint8Array as Body params (docs).
Convert your Blob instance to an ArrayBuffer (docs), and your ArrayBuffer to an Uint8Array.
Code would look like:
const { S3Client, PutObjectCommand } = require('#aws-sdk/client-s3');
const client = new S3Client(/* config */);
const arrayBuffer = await blob.arrayBuffer();
const typedArray = new Uint8Array(arrayBuffer);
await client.send(new PutObjectCommand({
Bucket: /* ... */,
Key: /* ... */,
Body: typedArray,
}));

Related

Uploading audio file buffer fails when uploading to aws s3

Here is my frontend code:
let audioFile = require("assets/hello.wav");
let blob = new Blob([audioFile], { type: "audio/wav" });
try {
await customFetch(`${API_URL}/new-audio-message`, {
method: "POST",
body: JSON.stringify({
audio: blob,
cloneId: cloneId,
}),
});
} catch (error) {
console.log(error);
}
Here is is how I upload the file to s3:
const { audio } = JSON.parse(event.body);
const fileKey = `${sub}/${cloneId}/audio/${uuidv4()}.wav`;
const buffer = Buffer.from(JSON.stringify(audio));
try {
await s3
.putObject({
Bucket: PUBLIC_BUCKET,
Key: fileKey,
Body: buffer,
})
.promise();
} catch (err) {
console.error(err);
}
The file uploads to s3 but the file size for every audio file is 155 B irrespective of the length of the audio file.
The issue seems to be that the audio file is not being properly converted to a buffer before being sent to S3. The line const buffer = Buffer.from(JSON.stringify(audio)) is attempting to convert the audio object to a string and then create a buffer from that string. However, this is not the correct way to convert a Blob object to a buffer.
Updated frontend code
let audioFile = require("assets/hello.wav");
let blob = new Blob([audioFile], { type: "audio/wav" });
const reader = new FileReader();
reader.readAsArrayBuffer(blob);
reader.onloadend = async () => {
const buffer = Buffer.from(reader.result);
try {
await customFetch(`${API_URL}/new-audio-message`, {
method: "POST",
body: JSON.stringify({
audio: buffer,
cloneId: cloneId,
}),
});
} catch (error) {
console.log(error);
}
};
Updated backend code
const { audio } = JSON.parse(event.body);
const fileKey = `${sub}/${cloneId}/audio/${uuidv4()}.wav`;
try {
await s3
.putObject({
Bucket: PUBLIC_BUCKET,
Key: fileKey,
Body: audio,
})
.promise();
} catch (err) {
console.error(err);
}

Audio Files Stored in S3 are not Playable

I can successfully upload audiofiles to AWS S3, however the files do not play whether downloaded or played in the browser. The files do have a filesize.
I'm uploading an audioBlob object to AWS S3:
const [audioBlob, setAudioBlob] = useState(null)
const submitVoiceMemo = async () => {
try {
await fetch('/api/createVoiceMemo', {
method: 'PUT',
body: audioBlob
})
} catch (error) {
console.error(error)
}
}
This is the API Route:
module.exports = requireAuth(async (req, res) => {
try {
AWS.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY_1,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY_ID,
region: 'us-east-2',
signatureVersion: 'v4'
})
const s3 = new AWS.S3();
const uuid = randomUUID()
const s3Params = {
Bucket: 'waveforms',
Key: `voicememo/${uuid}.mp3`,
Body: req.body,
ContentType: 'audio/mpeg',
ACL: 'public-read'
}
await s3.upload(s3Params).promise()
} catch (e) {
res.status(500).json({ error: e.message })
}
})
As per this question, I have confirmed that req.body is a string.
The file being received by S3 has a filesize and the correct contentType when checked in the S3 console. It just isn't playable when downloaded or played directly in the browser.
Below is how audioBlob and also audioFile (an MP3) are generated (Basically the user records a voice memo and when they click stopRecording, raw audio is stored as state):
const [audioBlob, setAudioBlob] = useState(null)
const [blobURL, setBlobUrl] = useState(null)
const [audioFile, setAudioFile] = useState(null)
const stopRecording = () => {
recorder.current
.stop()
.getMp3()
.then(([buffer, blob]) => {
const file = new File(buffer, 'audio.mp3', {
type: blob.type,
lastModified: Date.now()
})
setAudioBlob(blob)
const newBlobUrl = URL.createObjectURL(blob)
setBlobUrl(newBlobUrl)
setIsRecording(false)
setAudioFile(file)
})
.catch(e => console.log(e))
}
How can I correctly store audio generated on the front-end, in S3?

Img src="blob:http://localhost... doesn't work - neither with createObjectURL or readAsDataURL | Firebase | Vue.js

I think I have now tried everything and read every question on this matter, but still I can't make it to work..
Cart.vue
<template>
<div>
<h1>CART</h1>
<img :src="imgSrc" style="width:600px; height: 600px">
</div>
</template>
Cart.vue mounted()
mounted(){
const qr = firebase.functions().httpsCallable('sendPaymentRequest')
qr()
.then(res => {
const blob = new Blob([res.data], {type: 'image/jpg'})
console.log(blob);
const url = (window.URL || window.webkitURL).createObjectURL(blob)
console.log(url);
this.imgSrc = url;
})
Firebase functions
exports.sendPaymentRequest = functions.https.onCall((data, context) => {
const qr = async () => {
try {
const json = {
token: "umP7Eg2HT_OUId8Mc0FHPCxhX3Hkh4qI",
size: "300",
format: "jpg",
border: "0"
}
const response = await axios.post('https://mpc.getswish.net/qrg-swish/api/v1/commerce', json)
console.log('status', response.status)
if(response.status !== 200){throw new Error ('Error requesting QR code')}
return response.data
} catch (e){
console.log('error', e)
return e
}
}
return qr();
})
In my 2 console logs in the mounted() hook - the Blob and the URL - I get:
Looking pretty all right? There seem to be a Blob? And a URL?
however:
... sooo I tried changing the mounted() to
const qr = firebase.functions().httpsCallable('sendPaymentRequest')
qr()
.then(res => {
const self = this;
const blob = new Blob([res.data], {type: 'image/jpg'})
const reader = new FileReader();
reader.onloadend = function() {
self.imgSrc = reader.result
};
reader.readAsDataURL(blob);
})
.catch(e => console.log(e))
which also seem to work but.. well it's not.. Now I got a nice little base64-encoded string to my image instead of URL:
But still no image..
So I tried some other stuff I found while reading all of Internet.. moving from a callable function to onRequest function etc.. When I'm doing the exact same request with Postman I'm getting a fine QR code in the response..
If I'm loggin the response.headers in firebase functions I'm seeing
'content-type': 'image/jpeg',
'content-length': '31476',
So on the server I'm getting an image.. which I'm sending with return response.data
response.data being:
����JFIF��C
$.' ",#(7),01444'9=82<.342��C
2!!22222222222222222222222222222222222222222222222222�,,"��
and so on..
And that's where I'm at.. I'm getting .. frustrated.
Does anyone on here see what I'm doing wrong??
EDIT 1
for anyone running into this in the future - as #Kaiido points out on client I have to add
...
responseType: "blob"
}
but also on server, with firebase you need to move from
functions.https.onCall(async (data, context) => {
to
functions.https.onRequest(async (req, res) => {
call it on client with:
axios({
method: 'get',
url: 'http://localhost:5001/swook-4f328/us-central1/retrieveQr',
responseType: 'blob',
})
.then(async res => {
const url = (window.URL || window.webkitURL).createObjectURL(res.data)
this.imgSrc = url;
})
.catch(e => e)
and on server instead of axios use request (for some reason, but this works.. no idea why, but solves problem for now though I would be curious to why and I prefer axios to request)
works
const json = {
token: "umP7Eg2HT_OUId8Mc0FHPCxhX3Hkh4qI",
size: "300",
format: "png",
border: "0"
}
var requestSettings = {
url: 'https://mpc.getswish.net/qrg-swish/api/v1/commerce',
method: 'POST',
encoding: null,
json: true,
'content-type': 'application/json',
body: json,
};
request(requestSettings, (error, response, body) => {
res.set('Content-Type', 'image/png');
res.header({"Access-Control-Allow-Origin": "*"});
res.send(body);
});
does not work
const json = {
token: "umP7Eg2HT_OUId8Mc0FHPCxhX3Hkh4qI",
size: "300",
format: "png",
border: "0"
}
const response = await axios.post('https://mpc.getswish.net/qrg-swish/api/v1/commerce', json)
if(response.status !== 200){throw new Error ('Error requesting QR code')}
res.header({"Access-Control-Allow-Origin": "*"}).writeHead(200, {'Content-Type': 'image/png'}).end(response.data)
// neither this works:
// res.header({"Access-Control-Allow-Origin": "*"}).status(200).send(response.data)
You are receiving an utf8 encoded text, some bytes from the binary response have been mangled.
When doing your request, add an extra
...
responseType: "blob"
}
option to your axios request, this will ensure the binary data is not read as text but preserved correctly.
Plus now you don't need to build the Blob yourself, it is already one in response.data.

react-native through upload image on s3 Bucket using aws-sdk

I am using aws-sdk for upload image on the s3 bucket. Please look at my code below I already spend one day in it.
uploadImageOnS3 = () => {
var S3 = require("aws-sdk/clients/s3");
const BUCKET_NAME = "testtest";
const IAM_USER_KEY = "XXXXXXXXXXXXX";
const IAM_USER_SECRET = "XXXXX/XXXXXXXXXXXXXXXXXXXXXX";
const s3bucket = new S3({
accessKeyId: IAM_USER_KEY,
secretAccessKey: IAM_USER_SECRET,
Bucket: BUCKET_NAME
});
let contentType = "image/jpeg";
let contentDeposition = 'inline;filename="' + this.state.s3BucketObj + '"';
let file= {
uri: this.state.fileObj.uri,
type: this.state.fileObj.type,
name: this.state.fileObj.fileName
};
s3bucket.createBucket(() => {
const params = {
Bucket: BUCKET_NAME,
Key: this.state.s3BucketObj,
Body: file,
ContentDisposition: contentDeposition,
ContentType: contentType
};
s3bucket.upload(params, (err, data) => {
if (err) {
console.log("error in callback");
console.log(err);
}
// console.log('success');
console.log(data);
});
});
};
Error:
Unsupported body payload object
Please help me to short out I am also using react-native-image-picker for image upload.
You have to use array buffer in body stream to pass data object.
As per the aws documentation you can pass data stream, string, array buffer or blob data type in body parameter.
Please check below code, which will resolve your issue,
import fs from "react-native-fs";
import { decode } from "base64-arraybuffer";
uploadImageOnS3 = async() => {
var S3 = require("aws-sdk/clients/s3");
const BUCKET_NAME = "testtest";
const IAM_USER_KEY = "XXXXXXXXXXXXX";
const IAM_USER_SECRET = "XXXXX/XXXXXXXXXXXXXXXXXXXXXX";
const s3bucket = new S3({
accessKeyId: IAM_USER_KEY,
secretAccessKey: IAM_USER_SECRET,
Bucket: BUCKET_NAME,
signatureVersion: "v4"
});
let contentType = "image/jpeg";
let contentDeposition = 'inline;filename="' + this.state.s3BucketObj + '"';
const fPath = this.state.fileObj.uri;
const base64 = await fs.readFile(fPath, "base64");
//console.log(base64);
const arrayBuffer = decode(base64);
//console.log(arrayBuffer);
s3bucket.createBucket(() => {
const params = {
Bucket: BUCKET_NAME,
Key: this.state.s3BucketObj,
Body: arrayBuffer,
ContentDisposition: contentDeposition,
ContentType: contentType
};
s3bucket.upload(params, (err, data) => {
if (err) {
console.log("error in callback");
console.log(err);
}
// console.log('success');
console.log(data);
});
});
};
You can check out the React Native AWS amplify documentation for the proper process. In the documentation, it is mentioned that you can pass data stream, string, array buffer, or blob data type in body parameter.
import AWS from 'aws-sdk';
import fs from 'react-native-fs';
import {decode} from 'base64-arraybuffer';
export const uploadFileToS3 = async (file) => {
const BUCKET_NAME = 'xxxxx';
const IAM_USER_KEY = 'xxxxxxxxxxxxxxxxx';
const IAM_USER_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
const s3bucket = new AWS.S3({
accessKeyId: IAM_USER_KEY,
secretAccessKey: IAM_USER_SECRET,
Bucket: BUCKET_NAME,
signatureVersion: 'v4',
});
const contentType = file.type;
const contentDeposition = `inline;filename="${file.name}"`;
const fPath = file.uri;
const base64 = await fs.readFile(fPath, 'base64');
const arrayBuffer = decode(base64);
return new Promise((resolve, reject) => {
s3bucket.createBucket(() => {
const params = {
Bucket: BUCKET_NAME,
Key: file.name,
Body: arrayBuffer,
ContentDisposition: contentDeposition,
ContentType: contentType,
};
s3bucket.upload(params, (error, data) => {
if (error) {
reject(getApiError(error));
} else {
console.log(JSON.stringify(data));
resolve(data);
}
});
});
});
}

Javascript/React fetch api sending image via formdata

I'm trying to send a file object (image) as a multipart/form-data request using the javascript fetch api.
The code below shows how I convert the file object to string format.
The image parameter is an array of File objects. I want to send the first image of that array to a webservice.
onImageUpload = ((image) => {
//File to arraybuffer
let fr = new FileReader();
fr.onload = function(e) {
var text = e.target.result;
AccountService.uploadProfileImage(localStorage.getItem('session-key'), text)
.then((ro) => {
if(ro.code == 200) {
this.showSnackbar("Profile image uploaded successfully!");
}
else {
this.showSnackbar("Error uploading your image. Please try again later.");
}
})
}
fr.readAsText(image[0]);
})
Below is my uploadProfileImage function, which makes the post request using the fetch api.
static async uploadProfileImage(sessionKey, image) {
var reader = new FileReader();
let ro = new ResponseObject();
let fd = new FormData();
let imgObj = new Image();
imgObj.src = image;
let blob = new Blob([new Uint8Array(image)], { type: "image/jpg"});
fd.append("name", localStorage.getItem('username'));
fd.append("file", blob);
return new Promise((resolve, reject) => {
fetch(UrlConstants.BASE_URL + this.urlUploadProfileImage, {
method: "POST",
headers: {
'Authorization': 'Bearer ' + sessionKey,
"Content-type": "multipart/form-data;"
},
body: fd
}).then((response) => {
ro.code = response.status;
return response.text();
})
.then((text) => {
ro.message = text;
resolve(ro);
})
.catch((ex) => {
reject(ex);
})
});
}
When I sent the image parameter in the uploadProfileImage without converting it into a blob the data are being sent, but i need the blob to have the Content-Type: "image/jpg" in the request as the api I'm working with can't handle the request without it.
The problem that the image is not being included in the request.
How can I do that?
Thanks for your help.

Categories

Resources