1

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 do result.blob() I get blob is not a function
  • My file.uri begins with file:///, I've tried change it to file://, 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.

user2465134
  • 8,793
  • 5
  • 32
  • 46
  • I have had a similar issue to you, first time around this helped: https://stackoverflow.com/questions/69833454/using-lambda-to-get-image-from-s3-returns-a-white-box-in-python/69834396#69834396 but something has changed and now I am experiencing the issue again so I am back at it... curious if you have made progress in the last few months? – Gibron Nov 10 '22 at 21:01
  • @Gibron tldr I had to convert the image into a blob in order to save it. React Native doesn't use the native JavaScript fetch, they use a different library (whatwg-fetch), which is why creating a blob wasn't working properly. Check out https://stackoverflow.com/questions/72623333/react-native-blob-is-not-a-function for more – user2465134 Nov 15 '22 at 15:12
  • 1
    Thanks @user2465134 happy its sorted for you! – Gibron Nov 17 '22 at 16:29

0 Answers0