mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-23 14:49:16 +01:00
feat: deeplink-schema-match typescript
This commit is contained in:
parent
9ac475ce42
commit
1eb4833cf8
6 changed files with 90 additions and 67 deletions
|
@ -28,7 +28,7 @@ const _shareOpen = async (filePath: string) => {
|
|||
* Writes a file to fs, and triggers an OS sharing dialog, so user can decide where to put this file (share to cloud
|
||||
* or perhabs messaging app). Provided filename should be just a file name, NOT a path
|
||||
*/
|
||||
const writeFileAndExport = async function (filename: string, contents: string) {
|
||||
export const writeFileAndExport = async function (filename: string, contents: string) {
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
|
@ -75,7 +75,7 @@ const writeFileAndExport = async function (filename: string, contents: string) {
|
|||
/**
|
||||
* Opens & reads *.psbt files, and returns base64 psbt. FALSE if something went wrong (wont throw).
|
||||
*/
|
||||
const openSignedTransaction = async function (): Promise<string | boolean> {
|
||||
export const openSignedTransaction = async function (): Promise<string | boolean> {
|
||||
try {
|
||||
const res = await DocumentPicker.pickSingle({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
|
@ -106,7 +106,7 @@ const _readPsbtFileIntoBase64 = async function (uri: string): Promise<string> {
|
|||
}
|
||||
};
|
||||
|
||||
const showImagePickerAndReadImage = () => {
|
||||
export const showImagePickerAndReadImage = () => {
|
||||
return new Promise((resolve, reject) =>
|
||||
launchImageLibrary(
|
||||
{
|
||||
|
@ -134,7 +134,7 @@ const showImagePickerAndReadImage = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const showFilePickerAndReadFile = async function (): Promise<{ data: string | false; uri: string | false }> {
|
||||
export const showFilePickerAndReadFile = async function (): Promise<{ data: string | false; uri: string | false }> {
|
||||
try {
|
||||
const res = await DocumentPicker.pickSingle({
|
||||
copyTo: 'cachesDirectory',
|
||||
|
@ -194,18 +194,13 @@ const showFilePickerAndReadFile = async function (): Promise<{ data: string | fa
|
|||
}
|
||||
};
|
||||
|
||||
const readFileOutsideSandbox = (filePath: string) => {
|
||||
export const readFileOutsideSandbox = (filePath: string) => {
|
||||
if (Platform.OS === 'ios') {
|
||||
return readFile(filePath);
|
||||
} else if (Platform.OS === 'android') {
|
||||
return RNFS.readFile(filePath);
|
||||
} else {
|
||||
presentAlert({ message: 'Not implemented for this platform' });
|
||||
throw new Error('Not implemented for this platform');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.writeFileAndExport = writeFileAndExport;
|
||||
module.exports.openSignedTransaction = openSignedTransaction;
|
||||
module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile;
|
||||
module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage;
|
||||
module.exports.readFileOutsideSandbox = readFileOutsideSandbox;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { NativeModules } from 'react-native';
|
|||
|
||||
const { BwFileAccess } = NativeModules;
|
||||
|
||||
export function readFile(filePath) {
|
||||
export function readFile(filePath: string): Promise<string> {
|
||||
return BwFileAccess.readFileContent(filePath);
|
||||
}
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
"title": "React Native Bw File Access",
|
||||
"version": "1.0.0",
|
||||
"description": "TODO",
|
||||
"main": "index.js",
|
||||
"main": "index.ts",
|
||||
"homepage": "https://github.com/setavenger/react-native-bw-file-access",
|
||||
"files": [
|
||||
"README.md",
|
||||
"android",
|
||||
"index.js",
|
||||
"index.ts",
|
||||
"ios",
|
||||
"react-native-bw-file-access.podspec"
|
||||
],
|
||||
|
|
|
@ -10,7 +10,7 @@ export default class Azteco {
|
|||
*
|
||||
* @returns {Promise<boolean>} Successfully redeemed or not. This method does not throw exceptions
|
||||
*/
|
||||
static async redeem(voucher, address) {
|
||||
static async redeem(voucher: string, address: string): Promise<boolean> {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://azte.co/',
|
||||
});
|
||||
|
@ -24,14 +24,19 @@ export default class Azteco {
|
|||
}
|
||||
}
|
||||
|
||||
static isRedeemUrl(u) {
|
||||
static isRedeemUrl(u: string): boolean {
|
||||
return u.startsWith('https://azte.co');
|
||||
}
|
||||
|
||||
static getParamsFromUrl(u) {
|
||||
static getParamsFromUrl(u: string) {
|
||||
const urlObject = URL.parse(u, true); // eslint-disable-line n/no-deprecated-api
|
||||
|
||||
if (urlObject.query.code) {
|
||||
// check if code is a string
|
||||
if (typeof urlObject.query.code !== 'string') {
|
||||
throw new Error('Invalid URL');
|
||||
}
|
||||
|
||||
// newer format of the url
|
||||
return {
|
||||
uri: u,
|
|
@ -1,18 +1,30 @@
|
|||
import { LightningCustodianWallet, WatchOnlyWallet } from './';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import bip21, { TOptions } from 'bip21';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import URL from 'url';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
import Lnurl from './lnurl';
|
||||
import Azteco from './azteco';
|
||||
import { readFileOutsideSandbox } from '../blue_modules/fs';
|
||||
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const bip21 = require('bip21');
|
||||
import { readFileOutsideSandbox } from '../blue_modules/fs';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
import { LightningCustodianWallet, WatchOnlyWallet } from './';
|
||||
import Azteco from './azteco';
|
||||
import Lnurl from './lnurl';
|
||||
import type { TWallet } from './wallets/types';
|
||||
|
||||
const BlueApp = require('../BlueApp');
|
||||
const AppStorage = BlueApp.AppStorage;
|
||||
|
||||
type TCompletionHandlerParams = [string, object];
|
||||
type TContext = {
|
||||
wallets: TWallet[];
|
||||
saveToDisk: () => void;
|
||||
addWallet: (wallet: TWallet) => void;
|
||||
setSharedCosigner: (cosigner: string) => void;
|
||||
};
|
||||
|
||||
type TBothBitcoinAndLightning = { bitcoin: string; lndInvoice: string } | undefined;
|
||||
|
||||
class DeeplinkSchemaMatch {
|
||||
static hasSchema(schemaString) {
|
||||
static hasSchema(schemaString: string): boolean {
|
||||
if (typeof schemaString !== 'string' || schemaString.length <= 0) return false;
|
||||
const lowercaseString = schemaString.trim().toLowerCase();
|
||||
return (
|
||||
|
@ -33,9 +45,9 @@ class DeeplinkSchemaMatch {
|
|||
* @param completionHandler {function} Callback that returns [string, params: object]
|
||||
*/
|
||||
static navigationRouteFor(
|
||||
event,
|
||||
completionHandler,
|
||||
context = { wallets: [], saveToDisk: () => {}, addWallet: () => {}, setSharedCosigner: () => {} },
|
||||
event: { url: string },
|
||||
completionHandler: (args: TCompletionHandlerParams) => void,
|
||||
context: TContext = { wallets: [], saveToDisk: () => {}, addWallet: () => {}, setSharedCosigner: () => {} },
|
||||
) {
|
||||
if (event.url === null) {
|
||||
return;
|
||||
|
@ -121,7 +133,7 @@ class DeeplinkSchemaMatch {
|
|||
})
|
||||
.catch(e => console.warn(e));
|
||||
}
|
||||
let isBothBitcoinAndLightning;
|
||||
let isBothBitcoinAndLightning: TBothBitcoinAndLightning;
|
||||
try {
|
||||
isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(event.url);
|
||||
} catch (e) {
|
||||
|
@ -131,7 +143,7 @@ class DeeplinkSchemaMatch {
|
|||
completionHandler([
|
||||
'SelectWallet',
|
||||
{
|
||||
onWalletSelect: (wallet, { navigation }) => {
|
||||
onWalletSelect: (wallet: TWallet, { navigation }: any) => {
|
||||
navigation.pop(); // close select wallet screen
|
||||
navigation.navigate(...DeeplinkSchemaMatch.isBothBitcoinAndLightningOnWalletSelect(wallet, isBothBitcoinAndLightning));
|
||||
},
|
||||
|
@ -293,7 +305,7 @@ class DeeplinkSchemaMatch {
|
|||
* @param url {string}
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
static getServerFromSetElectrumServerAction(url) {
|
||||
static getServerFromSetElectrumServerAction(url: string): string | false {
|
||||
if (!url.startsWith('bluewallet:setelectrumserver') && !url.startsWith('setelectrumserver')) return false;
|
||||
const splt = url.split('server=');
|
||||
if (splt[1]) return decodeURIComponent(splt[1]);
|
||||
|
@ -307,42 +319,42 @@ class DeeplinkSchemaMatch {
|
|||
* @param url {string}
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
static getUrlFromSetLndhubUrlAction(url) {
|
||||
static getUrlFromSetLndhubUrlAction(url: string): string | false {
|
||||
if (!url.startsWith('bluewallet:setlndhuburl') && !url.startsWith('setlndhuburl')) return false;
|
||||
const splt = url.split('url=');
|
||||
if (splt[1]) return decodeURIComponent(splt[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
static isTXNFile(filePath) {
|
||||
static isTXNFile(filePath: string): boolean {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('.txn')
|
||||
);
|
||||
}
|
||||
|
||||
static isPossiblySignedPSBTFile(filePath) {
|
||||
static isPossiblySignedPSBTFile(filePath: string): boolean {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('-signed.psbt')
|
||||
);
|
||||
}
|
||||
|
||||
static isPossiblyPSBTFile(filePath) {
|
||||
static isPossiblyPSBTFile(filePath: string): boolean {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('.psbt')
|
||||
);
|
||||
}
|
||||
|
||||
static isPossiblyCosignerFile(filePath) {
|
||||
static isPossiblyCosignerFile(filePath: string): boolean {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('.bwcosigner')
|
||||
);
|
||||
}
|
||||
|
||||
static isBothBitcoinAndLightningOnWalletSelect(wallet, uri) {
|
||||
static isBothBitcoinAndLightningOnWalletSelect(wallet: TWallet, uri: any): TCompletionHandlerParams {
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
return [
|
||||
'SendDetailsRoot',
|
||||
|
@ -354,7 +366,7 @@ class DeeplinkSchemaMatch {
|
|||
},
|
||||
},
|
||||
];
|
||||
} else if (wallet.chain === Chain.OFFCHAIN) {
|
||||
} else {
|
||||
return [
|
||||
'ScanLndInvoiceRoot',
|
||||
{
|
||||
|
@ -368,7 +380,7 @@ class DeeplinkSchemaMatch {
|
|||
}
|
||||
}
|
||||
|
||||
static isBitcoinAddress(address) {
|
||||
static isBitcoinAddress(address: string): boolean {
|
||||
address = address.replace('://', ':').replace('bitcoin:', '').replace('BITCOIN:', '').replace('bitcoin=', '').split('?')[0];
|
||||
let isValidBitcoinAddress = false;
|
||||
try {
|
||||
|
@ -380,7 +392,7 @@ class DeeplinkSchemaMatch {
|
|||
return isValidBitcoinAddress;
|
||||
}
|
||||
|
||||
static isLightningInvoice(invoice) {
|
||||
static isLightningInvoice(invoice: string): boolean {
|
||||
let isValidLightningInvoice = false;
|
||||
if (
|
||||
invoice.toLowerCase().startsWith('lightning:lnb') ||
|
||||
|
@ -392,15 +404,15 @@ class DeeplinkSchemaMatch {
|
|||
return isValidLightningInvoice;
|
||||
}
|
||||
|
||||
static isLnUrl(text) {
|
||||
static isLnUrl(text: string): boolean {
|
||||
return Lnurl.isLnurl(text);
|
||||
}
|
||||
|
||||
static isWidgetAction(text) {
|
||||
static isWidgetAction(text: string): boolean {
|
||||
return text.startsWith('widget?action=');
|
||||
}
|
||||
|
||||
static hasNeededJsonKeysForMultiSigSharing(str) {
|
||||
static hasNeededJsonKeysForMultiSigSharing(str: string): boolean {
|
||||
let obj;
|
||||
|
||||
// Check if it's a valid JSON
|
||||
|
@ -414,11 +426,11 @@ class DeeplinkSchemaMatch {
|
|||
return typeof obj.xfp === 'string' && typeof obj.xpub === 'string' && typeof obj.path === 'string';
|
||||
}
|
||||
|
||||
static isBothBitcoinAndLightning(url) {
|
||||
static isBothBitcoinAndLightning(url: string): TBothBitcoinAndLightning {
|
||||
if (url.includes('lightning') && (url.includes('bitcoin') || url.includes('BITCOIN'))) {
|
||||
const txInfo = url.split(/(bitcoin:\/\/|BITCOIN:\/\/|bitcoin:|BITCOIN:|lightning:|lightning=|bitcoin=)+/);
|
||||
let btc;
|
||||
let lndInvoice;
|
||||
let btc: string | false = false;
|
||||
let lndInvoice: string | false = false;
|
||||
for (const [index, value] of txInfo.entries()) {
|
||||
try {
|
||||
// Inside try-catch. We dont wan't to crash in case of an out-of-bounds error.
|
||||
|
@ -450,8 +462,10 @@ class DeeplinkSchemaMatch {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
static bip21decode(uri) {
|
||||
if (!uri) return {};
|
||||
static bip21decode(uri?: string) {
|
||||
if (!uri) {
|
||||
throw new Error('No URI provided');
|
||||
}
|
||||
let replacedUri = uri;
|
||||
for (const replaceMe of ['BITCOIN://', 'bitcoin://', 'BITCOIN:']) {
|
||||
replacedUri = replacedUri.replace(replaceMe, 'bitcoin:');
|
||||
|
@ -460,37 +474,34 @@ class DeeplinkSchemaMatch {
|
|||
return bip21.decode(replacedUri);
|
||||
}
|
||||
|
||||
static bip21encode() {
|
||||
const argumentsArray = Array.from(arguments);
|
||||
for (const argument of argumentsArray) {
|
||||
if (String(argument.label).replace(' ', '').length === 0) {
|
||||
delete argument.label;
|
||||
static bip21encode(address: string, options: TOptions): string {
|
||||
for (const key in options) {
|
||||
if (key === 'label' && String(options[key]).replace(' ', '').length === 0) {
|
||||
delete options[key];
|
||||
}
|
||||
if (!(Number(argument.amount) > 0)) {
|
||||
delete argument.amount;
|
||||
if (key === 'amount' && !(Number(options[key]) > 0)) {
|
||||
delete options[key];
|
||||
}
|
||||
}
|
||||
return bip21.encode.apply(bip21, argumentsArray);
|
||||
return bip21.encode(address, options);
|
||||
}
|
||||
|
||||
static decodeBitcoinUri(uri) {
|
||||
let amount = '';
|
||||
let parsedBitcoinUri = null;
|
||||
static decodeBitcoinUri(uri: string) {
|
||||
let amount;
|
||||
let address = uri || '';
|
||||
let memo = '';
|
||||
let payjoinUrl = '';
|
||||
try {
|
||||
parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri);
|
||||
address = 'address' in parsedBitcoinUri ? parsedBitcoinUri.address : address;
|
||||
const parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri);
|
||||
address = parsedBitcoinUri.address ? parsedBitcoinUri.address.toString() : address;
|
||||
if ('options' in parsedBitcoinUri) {
|
||||
if ('amount' in parsedBitcoinUri.options) {
|
||||
amount = parsedBitcoinUri.options.amount.toString();
|
||||
amount = parsedBitcoinUri.options.amount;
|
||||
if (parsedBitcoinUri.options.amount) {
|
||||
amount = Number(parsedBitcoinUri.options.amount);
|
||||
}
|
||||
if ('label' in parsedBitcoinUri.options) {
|
||||
memo = parsedBitcoinUri.options.label || memo;
|
||||
if (parsedBitcoinUri.options.label) {
|
||||
memo = parsedBitcoinUri.options.label;
|
||||
}
|
||||
if ('pj' in parsedBitcoinUri.options) {
|
||||
if (parsedBitcoinUri.options.pj) {
|
||||
payjoinUrl = parsedBitcoinUri.options.pj;
|
||||
}
|
||||
}
|
12
typings/bip21.d.ts
vendored
Normal file
12
typings/bip21.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
declare module 'bip21' {
|
||||
export type TOptions =
|
||||
| {
|
||||
amount?: number;
|
||||
label?: string;
|
||||
pj?: string;
|
||||
}
|
||||
| { [key: string]: string };
|
||||
|
||||
export function decode(uri: string, urnScheme?: string): { address: string; options: TOptions };
|
||||
export function encode(address: string, options?: TOptions, urnScheme?: string): string;
|
||||
}
|
Loading…
Add table
Reference in a new issue