2020-11-23 14:55:32 +01:00
|
|
|
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';
|
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';
|
2021-05-29 07:57:40 +02:00
|
|
|
import { isDesktop } from '../blue_modules/environment';
|
2021-10-04 08:02:33 +02:00
|
|
|
import alert from '../components/Alert';
|
2023-11-12 15:41:51 +01:00
|
|
|
import { readFile } from './react-native-bw-file-access';
|
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
|
|
|
|
2021-07-10 04:38:09 +02:00
|
|
|
const writeFileAndExportToAndroidDestionation = async ({ filename, contents, destinationLocalizedString, destination }) => {
|
|
|
|
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,
|
|
|
|
});
|
2023-07-09 19:12:05 +02:00
|
|
|
if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) {
|
2021-07-10 04:38:09 +02:00
|
|
|
const filePath = destination + `/${filename}`;
|
|
|
|
try {
|
|
|
|
await RNFS.writeFile(filePath, contents);
|
|
|
|
alert(loc.formatString(loc._.file_saved, { filePath: filename, destination: destinationLocalizedString }));
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e);
|
|
|
|
alert(e.message);
|
|
|
|
}
|
|
|
|
} 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
|
|
|
const writeFileAndExport = async function (filename, contents) {
|
|
|
|
if (Platform.OS === 'ios') {
|
|
|
|
const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`;
|
|
|
|
await RNFS.writeFile(filePath, contents);
|
2021-09-11 19:46:01 +02:00
|
|
|
await Share.open({
|
2020-10-05 23:25:14 +02:00
|
|
|
url: 'file://' + filePath,
|
2021-05-29 07:57:40 +02:00
|
|
|
saveToFiles: isDesktop,
|
2020-10-05 23:25:14 +02:00
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.log(error);
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
RNFS.unlink(filePath);
|
|
|
|
});
|
|
|
|
} else if (Platform.OS === 'android') {
|
2021-09-11 19:46:01 +02:00
|
|
|
await writeFileAndExportToAndroidDestionation({
|
|
|
|
filename,
|
|
|
|
contents,
|
|
|
|
destinationLocalizedString: loc._.downloads_folder,
|
|
|
|
destination: RNFS.DownloadDirectoryPath,
|
|
|
|
});
|
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 {
|
2021-12-27 22:17:02 +01:00
|
|
|
const res = await DocumentPicker.pickSingle({
|
2021-08-13 22:50:19 +02:00
|
|
|
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles],
|
2020-10-05 23:25:14 +02:00
|
|
|
});
|
|
|
|
|
2020-10-07 16:54:22 +02:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2020-10-07 16:54:22 +02:00
|
|
|
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,
|
2021-08-02 16:03:38 +02:00
|
|
|
maxHeight: 800,
|
|
|
|
maxWidth: 600,
|
2021-08-25 15:53:33 +02:00
|
|
|
selectionLimit: 1,
|
2020-12-11 04:28:54 +01:00
|
|
|
},
|
|
|
|
response => {
|
2021-09-28 23:58:53 +02:00
|
|
|
if (!response.didCancel) {
|
|
|
|
const asset = response.assets[0];
|
|
|
|
if (asset.uri) {
|
|
|
|
const uri = asset.uri.toString().replace('file://', '');
|
|
|
|
LocalQRCode.decode(uri, (error, result) => {
|
|
|
|
if (!error) {
|
|
|
|
resolve(result);
|
|
|
|
} else {
|
|
|
|
reject(new Error(loc.send.qr_error_no_qrcode));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-12-11 04:28:54 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
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) {
|
2021-04-27 14:50:31 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-10-07 16:54:22 +02:00
|
|
|
const showFilePickerAndReadFile = async function () {
|
|
|
|
try {
|
2021-12-27 22:17:02 +01:00
|
|
|
const res = await DocumentPicker.pickSingle({
|
2020-10-07 16:54:22 +02:00
|
|
|
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,
|
|
|
|
]
|
2020-10-07 16:54:22 +02:00
|
|
|
: [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
|
|
|
|
|
2020-10-07 16:54:22 +02:00
|
|
|
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) };
|
2021-02-18 14:59:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (res?.type === DocumentPicker.types.images || res?.type?.startsWith('image/')) {
|
|
|
|
return new Promise(resolve => {
|
2023-07-25 15:50:04 +02:00
|
|
|
const uri2 = res.uri.toString().replace('file://', '');
|
|
|
|
LocalQRCode.decode(decodeURI(uri2), (error, result) => {
|
2021-02-18 14:59:45 +01:00
|
|
|
if (!error) {
|
|
|
|
resolve({ data: result, uri: decodeURI(res.uri) });
|
|
|
|
} else {
|
|
|
|
resolve({ data: false, uri: false });
|
|
|
|
}
|
2020-11-10 19:42:15 +01:00
|
|
|
});
|
2021-02-18 14:59:45 +01:00
|
|
|
});
|
2020-10-07 16:54:22 +02:00
|
|
|
}
|
2021-02-18 14:59:45 +01:00
|
|
|
|
|
|
|
file = await RNFS.readFile(uri);
|
|
|
|
return { data: file, uri: decodeURI(res.uri) };
|
2020-10-07 16:54:22 +02:00
|
|
|
} catch (err) {
|
2023-10-28 21:18:17 +02:00
|
|
|
if (!DocumentPicker.isCancel(err)) {
|
|
|
|
alert(err.message);
|
|
|
|
}
|
2020-10-13 18:58:06 +02:00
|
|
|
return { data: false, uri: false };
|
2020-10-07 16:54:22 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-11-12 15:41:51 +01:00
|
|
|
// todo expand with other platforms if necessary
|
|
|
|
const readFileOutsideSandbox = filePath => {
|
|
|
|
if (Platform.OS === 'ios') {
|
|
|
|
return readFile(filePath);
|
|
|
|
} else {
|
|
|
|
return RNFS.readFile(filePath);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-05 23:25:14 +02:00
|
|
|
module.exports.writeFileAndExport = writeFileAndExport;
|
|
|
|
module.exports.openSignedTransaction = openSignedTransaction;
|
2020-10-07 16:54:22 +02:00
|
|
|
module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile;
|
2020-12-11 04:28:54 +01:00
|
|
|
module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage;
|
|
|
|
module.exports.takePhotoWithImagePickerAndReadPhoto = takePhotoWithImagePickerAndReadPhoto;
|
2023-11-12 15:41:51 +01:00
|
|
|
module.exports.readFileOutsideSandbox = readFileOutsideSandbox;
|