benjreinhart / react-native-aws3

Pure JavaScript React Native library for uploading to AWS S3
MIT License
399 stars 151 forks source link

Upload base64 #14

Open note89 opened 8 years ago

note89 commented 8 years ago

Hi i would like to be able to upload images that is in base64 format. There is a bug in another library I'm using so i cannot get the image, but I can get the base64 representation of the image. So i would like to upload that to S3 and have it become a actual image. Any ideas on how to do this ?

benjreinhart commented 8 years ago

Unfortunately, I am not sure. Did you try to use this library and pass the base64 data and see if that worked?

agrass commented 7 years ago

It didn't work. Error: Could not retrieve file for contentUri

MrHubble commented 7 years ago

@note89 @agrass did you figure out how to upload an image in base64 format? If yes, could you please share your solution? Thanks.

MrHubble commented 7 years ago

@benjreinhart answers on Stack Overflow suggest using Buffer and then uploading the file with the S3 plugin, ie:

 buf = new Buffer(req.body.imageBinary.replace(/^data:image\/\w+;base64,/, ""),'base64')
  var data = {
    Key: req.body.userId, 
    Body: buf,
    ContentEncoding: 'base64',
    ContentType: 'image/jpeg'
  };

s3Bucket.putObject(data, function(err, data){

I believe the react-native-aws3 codebase uses FormData to create a set of key/value pairs representing form fields and their values for uri, name and type. It then uses XMLHttpRequest to submit to AWS S3. I think.

Is there anyway to use a buffer object in place of the uri key and object?

When I try to upload with the current code base (nothing to do with the buffer example above) with a file object like:

let file = {
    uri: `data:image/png;base64, ${base64Image}`,
    name: "image.png",
    type: "image/png"
}

I get the error: Error Domain=NSCocoaErrorDomain Code=256 "The file couldn’t be opened." UserInfo={NSURL

MrHubble commented 7 years ago

As an update, I believe S3 POST requires a file object, whereas S3 PUT does not. I have unsuccessfully tried converting the base64 format to a file object to use react-native-aws3 but get a Possible Unhandled Promise Rejection error:

let fetchResponse = await fetch(base64image)
let buffResponse = await fetchResponse.arrayBuffer()
let file = new File([buffResponse], 'image.png', {type:'image/png'})              
uploader(file, (percent, modalVisible, response) => {
  if(response)
    {
    console.log(response)
    this.buildRequest(prestart)
    }
})
//uploader.js
  RNS3.put(file, options).then(response => {
    if (response.status !== 201)
      throw new Error("Failed to upload image to S3")
    callback(0, false, response)
  }).progress((e) => callback(e.percent, true, false))
  .catch((error) => {
    let errorMessage = error.text
    callback(0, false, false)
  })

Next I plan on using the default aws-sdk gem to put with a buffer body as shown in a pervious comment rather than post with a file object. If that works I may try to submit a pull request for react-native-aws3 to support put as well as post.

MrHubble commented 7 years ago

I ended up with a working solution by using aws-sdk instead:

const Buffer = global.Buffer || require('buffer').Buffer;
var AWS = require('aws-sdk/dist/aws-sdk-react-native');
AWS.config.update({
  accessKeyId: myKeyId,
  secretAccessKey: mySecretKey,
  region: myRegion
})

var s3 = new AWS.S3()
buf = new Buffer(base64image.replace(/^data:image\/\w+;base64,/, ""), 'base64')
let keyPrefix = `uploads/image.png`    
var params = {
  Bucket: "mybucket",
  Key: keyPrefix,
  Body: buf,
  ContentType: 'image/png',
  ContentEncoding: 'base64',
  ACL: 'public-read'  
}
let putObjectPromise = await s3.upload(params).promise()
bdesouza commented 7 years ago

@MrHubble great idea! Thanks for sharing. I'm running into something similar but with audio. How did you construct the base64image?

MrHubble commented 7 years ago

@bdesouza I was using a signature capture component which returned a Base64 Encoded String. That component doesn't sound like it would be any help for you with audio.

frayeralex commented 7 years ago

My solution with reading file, decode to ArrayBuffer and upload to s3

// @flow
import { S3 } from "aws-sdk";
import { config } from "../environments/config";
import fs from "react-native-fs";
import { decode } from "base64-arraybuffer";

// init s3 client with your credentials
const s3 = new AWS.S3();

// path = "file://...", name = "profile.jpg"
const uploadFile = async (path: string, name: string): Promise => {
    try {
      const base64 = await fs.readFile(path, "base64");
      const arrayBuffer = decode(base64);
      const params = {
        Body: arrayBuffer,
        Bucket: config.aws.s3.bucket,
        Key: `${config.aws.s3.folder}/${name}`,
        ACL: "public-read",
        ContentEncoding: "utf-8",
        ContentType: "binary"
      };
      const uploadData = await s3.putObject(params).promise();
      return Promise.resolve(uploadData);
    } catch (error) {
      return Promise.reject(error);
    }
}
bdesouza commented 7 years ago

Oh that's an interesting approach. I ended up going the route of creating a presigned url and then doing and xhr post to it. That way I avoided having to use fs library which isn't fully supported on the Expo framework as yet.

                    //Upload File
                    const xhr = new XMLHttpRequest();
                    xhr.open('PUT', presignedUrl);
                    xhr.setRequestHeader('X-Amz-ACL', 'public-read');
                    xhr.setRequestHeader('Content-Type', fileType);
                    xhr.onreadystatechange = function() {
                        console.log(xhr.responseText);
                        if (xhr.readyState === 4) {
                            if (xhr.status === 200) {
                                // Successfully uploaded the file.
                                console.log('File Uploaded!')
                            } else {
                                // The file could not be uploaded.
                                console.log('File upload failed. :( ')
                            }
                        }
                    }
                    xhr.send({ uri: filePath, type: fileType, name: fileName });
iamtekeste commented 6 years ago

I spent two days trying to solve this issue! Thanks @frayeralex your solution did it for me 🎉

rochapablo commented 6 years ago

@frayeralex, I tried, but I'm getting:

{ [Error: Permission Denial: reading com.android.providers.media.MediaDocumentsProvider uri content://com.android.providers.media.documents/document/image%3A315/pexels-photo-267151.jpeg from pid=9944, uid=10168 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()]
      framesToPop: 1,
      code: 'EUNSPECIFIED',
      line: 11732,
      column: 29,
      sourceURL: 'http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=false&minify=false' }

Any Idea how to fix?

https://github.com/joltup/react-native-fetch-blob/issues/59