BlueWallet/blue_modules/fs.js

235 lines
7.7 KiB
JavaScript
Raw Normal View History

2020-10-05 23:25:14 +02:00
/* global alert */
import { Alert, Linking, PermissionsAndroid, Platform } from 'react-native';
2020-10-05 23:25:14 +02:00
import RNFS from 'react-native-fs';
import Share from 'react-native-share';
import loc from '../loc';
import DocumentPicker from 'react-native-document-picker';
import isCatalyst from 'react-native-is-catalyst';
2020-12-12 19:07:00 +01:00
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
2020-12-12 19:29:34 +01:00
import { presentCameraNotAuthorizedAlert } from '../class/camera';
2020-12-15 05:11:05 +01:00
import ActionSheet from '../screen/ActionSheet';
import BlueClipboard from './clipboard';
2020-11-10 19:42:15 +01:00
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
2020-10-05 23:25:14 +02:00
const writeFileAndExport = async function (filename, contents) {
if (Platform.OS === 'ios') {
const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`;
await RNFS.writeFile(filePath, contents);
Share.open({
url: 'file://' + filePath,
saveToFiles: isCatalyst,
2020-10-05 23:25:14 +02:00
})
.catch(error => {
console.log(error);
})
.finally(() => {
RNFS.unlink(filePath);
});
} else if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
title: loc.send.permission_storage_title,
message: loc.send.permission_storage_message,
buttonNeutral: loc.send.permission_storage_later,
buttonNegative: loc._.cancel,
buttonPositive: loc._.ok,
});
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('Storage Permission: Granted');
const filePath = RNFS.DownloadDirectoryPath + `/${filename}`;
try {
await RNFS.writeFile(filePath, contents);
alert(loc.formatString(loc._.file_saved, { filePath: filename }));
} catch (e) {
console.log(e);
alert(e.message);
}
2020-10-05 23:25:14 +02:00
} else {
console.log('Storage Permission: Denied');
Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [
{
text: loc.send.open_settings,
onPress: () => {
Linking.openSettings();
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
]);
2020-10-05 23:25:14 +02:00
}
}
};
/**
* Opens & reads *.psbt files, and returns base64 psbt. FALSE if something went wrong (wont throw).
*
* @returns {Promise<string|boolean>} Base64 PSBT
*/
const openSignedTransaction = async function () {
try {
const res = await DocumentPicker.pick({
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles],
});
return await _readPsbtFileIntoBase64(res.uri);
2020-10-05 23:25:14 +02:00
} catch (err) {
if (!DocumentPicker.isCancel(err)) {
alert(loc.send.details_no_signed_tx);
}
}
return false;
};
const _readPsbtFileIntoBase64 = async function (uri) {
const base64 = await RNFS.readFile(uri, 'base64');
const stringData = Buffer.from(base64, 'base64').toString(); // decode from base64
if (stringData.startsWith('psbt')) {
// file was binary, but outer code expects base64 psbt, so we return base64 we got from rn-fs;
// most likely produced by Electrum-desktop
return base64;
} else {
// file was a text file, having base64 psbt in there. so we basically have double base64encoded string
// thats why we are returning string that was decoded once;
// most likely produced by Coldcard
return stringData;
}
};
2020-12-11 04:28:54 +01:00
const showImagePickerAndReadImage = () => {
return new Promise((resolve, reject) =>
2020-12-12 19:07:00 +01:00
launchImageLibrary(
2020-12-11 04:28:54 +01:00
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
2020-12-11 04:28:54 +01:00
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
resolve(result);
} else {
reject(new Error(loc.send.qr_error_no_qrcode));
}
});
}
},
),
);
};
const takePhotoWithImagePickerAndReadPhoto = () => {
return new Promise((resolve, reject) =>
2020-12-12 19:07:00 +01:00
launchCamera(
2020-12-11 04:28:54 +01:00
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
2020-12-11 04:28:54 +01:00
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
resolve(result);
} else {
reject(new Error(loc.send.qr_error_no_qrcode));
}
});
} else if (response.error) {
presentCameraNotAuthorizedAlert(response.error);
}
},
),
);
};
const showFilePickerAndReadFile = async function () {
try {
const res = await DocumentPicker.pick({
type:
Platform.OS === 'ios'
2020-11-10 19:42:15 +01:00
? [
'io.bluewallet.psbt',
'io.bluewallet.psbt.txn',
'io.bluewallet.backup',
DocumentPicker.types.plainText,
'public.json',
DocumentPicker.types.images,
]
: [DocumentPicker.types.allFiles],
});
2020-11-04 16:38:49 +01:00
const uri = Platform.OS === 'ios' ? decodeURI(res.uri) : res.uri;
// ^^ some weird difference on how spaces in filenames are treated on ios and android
let file = false;
if (res.uri.toLowerCase().endsWith('.psbt')) {
// this is either binary file from ElectrumDesktop OR string file with base64 string in there
2020-11-04 16:38:49 +01:00
file = await _readPsbtFileIntoBase64(uri);
2020-11-10 19:42:15 +01:00
return { data: file, uri: decodeURI(res.uri) };
}
if (res?.type === DocumentPicker.types.images || res?.type?.startsWith('image/')) {
return new Promise(resolve => {
const uri = res.uri.toString().replace('file://', '');
LocalQRCode.decode(decodeURI(uri), (error, result) => {
if (!error) {
resolve({ data: result, uri: decodeURI(res.uri) });
} else {
resolve({ data: false, uri: false });
}
2020-11-10 19:42:15 +01:00
});
});
}
file = await RNFS.readFile(uri);
return { data: file, uri: decodeURI(res.uri) };
} catch (err) {
2020-10-13 18:58:06 +02:00
return { data: false, uri: false };
}
};
2020-12-15 05:11:05 +01:00
// Intended for macOS Catalina. Not for long press shortcut
const showActionSheet = async props => {
const isClipboardEmpty = (await BlueClipboard.getClipboardContent()).trim().length === 0;
2020-12-15 05:11:05 +01:00
let copyFromClipboardIndex;
const options = [loc._.cancel, loc.wallets.take_photo, loc.wallets.list_long_choose];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
copyFromClipboardIndex = options.length - 1;
}
options.push(loc.wallets.import_file);
const importFileButtonIndex = options.length - 1;
return new Promise(resolve =>
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0, anchor: props.anchor }, async buttonIndex => {
2020-12-15 05:11:05 +01:00
if (buttonIndex === 1) {
takePhotoWithImagePickerAndReadPhoto().then(resolve);
} else if (buttonIndex === 2) {
showImagePickerAndReadImage()
.then(resolve)
.catch(error => alert(error.message));
} else if (buttonIndex === copyFromClipboardIndex) {
const clipboard = await BlueClipboard.getClipboardContent();
2020-12-15 05:11:05 +01:00
resolve(clipboard);
} else if (importFileButtonIndex) {
const { data } = await showFilePickerAndReadFile();
if (data) {
resolve(data);
}
}
}),
);
};
2020-10-05 23:25:14 +02:00
module.exports.writeFileAndExport = writeFileAndExport;
module.exports.openSignedTransaction = openSignedTransaction;
module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile;
2020-12-11 04:28:54 +01:00
module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage;
module.exports.takePhotoWithImagePickerAndReadPhoto = takePhotoWithImagePickerAndReadPhoto;
2020-12-15 05:11:05 +01:00
module.exports.showActionSheet = showActionSheet;