nativescript image upload to s3 encoding issue - amazon-s3

I'm having issues uploading an image from Nativescript to AWS, and I'm pretty sure it's a configuration issue.
select an image
const context = imagepicker.create({
mode: 'single' // use "multiple" for multiple selection
});
await context.authorize();
const selection: Array<ImageAsset> = await context.present();
const imageAsset = selection[0];
const source: ImageSource = await new ImageSource().fromAsset(imageAsset);
const fileLocation = imageAsset.android ? imageAsset.android : imageAsset.ios;
const fileType = mime.extension(mime.lookup(fileLocation));
const image = source.toBase64String(fileType);
console.log(image);
image at this point is: iVBORw0KGgoAAAANSUhEUgAAB4AAAASwCAIAAACVUsChAAAAA3NCSVQI...
image location at this point is: /storage/emulated/0/DCIM/Screenshots/Screenshot_20181106-150854.png
const fileLocation = imageAsset.android ? imageAsset.android : imageAsset.ios;
const signedUrl = await this.getSignedUrl(fileLocation);
Backend Code to get signedURL
const getSignedUrlPromise = (operation, params) => {
return new Promise((resolve, reject) => {
s3.getSignedUrl(operation, params, (err, url) => {
err ? reject(err) : resolve(url);
});
});
}
const params = {
Bucket: BUCKET_NAME,
Key: `abc123/456/3/${fileName}`,
ContentType: contentType,
ContentEncoding: 'base64'
}
const url = await getSignedUrlPromise('putObject', params).catch(err => {
console.log('error', JSON.stringify(err))
return {
statusCode: 400,
body: JSON.stringify(err)
}
});
console.log('success', url);
return {
statusCode: 200,
body: JSON.stringify({ url: url })
}
signedUrl at this point is:
https://myproject.s3.amazonaws.com/abc123/456/3/Screenshot_20181106-150854.png?AWSAccessKeyId=xxxxxxxxxxxxxx&Content-Encoding=base64&Content-Type=image%2Fpng&Expires=1555517358&Signature=yyyyyyyy&x-amz-security-token=long_token
Then, using the signedURL, i upload the image:
const mimeType = mime.lookup(fileLocation);
this.http.put(signedUrl, image, {
headers: {
'Content-Type': mimeType,
'Content-Encoding': 'base64'
}
}).subscribe((resp) => {
console.log('resp2', resp);
});
}
When I open the file, this is what I see
and the meta-data on the S3 object looks correct
When I download the file and open it in NP++, I see the base64 value.
iVBORw0KGgoAAAANSUhEUgAAB4AAAASwCAIAAACVUsChAAAAA3NCSVQI...
I also cannot open the downloaded image
ATTEMPT 2
I saw where some people were using buffers, so I changed my image code to
const image = Buffer.from(source.toBase64String(fileType).replace(/^data:image\/\w+;base64,/, ''), 'base64');
which the image is still broken, and when I download and open the file using NP++ I see
{"type":"Buffer","data":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,7,128,0,0,4,176,8,2,0,0,0

Related

React native content uri to base64 string

I'm trying to upload files using RN Document Picker.
Once I get those files selected, I need to turn them to base64 string so I can send it to my API.
const handlePickFiles = async () => {
if (await requestExternalStoreageRead()) {
const results = await DocumentPicker.pickMultiple({
type: [
DocumentPicker.types.images,
DocumentPicker.types.pdf,
DocumentPicker.types.docx,
DocumentPicker.types.zip,
],
});
const newUploadedFile: IUploadedFile[] = [];
for (const res of results) {
console.log(JSON.stringify(res, null, 2));
newUploadedFile.push({
name: res.name,
type: res.type as string,
size: res.size as number,
extension: res.type!.split('/')[1],
blob: res.uri, <<-- Must turn this in base64 string
});
}
setUploadedFiles(newUploadedFile);
console.log(newUploadedFile);
}
}
};
The document picker returns content uri (content://...)
They lists this as an example of handling blob data and base64:
let data = new FormData()
data.append('image', {uri: 'content://path/to/content', type: 'image/png', name: 'name'})
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: data
})
Where they basically say that you don't need to use blob or base64 when using multipart/form-data as content type. However, my graphql endpoint cannot handle multipart data and I don't have time to rewrite the whole API. All I want is to turn it to blob and base64 string, even if other ways are more performant.
Searching for other libraries, all of them are no longer maintained, or has issues with new versions of android. RN Blob Utils is the latest npm that was no longer maintained.
I tried to use RN Blob Utils but I either get errors, wrong data type, or the file uploads but is corrupted.
Some other things I found is that I can use
fetch(res.uri).then(response => {response.blob()})
const response = await ReactNativeBlobUtil.fetch('GET', res.uri);
const data = response.base64();
ReactNativeBlobUtil.wrap(decodeURIComponent(blob))
///----
const blob = ReactNativeBlobUtil.fs.readFile(res.uri, 'base64');
But I can't do anything with that blob file.
What is the simplest way to uplaod files from document picker as base64 format? Is it possible to avoid using external storage permission?
You don't need to the third-party package to fetch BLOB data
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
resolve(xhr.response);
};
xhr.onerror = function (e) {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", "[LOCAL_FILE_PATH]", true);
xhr.send(null);
});
// Code to submit blob file to server
// We're done with the blob, close and release it
blob.close();
I ended up using react-native-blob-util
const res = await DocumentPicker.pickSingle({
type: [
DocumentPicker.types.images,
DocumentPicker.types.pdf,
DocumentPicker.types.docx,
DocumentPicker.types.zip,
],
});
const newUploadedFile: IUploadedFile[] = [];
const fileType = res.type;
if (fileType) {
const fileExtension = fileType.substr(fileType.indexOf('/') + 1);
const realURI = Platform.select({
android: res.uri,
ios: decodeURI(res.uri),
});
if (realURI) {
const b64 = await ReactNativeBlobUtil.fs.readFile(
realURI,
'base64',
);
const filename = res.name.replace(/\s/g, '');
const path = uuid.v4();
newUploadedFile.push({
name: filename,
type: fileType,
size: res.size as number,
extension: fileExtension,
blob: b64,
path: Array.isArray(path) ? path.join() : path,
});
} else {
throw new Error('Failed to process file');
}
} else {
throw new Error('Failed to process file');
}

