Last 10ish hours I've been trying to successfully upload an image from my react native app to S3.
React Native app makes a request to my Serverless lambda endpoint:
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')
const BUCKET_NAME = process.env.BUCKET_NAME
const REGION = process.env.AWS_REGION
const expirationTime = 60
const s3Client = new S3Client({
region: REGION,
})
module.exports = App => {
App.controllers.putPresignedUrl = async (event, context, cb) => {
const body = JSON.parse(event.body)
const { fileName, type } = body
const params = {
Bucket: BUCKET_NAME,
Key: fileName,
ContentType: type
}
try {
const command = new PutObjectCommand(params)
const signedUrl = await getSignedUrl(s3Client, command, {
expiresIn: expirationTime
})
return cb(null, utils.res(200, { url: signedUrl }))
} catch (err) {
console.log('ERROR putPresignedUrl : ', err)
return cb(null, utils.res(400, { message: 'Failed to pre-sign url' }))
}
}
}
In my RN app I have:
import React from 'react'
import { Button, SafeAreaView } from 'react-native'
import { useDispatch } from 'react-redux'
import { launchImageLibrary } from 'react-native-image-picker'
import { createPicture } from '../store/pictures/Picture.reducer'
const Home = () => {
const dispatch = useDispatch()
const getImageFromLibrary = async () => {
const options = { noData: true }
const result = await launchImageLibrary(options)
const file = result.assets[0]
dispatch(createPicture({ file }))
}
return (
<SafeAreaView>
<Button onPress={getImageFromLibrary} title="Get from library" />
</SafeAreaView>
)
}
export default Home
Running createPicture will call my lambda function via my API file:
export async function saveImageToS3(preSignedUrl, file) {
const awsAxios = axios.create({
transformRequest: (data, headers) => {
// Remove all shared headers
delete headers.common
return data
}
})
const data = await awsAxios.put(
preSignedUrl,
file.uri,
{
'Content-Type': file.type
}
)
}
I've always been able to upload to my s3 bucket but the files are either corrupt or just a small white box when opened
- Doing the above code and trying to just upload the
result.assets[0]
(file) will give me a corrupt file in S3 - Trying to append all the data from the file to a formData object will give me a corrupted file
- Trying to create a blob doing
const result = fetch(file.uri)
will work, then when I try to doresult.blob()
I get blob is not a function - My
file.uri
begins withfile:///
, I've tried change it tofile://
,file:/
and getting rid of it altogether. Corrupted file in the end
I then turned to try to upload a base64 string. I am using react-native-fs
to create the base64. I also tried using an xhr request instead of axios:
const fileString = await RNFS.readFile(file.uri, 'base64')
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// success
} else {
// failure
}
}
}
xhr.open('PUT', preSignedUrl)
xhr.setRequestHeader('Content-Type', pictureData.type)
xhr.send({ uri: fileString, type: pictureData.type, name: pictureData.fileName })
I've tried to change the headers to ContentType: 'application/octet-stream'
on the backend and frontend but that still doesn't work. I've also added ContentEncoding: 'base64'
to my params on the lambda side. However, when I started to use the XHR requests, my files were no longer corrupted and were a small white box instead.
I feel like I've read every thread and visited every blog that discusses presigned uploads. Everyone seems to be able to do this easily and I'm really unsure and frustrated how my code isn't able to do this successfully.