Firebase 存储:字符串与格式 base64 不匹配:找到无效字符。仅当调试关闭时
Firebase Storage: string does not match format base64: invalid character found. Only when debug is off
我正在尝试将图像文件上传到 firebase 存储,保存下载 URL,并在上传完成后加载它。当我 运行 远程调试 js 的应用程序工作正常。当我关闭调试模式时,它会停止处理无效格式异常。当我在真实设备中 运行(iOS 和 Android)
时,也会发生同样的情况
来自 React Native Image Picker 的 base64 响应数据似乎是正确的
这是我的代码
...
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
}
知道如何解决这个问题吗?
提前致谢。
经过一些研究和调试,我找到了问题的原因和解决方案。
为什么会这样?
Firebase 使用atob
方法对putstring
方法发送的base64 字符串进行解码。
但是,由于JavaScriptCore默认不支持atob
和btoa
,无法转换base64字符串,所以会触发this异常。
当我们运行 应用程序处于调试javascript 远程模式时,所有javascript 代码在chrome 环境下都是运行,其中atob
和 btoa
得到支持。这就是为什么代码在调试打开时工作而在关闭时不工作的原因。
如何解决?
要在 React Native 中处理 atob
和 btoa
,我们应该编写自己的 encode/decode 方法,或者安装一个库来为我们处理它。
在我的例子中,我更喜欢安装 base-64
lib
但是这里有一个 encode/decode 脚本的例子:
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const Base64 = {
btoa: (input:string = '') => {
let str = input;
let output = '';
for (let block = 0, charCode, i = 0, map = chars;
str.charAt(i | 0) || (map = '=', i % 1);
output += map.charAt(63 & block >> 8 - i % 1 * 8)) {
charCode = str.charCodeAt(i += 3/4);
if (charCode > 0xFF) {
throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
}
block = block << 8 | charCode;
}
return output;
},
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;
用法:
import Base64 from '[path to your script]';
const stringToEncode = 'xxxx';
Base64.btoa(scriptToEncode);
const stringToDecode = 'xxxx';
Base64.atob(stringToDecode);
选择使用自定义脚本或库后,现在我们必须将以下代码添加到 index.js
文件中:
import { decode, encode } from 'base-64';
if (!global.btoa) {
global.btoa = encode;
}
if (!global.atob) {
global.atob = decode;
}
AppRegistry.registerComponent(appName, () => App);
这将全局声明 atob
和 btoa
。因此,无论何时在应用程序中调用这些函数,React Native 都会使用全局范围来处理它,然后触发 base-64
lib.
中的 encode
和 decode
方法。
所以这是 Base64 问题的解决方案。
然而,在解决此问题后,我在尝试上传更大的图像时发现了另一个问题 Firebase Storage: Max retry time for operation exceed. Please try again
。似乎 firebase
对 support to React Native uploads, as this issue 建议有一些限制。
我相信 react-native-firebase
可能不会在这方面遇到困难,因为它已经准备好 [=71=] 本机,而不是像 firebase
那样使用网络环境。我还没有测试它来确认,但看起来这将是处理它的最佳方法。
希望这对其他人有所帮助。
问题现在已使用 fetch() API 解决。返回的承诺可以转换为 blob,您可以将其上传到 firebase/storage
这是一个例子
let storageRef = storage().ref();
let imageName = data.name + "image";
let imagesRef = storageRef.child(`images/${imageName}`);
const response = await fetch(image);
const blob = await response.blob(); // Here is the trick
imagesRef
.put(blob)
.then((snapshot) => {
console.log("uploaded an image.");
})
.catch((err) => console.log(err));
我正在尝试将图像文件上传到 firebase 存储,保存下载 URL,并在上传完成后加载它。当我 运行 远程调试 js 的应用程序工作正常。当我关闭调试模式时,它会停止处理无效格式异常。当我在真实设备中 运行(iOS 和 Android)
时,也会发生同样的情况来自 React Native Image Picker 的 base64 响应数据似乎是正确的
这是我的代码
...
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
}
知道如何解决这个问题吗?
提前致谢。
经过一些研究和调试,我找到了问题的原因和解决方案。
为什么会这样?
Firebase 使用atob
方法对putstring
方法发送的base64 字符串进行解码。
但是,由于JavaScriptCore默认不支持atob
和btoa
,无法转换base64字符串,所以会触发this异常。
当我们运行 应用程序处于调试javascript 远程模式时,所有javascript 代码在chrome 环境下都是运行,其中atob
和 btoa
得到支持。这就是为什么代码在调试打开时工作而在关闭时不工作的原因。
如何解决?
要在 React Native 中处理 atob
和 btoa
,我们应该编写自己的 encode/decode 方法,或者安装一个库来为我们处理它。
在我的例子中,我更喜欢安装 base-64
lib
但是这里有一个 encode/decode 脚本的例子:
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const Base64 = {
btoa: (input:string = '') => {
let str = input;
let output = '';
for (let block = 0, charCode, i = 0, map = chars;
str.charAt(i | 0) || (map = '=', i % 1);
output += map.charAt(63 & block >> 8 - i % 1 * 8)) {
charCode = str.charCodeAt(i += 3/4);
if (charCode > 0xFF) {
throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
}
block = block << 8 | charCode;
}
return output;
},
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;
用法:
import Base64 from '[path to your script]';
const stringToEncode = 'xxxx';
Base64.btoa(scriptToEncode);
const stringToDecode = 'xxxx';
Base64.atob(stringToDecode);
选择使用自定义脚本或库后,现在我们必须将以下代码添加到 index.js
文件中:
import { decode, encode } from 'base-64';
if (!global.btoa) {
global.btoa = encode;
}
if (!global.atob) {
global.atob = decode;
}
AppRegistry.registerComponent(appName, () => App);
这将全局声明 atob
和 btoa
。因此,无论何时在应用程序中调用这些函数,React Native 都会使用全局范围来处理它,然后触发 base-64
lib.
encode
和 decode
方法。
所以这是 Base64 问题的解决方案。
然而,在解决此问题后,我在尝试上传更大的图像时发现了另一个问题 Firebase Storage: Max retry time for operation exceed. Please try again
。似乎 firebase
对 support to React Native uploads, as this issue 建议有一些限制。
我相信 react-native-firebase
可能不会在这方面遇到困难,因为它已经准备好 [=71=] 本机,而不是像 firebase
那样使用网络环境。我还没有测试它来确认,但看起来这将是处理它的最佳方法。
希望这对其他人有所帮助。
问题现在已使用 fetch() API 解决。返回的承诺可以转换为 blob,您可以将其上传到 firebase/storage
这是一个例子
let storageRef = storage().ref();
let imageName = data.name + "image";
let imagesRef = storageRef.child(`images/${imageName}`);
const response = await fetch(image);
const blob = await response.blob(); // Here is the trick
imagesRef
.put(blob)
.then((snapshot) => {
console.log("uploaded an image.");
})
.catch((err) => console.log(err));