Problem to generate pdf from a blob in an expo app using FileSystem

I get a blob and I treat it like this:
const file = response.data;
var blob = new Blob([file], {
type: 'application/pdf',
});
const fileReaderInstance = new FileReader();
fileReaderInstance.readAsDataURL(blob);
fileReaderInstance.onload = async () => {
const fileUri = `${FileSystem.documentDirectory}file.pdf`;
await FileSystem.writeAsStringAsync(
fileUri,
fileReaderInstance.result.split(',')[1],
{
encoding: FileSystem.EncodingType.Base64,
}
);
console.log(fileUri);
Sharing.shareAsync(fileUri);
};
however when I generate and share the file, I can't access it and if I get its URI and search on the web it returns:
i solved my problem in this way:
This is a func who get other data to request, do the request (generate PDF()) and treat the data and generate by received blob the buffer on (fileReaderInstance.result) who is shared in Sharing.shareAsync()
const generatePDF = async () => {
setAnimating(true);
const companyReponse = await CompanyService.getCompany();
const peopleResponse = await PeopleService.getPerson(sale.customerId);
const company = companyReponse.response.company;
const people = peopleResponse.response;
const quote = false;
const json = await SaleService.generatePDF({
sale,
company,
people,
quote,
});
if (json && json.success) {
try {
const fileReaderInstance = new FileReader();
fileReaderInstance.readAsDataURL(json.data);
fileReaderInstance.onload = async () => {
const base64data = fileReaderInstance.result.split(',');
const pdfBuffer = base64data[1];
const path = `${FileSystem.documentDirectory}/${sale._id}.pdf`;
await FileSystem.writeAsStringAsync(`${path}`, pdfBuffer, {
encoding: FileSystem.EncodingType.Base64,
});
await Sharing.shareAsync(path, { mimeType: 'application/pdf' });
};
} catch (error) {
Alert.alert('Erro ao gerar o PDF', error.message);
}
}
setAnimating(false);
}
This is the func in SaleServicegeneratePDF who do the request to api and pass the parameters that return a blob of pdf using axios:
generatePDF: async ({ sale, company, people, quote }) => {
const token = await AsyncStorage.getItem('token');
const body = { sale, company, people, quote };
try {
const response = await axios(`${BASE_API}/generate-sale-pdf`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
responseType: 'blob',
data: body,
});
return {
success: true,
data: response.data,
};
} catch (err) {
return err.error;
}
},
I have solved this problem by passing the blob string to WriteAsStringAsync method of FileSystem library from expo.
const blobDat = data.data[0].data; //blob data coming from an API call
const fileUri = FileSystem.documentDirectory + `testt.pdf`; //Directory Link of the file to be saved
await FileSystem.writeAsStringAsync(fileUri, blobDat, {
encoding: FileSystem.EncodingType.UTF8,
}) //This step writes the blob string to the pdf fileURI
await IntentLauncher.startActivityAsync("android.intent.action.VIEW", {
data: fileUri,
flags: 1,
type: "application/pdf",
});
//prompts user with available application to open the above created pdf.

