Browse Source

FIX: capitalized deeplink (closes #1005)

master
Overtorment 5 years ago
parent
commit
d552669dd2
  1. 2
      App.js
  2. 16
      class/deeplink-schema-match.js
  3. 8
      screen/receive/details.js
  4. 23
      screen/send/details.js
  5. 2
      screen/wallets/list.js
  6. 45
      tests/unit/deeplink-schema-match.test.js

2
App.js

@ -10,7 +10,7 @@ import { Chain } from './models/bitcoinUnits';
import QuickActions from 'react-native-quick-actions';
import * as Sentry from '@sentry/react-native';
import OnAppLaunch from './class/onAppLaunch';
import DeeplinkSchemaMatch from './class/deeplinkSchemaMatch';
import DeeplinkSchemaMatch from './class/deeplink-schema-match';
import BitcoinBIP70TransactionDecode from './bip70/bip70';
const A = require('./analytics');

16
class/deeplinkSchemaMatch.js → class/deeplink-schema-match.js

@ -5,6 +5,7 @@ import RNFS from 'react-native-fs';
import url from 'url';
import { Chain } from '../models/bitcoinUnits';
const bitcoin = require('bitcoinjs-lib');
const bip21 = require('bip21');
const BlueApp: AppStorage = require('../BlueApp');
class DeeplinkSchemaMatch {
@ -194,6 +195,7 @@ class DeeplinkSchemaMatch {
static isBitcoinAddress(address) {
address = address
.replace('bitcoin:', '')
.replace('BITCOIN:', '')
.replace('bitcoin=', '')
.split('?')[0];
let isValidBitcoinAddress = false;
@ -228,14 +230,14 @@ class DeeplinkSchemaMatch {
}
static isBothBitcoinAndLightning(url) {
if (url.includes('lightning') && url.includes('bitcoin')) {
const txInfo = url.split(/(bitcoin:|lightning:|lightning=|bitcoin=)+/);
if (url.includes('lightning') && (url.includes('bitcoin') || url.includes('BITCOIN'))) {
const txInfo = url.split(/(bitcoin:|BITCOIN:|lightning:|lightning=|bitcoin=)+/);
let bitcoin;
let lndInvoice;
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.
if (value.startsWith('bitcoin')) {
if (value.startsWith('bitcoin') || value.startsWith('BITCOIN')) {
bitcoin = `bitcoin:${txInfo[index + 1]}`;
if (!DeeplinkSchemaMatch.isBitcoinAddress(bitcoin)) {
bitcoin = false;
@ -261,6 +263,14 @@ class DeeplinkSchemaMatch {
}
return undefined;
}
static bip21decode(uri) {
return bip21.decode(uri.replace('BITCOIN:', 'bitcoin:'));
}
static bip21encode() {
return bip21.encode.apply(bip21, arguments);
}
}
export default DeeplinkSchemaMatch;

8
screen/receive/details.js

@ -2,7 +2,6 @@ import React, { useEffect, useState, useCallback } from 'react';
import { View, InteractionManager, Platform, TextInput, KeyboardAvoidingView, Keyboard, StyleSheet, ScrollView } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
import bip21 from 'bip21';
import {
BlueLoading,
SafeBlueArea,
@ -21,6 +20,7 @@ import Share from 'react-native-share';
import { Chain, BitcoinUnit } from '../../models/bitcoinUnits';
import Modal from 'react-native-modal';
import HandoffSettings from '../../class/handoff';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import Handoff from 'react-native-handoff';
/** @type {AppStorage} */
const BlueApp = require('../../BlueApp');
@ -75,7 +75,7 @@ const ReceiveDetails = () => {
setAddress(wallet.getAddress());
}
InteractionManager.runAfterInteractions(async () => {
const bip21encoded = bip21.encode(address);
const bip21encoded = DeeplinkSchemaMatch.bip21encode(address);
setBip21encoded(bip21encoded);
});
}, [wallet]);
@ -116,7 +116,7 @@ const ReceiveDetails = () => {
const createCustomAmountAddress = () => {
setIsCustom(true);
setIsCustomModalVisible(false);
setBip21encoded(bip21.encode(address, { amount: customAmount, label: customLabel }));
setBip21encoded(DeeplinkSchemaMatch.bip21encode(address, { amount: customAmount, label: customLabel }));
};
const clearCustomAmount = () => {
@ -124,7 +124,7 @@ const ReceiveDetails = () => {
setIsCustomModalVisible(false);
setCustomAmount('');
setCustomLabel('');
setBip21encoded(bip21.encode(address));
setBip21encoded(DeeplinkSchemaMatch.bip21encode(address));
};
const renderCustomAmountModal = () => {

23
screen/send/details.js

@ -35,18 +35,16 @@ import Modal from 'react-native-modal';
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
import BitcoinBIP70TransactionDecode from '../../bip70/bip70';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { HDSegwitBech32Wallet, LightningCustodianWallet, WatchOnlyWallet } from '../../class';
import { AppStorage, HDSegwitBech32Wallet, LightningCustodianWallet, WatchOnlyWallet } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { BitcoinTransaction } from '../../models/bitcoinTransactionInfo';
import DocumentPicker from 'react-native-document-picker';
import RNFS from 'react-native-fs';
import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
const bitcoin = require('bitcoinjs-lib');
const bip21 = require('bip21');
let BigNumber = require('bignumber.js');
const { width } = Dimensions.get('window');
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let BlueApp: AppStorage = require('../../BlueApp');
let loc = require('../../loc');
const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/;
@ -73,6 +71,7 @@ export default class SendDetails extends Component {
if (props.navigation.state.params) fromAddress = props.navigation.state.params.fromAddress;
let fromSecret;
if (props.navigation.state.params) fromSecret = props.navigation.state.params.fromSecret;
/** @type {LegacyWallet} */
let fromWallet = null;
if (props.navigation.state.params) fromWallet = props.navigation.state.params.fromWallet;
@ -136,12 +135,10 @@ export default class SendDetails extends Component {
bip70TransactionExpiration: bip70.bip70TransactionExpiration,
});
} else {
console.warn('2');
let recipients = this.state.addresses;
const dataWithoutSchema = data.replace('bitcoin:', '');
if (
btcAddressRx.test(dataWithoutSchema) ||
((dataWithoutSchema.indexOf('bc1') === 0 || dataWithoutSchema.indexOf('BC1') === 0) && dataWithoutSchema.indexOf('?') === -1)
) {
const dataWithoutSchema = data.replace('bitcoin:', '').replace('BITCOIN:', '');
if (this.state.fromWallet.isAddressValid(dataWithoutSchema)) {
recipients[[this.state.recipientsScrollIndex]].address = dataWithoutSchema;
this.setState({
address: recipients,
@ -155,12 +152,12 @@ export default class SendDetails extends Component {
if (!data.toLowerCase().startsWith('bitcoin:')) {
data = `bitcoin:${data}`;
}
const decoded = bip21.decode(data);
const decoded = DeeplinkSchemaMatch.bip21decode(data);
address = decoded.address;
options = decoded.options;
} catch (error) {
data = data.replace(/(amount)=([^&]+)/g, '').replace(/(amount)=([^&]+)&/g, '');
const decoded = bip21.decode(data);
const decoded = DeeplinkSchemaMatch.bip21decode(data);
decoded.options.amount = 0;
address = decoded.address;
options = decoded.options;
@ -277,7 +274,7 @@ export default class SendDetails extends Component {
let address = uri || '';
let memo = '';
try {
parsedBitcoinUri = bip21.decode(uri);
parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri);
address = parsedBitcoinUri.hasOwnProperty('address') ? parsedBitcoinUri.address : address;
if (parsedBitcoinUri.hasOwnProperty('options')) {
if (parsedBitcoinUri.options.hasOwnProperty('amount')) {

2
screen/wallets/list.js

@ -20,7 +20,7 @@ import { PlaceholderWallet } from '../../class';
import WalletImport from '../../class/walletImport';
import ViewPager from '@react-native-community/viewpager';
import ScanQRCode from '../send/ScanQRCode';
import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
let EV = require('../../events');
let A = require('../../analytics');
/** @type {AppStorage} */

45
tests/unit/deepLinkSchemaMatch.test.js → tests/unit/deeplink-schema-match.test.js

@ -1,5 +1,5 @@
/* global describe, it */
import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
const assert = require('assert');
describe('unit - DeepLinkSchemaMatch', function() {
@ -7,12 +7,18 @@ describe('unit - DeepLinkSchemaMatch', function() {
assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'));
assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo'));
assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7?amount=666&label=Yo'));
assert.ok(DeeplinkSchemaMatch.hasSchema('BITCOIN:BC1Q3RL0MKYK0ZRTXFMQN9WPCD3GNAZ00YV9YP0HXE'));
assert.ok(DeeplinkSchemaMatch.hasSchema('BITCOIN:BC1Q3RL0MKYK0ZRTXFMQN9WPCD3GNAZ00YV9YP0HXE?amount=666&label=Yo'));
});
it('isBitcoin Address', () => {
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'));
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK'));
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('bc1qykcp2x3djgdtdwelxn9z4j2y956npte0a4sref'));
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('BC1QYKCP2X3DJGDTDWELXN9Z4J2Y956NPTE0A4SREF'));
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('bitcoin:BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7'));
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('BITCOIN:BC1Q3RL0MKYK0ZRTXFMQN9WPCD3GNAZ00YV9YP0HXE'));
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('BITCOIN:BC1Q3RL0MKYK0ZRTXFMQN9WPCD3GNAZ00YV9YP0HXE?amount=666&label=Yo'));
});
it('isLighting Invoice', () => {
@ -26,7 +32,12 @@ describe('unit - DeepLinkSchemaMatch', function() {
it('isBoth Bitcoin & Invoice', () => {
assert.ok(
DeeplinkSchemaMatch.isBothBitcoinAndLightning(
'bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=lnbc1u1pwry044pp53xlmkghmzjzm3cljl6729cwwqz5hhnhevwfajpkln850n7clft4sdqlgfy4qv33ypmj7sj0f32rzvfqw3jhxaqcqzysxq97zvuq5zy8ge6q70prnvgwtade0g2k5h2r76ws7j2926xdjj2pjaq6q3r4awsxtm6k5prqcul73p3atveljkn6wxdkrcy69t6k5edhtc6q7lgpe4m5k4',
'bitcoin:BC1Q3RL0MKYK0ZRTXFMQN9WPCD3GNAZ00YV9YP0HXE?amount=0.000001&lightning=lnbc1u1pwry044pp53xlmkghmzjzm3cljl6729cwwqz5hhnhevwfajpkln850n7clft4sdqlgfy4qv33ypmj7sj0f32rzvfqw3jhxaqcqzysxq97zvuq5zy8ge6q70prnvgwtade0g2k5h2r76ws7j2926xdjj2pjaq6q3r4awsxtm6k5prqcul73p3atveljkn6wxdkrcy69t6k5edhtc6q7lgpe4m5k4',
),
);
assert.ok(
DeeplinkSchemaMatch.isBothBitcoinAndLightning(
'BITCOIN:12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG?amount=0.000001&lightning=lnbc1u1pwry044pp53xlmkghmzjzm3cljl6729cwwqz5hhnhevwfajpkln850n7clft4sdqlgfy4qv33ypmj7sj0f32rzvfqw3jhxaqcqzysxq97zvuq5zy8ge6q70prnvgwtade0g2k5h2r76ws7j2926xdjj2pjaq6q3r4awsxtm6k5prqcul73p3atveljkn6wxdkrcy69t6k5edhtc6q7lgpe4m5k4',
),
);
});
@ -55,4 +66,34 @@ describe('unit - DeepLinkSchemaMatch', function() {
});
});
});
it('decodes bip21', () => {
let decoded = DeeplinkSchemaMatch.bip21decode('bitcoin:1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH?amount=20.3&label=Foobar');
assert.deepStrictEqual(decoded, {
address: '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH',
options: {
amount: 20.3,
label: 'Foobar',
},
});
decoded = DeeplinkSchemaMatch.bip21decode('BITCOIN:1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH?amount=20.3&label=Foobar');
assert.deepStrictEqual(decoded, {
address: '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH',
options: {
amount: 20.3,
label: 'Foobar',
},
});
});
it('encodes bip21', () => {
let encoded = DeeplinkSchemaMatch.bip21encode('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH');
assert.strictEqual(encoded, 'bitcoin:1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH');
encoded = DeeplinkSchemaMatch.bip21encode('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', {
amount: 20.3,
label: 'Foobar',
});
assert.strictEqual(encoded, 'bitcoin:1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH?amount=20.3&label=Foobar');
});
});
Loading…
Cancel
Save