Firebase Storage: string does not match format base64: invalid character found. Only when debug is off
Firebase Storage: string does not match format base64: invalid character found. Only when debug is off
I'm trying to upload an image file to firebase storage, save the download URL, and load it after the upload is completed. When I run the app with debug js remotely on it works fine. When I turn off debug mode it stops working with the invalid format exception. The same happens when I run in a real device (both iOS and Android)
The base64 response data from React Native Image Picker seems to be correct
Here's my code
...
import * as ImagePicker from 'react-native-image-picker'; //0.26.10
import firebase from 'firebase'; //4.9.1
...
handleImagePicker = () =>
const me = this.props;
const options =
title: 'Select pic',
storageOptions:
skipBackup: true,
path: 'images'
,
mediaType: 'photo',
quality: 0.5,
;
ImagePicker.showImagePicker(options, async (response) =>
const storageRef = firebase.storage().ref(`/profile-images/user_$me.id.jpg`);
const metadata =
contentType: 'image/jpeg',
;
const task = storageRef.putString(response.data, 'base64', metadata);
return new Promise((resolve, reject) =>
task.on(
'state_changed',
(snapshot) =>
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
,
(error) =>
console.log(error),
() =>
this.onChangeProfileImage();
);
);
onChangeProfileImage = async () =>
const me = this.props;
const storageRef = firebase.storage().ref(`/profile-images/user_$me.id.jpg`);
const profileImageUrl = await new Promise((resolve, reject) =>
storageRef.getDownloadURL()
.then((url) =>
resolve(url);
)
.catch((error) =>
console.log(error);
);
);
// some more logic to store profileImageUrl in the database
Any idea how to solve this?
Thanks in advance.
1 Answer
1
After some research and debug I found the cause of the issue and a solution for it.
Firebase uses atob
method to decode the base64 string sent by putstring
method.
However, since JavaScriptCore doesn't have a default support to atob
and btoa
, the base64 string can't be converted, so this exception is triggered.
atob
putstring
atob
btoa
When we run the app in debug javascript remotely mode, all javascript code is run under chrome environment, where atob
and btoa
are supported. That's why the code works when debug is on and doesn't when its off.
atob
btoa
To handle atob
and btoa
in React Native, we should either write our own encode/decode method, or install a lib to handle it for us.
atob
btoa
In my case I preferred to install base-64
lib
base-64
But here's an example of a encode/decode script:
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const Base64 =
btoa: (input:string = '') => 0) ,
atob: (input:string = '') =>
let str = input.replace(/=+$/, '');
let output = '';
if (str.length % 4 == 1)
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
for (let bc = 0, bs = 0, buffer, i = 0;
buffer = str.charAt(i++);
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
)
buffer = chars.indexOf(buffer);
return output;
;
export default Base64;
Usage:
import Base64 from '[path to your script]';
const stringToEncode = 'xxxx';
Base64.btoa(scriptToEncode);
const stringToDecode = 'xxxx';
Base64.atob(stringToDecode);
After choosing either to use the custom script or the lib, now we must add the following code to the index.js
file:
index.js
import decode, encode from 'base-64';
if (!global.btoa)
global.btoa = encode;
if (!global.atob)
global.atob = decode;
AppRegistry.registerComponent(appName, () => App);
This will declare atob
and btoa
globally. So whenever in the app those functions are called, React Native will use the global scope to handle it, and then trigger the encode
and decode
methods from base-64
lib.
atob
btoa
encode
decode
base-64
So this is the solution for Base64 issue.
However, after this is solved, I found another issue Firebase Storage: Max retry time for operation exceed. Please try again
when trying to upload larger images. It seems that firebase
has some limitation on support to React Native uploads, as this issue suggests.
Firebase Storage: Max retry time for operation exceed. Please try again
firebase
I believe that react-native-firebase
may not struggle on this since it's already prepared to run natively, instead of using the web environment as firebase
does. I didn't test it yet to confirm, but it looks like this will be the best approach to handle it.
react-native-firebase
firebase
Hope this can be helpful for someone else.
BTW, @soutot, did you use fetch() for reading the file from cache? It didn't work for me, so I am trying to use expo's FileSystem.readAsStringAsync instead.
– Yossi
Nov 13 '18 at 21:32
I started working on other projects and didn't finish this one, so I haven't implemented cache reading. My idea at that time was to download the image and store in the local storage. Then read from it if there's any valid image stored, otherwise fetch again. I think it might work.
– soutot
Nov 13 '18 at 23:52
Well, good luck :) fetch seemed to work for me for a while, and then stopped working. github.com/expo/firebase-storage-upload-example/issues/14
– Yossi
Nov 14 '18 at 5:39
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Brilliant, it works! BTW, I used github.com/dankogai/js-base64
– Yossi
Nov 13 '18 at 21:26