Converting Base64 string into blob in React Native

i have been looking for a solution to convert Image from base64 string into Blob
i get my images via react-native-image-crop-picker
the image object i get is formatted in this way :
{
creationDate: "1299975445"
cropRect: null
data: "/9j...AA"
duration: null
exif: null
filename: "IMG_0001.JPG"
height: 2848
localIdentifier: "10...001"
mime: "image/jpeg"
modificationDate: "1441224147"
path: "/Users/...351F66445.jpg"
size: 1896240
sourceURL: "file:///Users/...IMG_0001.JPG"
width: 4288
}
which means i have the path and source url and image data as base64 string.
what i need is to upload the images that the user picks as blob file to the server.
so is there any way to do the conversion in react native.
ps: i have tried solutions i found online but non of them seems to work so far but none of them seems to work for me.
urlToBlob = (url) => new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onerror = reject;
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
resolve(xhr.response);
}
};
xhr.open('GET', url);
xhr.responseType = 'blob'; // convert type
xhr.send();
})
this.urlToBlob(data)
.then((blob) => {
console.log(blob);
});
i tried this peace of code and this what i got in my console :
{
_data:
blobId: "B69744A5-B8D7-4E6B-8D15-1C95069737EA"
name: "Unknown"
offset: 0
size: 1896240
type: "image/jpeg"
__collector: null
}
after a lot of search i found a solution if anyone faced this issue.
i used a package 'extract-files' it allowed me to the images as files in order to send them to the server
it worked like this:
import { ReactNativeFile } from 'extract-files';
const FormData = require('form-data');
_addImage = async () => {
const { images } = this.state;
if (images.imageData.length > 0) {
const formData = new FormData();
// converting images to files
images.imageData.forEach((element) => {
const file = new ReactNativeFile({
uri: element.sourceURL,
name: element.filename,
type: element.mime,
});
formData.append('files', file);
});
// sending files to the server
await addImages(formData).then((response) => {
console.log('response : ', response);
if (response.success) {
this.pictures = response.filesNames;
this.setState({
isLoading: true,
pictures: response.filesNames
});
return response.success;
}
}).catch((err) => {
console.error(err);
});
}
}
i hope this is helpful

Convert Base64 to png and save in the device React Native Expo

I've been trying to save an image on a mobile device with React Native and Expo, i have tried with these packages:
import RNFetchBlob from 'react-native-fetch-blob';
import RNfs from 'react-native-fs ';
but both give me this error when implementing them
null is not an object (evaluating 'RNFetchBlob.DocumentDir')
then try expo-file-system but i don't see any clear example of how to convert base64 and download it to mobile
UPDATE
I was able to do it, my purpose was to save the base64 of a QR and convert it to png and at the same time be able to share it, I did it using expo-file-system and expo-sharing
this is mi code,
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
//any image, I use it to initialize it
const image_source = 'https://images.unsplash.com/photo-1508138221679-760a23a2285b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80';
share=()=>{
var self = this;
self.setState({loading:true})
FileSystem.downloadAsync(
image_source,
FileSystem.documentDirectory + '.png'
)
.then(({ uri }) => {
console.log(self.state.base64Code);
FileSystem.writeAsStringAsync(
uri,
self.state.base64Code,
{'encoding':FileSystem.EncodingType.Base64}
)
.then(( ) => {
this.setState({loading:false})
Sharing.shareAsync(uri);
})
})
.catch(error => {
console.error(error);
});
}
Actually, I don't know if it's the best way, first write a png image in the directory and then rewrite it with the base64 code, but it worked
This worked for me:
const data = "data:image/png;base64,ASDFASDFASDf........"
const base64Code = data.split("data:image/png;base64,")[1];
const filename = FileSystem.documentDirectory + "some_unique_file_name.png";
await FileSystem.writeAsStringAsync(filename, base64Code, {
encoding: FileSystem.EncodingType.Base64,
});
const mediaResult = await MediaLibrary.saveToLibraryAsync(filename);
Thanks for the update. I've been struggling for days with this on Android and base64 images!
In my case i was trying to upload a base64 image from a signature pad on expo and always got "Network request failed"
I managed to make it work like this hope it helps!
import * as FileSystem from 'expo-file-system';
const uploadBase64 = async (base64String) => {
this.setState({ uploading: true });
//Without this the FilySystem crashes with 'bad base-64'
const base64Data = base64String.replace("data:image/png;base64,","");
try {
//This creates a temp uri file so there's no neeed to download an image_source to get a URI Path
const uri = FileSystem.cacheDirectory + 'signature-image-temp.png'
await FileSystem.writeAsStringAsync(
uri,
base64Data,
{
'encoding': FileSystem.EncodingType.Base64
}
)
//At this point the URI 'file://...' has our base64 image data and now i can upload it with no "Network request failed" or share the URI as you wish
const uploadResult = await this.uploadImageAsync(uri).then(res => res.json())
if (uploadResult) {
this.setState({ image: uploadResult.location });
}
this.setState({ uploading: false });
} catch (e) {
this.setState({ uploading: false });
console.log('*Error*')
console.log(e)
}
}
//Just and ordinary upload fetch function
const uploadImageAsync = (uri) => {
let apiUrl = 'https://file-upload-example-backend-dkhqoilqqn.now.sh/upload';
let formData = new FormData();
formData.append('photo', {
uri,
name: `photo.png`,
type: `image/png`,
});
let options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
return fetch(apiUrl, options);
}

React Native - send image from local cache to firebase storage

With React Native on Android I am trying to send the image profile of the user from local cache to a firebase storage bucket. If I send it as blob or Uint8Array, when I open the image on firebase I get the error The image "https://firebasestorage<resto of url here>" cannot be displayed because it contain errors. If I send it as base64 data url,it does not upload to the bucket and I get the message Firebase Storage: String does not match format 'base64': Invalid character found. I have tested the base64 data url with a decoder and it works. How can I get this to work, either as blob, Uint8Array or base64?. Here is the code:
As blob
let mime = 'image/jpeg';
getFile(imageUri)
.then(data => {
return new Blob([data], { type: mime });
})
.then(blob => {
return imageRef.put(blob, { contentType: mime });
})
async function getFile(imageUri) {
let bytes = await FileSystem.readAsStringAsync(imageUri);
return Promise.resolve(bytes);
}
As Uin8Array
let mime = 'image/jpeg';
getFile(imageUri)
.then(data => {
return imageRef.put(data, { contentType: mime });
})
async function getFile(imageUri) {
let bytes = await FileSystem.readAsStringAsync(imageUri);
const imageBytes = new Uint8Array(bytes.length);
for ( let i = 0; i < imageBytes.length; i++) {
imageBytes[i] = bytes.charCodeAt(i);
}
return Promise.resolve(imageBytes);
}
As base64 data url
imageBase64Url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAF0lEQVQI12P8//8/AwMDAwMDEwMMIFgAVCQDA25yGkkAAAAASUVORK5CYII=";
return imageRef.putString(imageBase64Url, 'data_url');
The URI
I retrieve the uri from this object:
Object {
"cancelled": false,
"height": 60,
"type": "image",
"uri": "file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FMCC_Project-ee81e7bd-82b1-4624-8c6f-8c882fb131c4/ImagePicker/6ec14b33-d2ec-4f80-8edc-2ee501bf6e92.jpg",
"width": 80,
}
We found at least two problems with the way I was trying to retrieve the picture and send it to the Firebase bucket:
1) When retrieving the image from memory and trying to send it as blob to the bucket, FileSystem.readAsStringAsync(imageUri) was returning for some reason a corrupted file
2) Instead when trying to save the image to Firebase bucket as base64, the problem seems to be with firebase, since not even the very same examples provided here https://firebase.google.com/docs/storage/web/upload-files were working.
The solution:
We retrieved the image from local cache with XMLHttpRequestinstead of Expo's FileSystem, and saved it to Firebase bucket as blob:
import React, { Component } from 'react';
import firebase from './firebase';
export default async function saveImage(picture, uid) {
const storageRef = firebase
.storage('gs://*<bucket-here>*')
.ref(uid + '/' + 'profile-picture.jpeg');
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(xhr.response);
};
xhr.onerror = function(e) {
console.log(e);
reject(new TypeError('Network request failed'));
};
xhr.responseType = 'blob';
xhr.open('GET', picture.uri, true);
xhr.send(null);
});
const metadata = {
contentType: 'image/jpeg',
};
return (downloadURL = await new Promise((resolve, reject) => {
try {
storageRef.put(blob, metadata).then(snapshot => {
snapshot.ref.getDownloadURL().then(downloadURL => {
resolve(downloadURL);
});
});
} catch (err) {
reject(err);
}
}));
}