lnurlpay: replace realm with async-storage.
also stop checking pending transactions, insted delete every lnurlpay thing older than 30 days (a check is done in 10% of the times the wallet is turned on).
This commit is contained in:
+1
-1
@@ -11,6 +11,7 @@
|
||||
"prettier": "prettier --check --write '**/*.ts*'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/async-storage": "^1.10.1",
|
||||
"@react-native-community/masked-view": "^0.1.6",
|
||||
"@tradle/react-native-http": "^2.0.1",
|
||||
"assert": "^1.5.0",
|
||||
@@ -64,7 +65,6 @@
|
||||
"react-navigation": "^4.0.10",
|
||||
"react-navigation-stack": "^2.0.16",
|
||||
"readable-stream": "^1.0.33",
|
||||
"realm": "^3.6.0",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
"sha.js": "^2.4.11",
|
||||
"stream-browserify": "^1.0.0",
|
||||
|
||||
+56
-177
@@ -1,24 +1,16 @@
|
||||
import { action } from 'mobx';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import Realm from 'realm';
|
||||
import { when } from 'mobx';
|
||||
import { LNURLPaySuccessAction } from 'js-lnurl';
|
||||
import NodeInfoStore from './../stores/NodeInfoStore';
|
||||
import SettingsStore from './../stores/SettingsStore';
|
||||
import Payment from './../models/Payment';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
|
||||
export interface LnurlPayTransaction {
|
||||
paymentHash: string;
|
||||
pending: boolean;
|
||||
domain: string;
|
||||
lnurl: string;
|
||||
metadata: LnurlPayMetadata;
|
||||
metadata_hash: string;
|
||||
successAction: LnurlPaySuccessAction;
|
||||
}
|
||||
time: number;
|
||||
|
||||
interface LnurlPayMetadata {
|
||||
descriptionHash: string;
|
||||
metadata: string;
|
||||
metadata: string; // only after an independent load from AsyncStorage.
|
||||
}
|
||||
|
||||
interface LnurlPaySuccessAction {
|
||||
@@ -30,150 +22,57 @@ interface LnurlPaySuccessAction {
|
||||
ciphertext: string;
|
||||
}
|
||||
|
||||
const LnurlPayTransactionSchema = {
|
||||
name: 'LnurlPayTransaction',
|
||||
primaryKey: 'paymentHash',
|
||||
properties: {
|
||||
paymentHash: 'string',
|
||||
pending: { type: 'bool', default: true },
|
||||
domain: { type: 'string', indexed: true },
|
||||
lnurl: 'string',
|
||||
metadata: 'LnurlPayMetadata',
|
||||
successAction: 'LnurlPaySuccessAction'
|
||||
}
|
||||
};
|
||||
|
||||
const LnurlPayMetadataSchema = {
|
||||
name: 'LnurlPayMetadata',
|
||||
primaryKey: 'descriptionHash',
|
||||
properties: {
|
||||
descriptionHash: 'string',
|
||||
metadata: 'string'
|
||||
}
|
||||
};
|
||||
|
||||
const LnurlPaySuccessActionSchema = {
|
||||
name: 'LnurlPaySuccessAction',
|
||||
properties: {
|
||||
tag: { type: 'string', default: 'noop' },
|
||||
description: 'string?',
|
||||
url: 'string?',
|
||||
message: 'string?',
|
||||
iv: 'string?',
|
||||
ciphertext: 'string?'
|
||||
}
|
||||
};
|
||||
interface LnurlPayMetadataEntry {
|
||||
metadata: string;
|
||||
last_stored: number;
|
||||
}
|
||||
|
||||
export default class LnurlPayStore {
|
||||
paymentHash: string | null;
|
||||
domain: string | null;
|
||||
successAction: LNURLPaySuccessAction | null;
|
||||
realm: any;
|
||||
settingsStore: SettingsStore;
|
||||
nodeInfoStore: NodeInfoStore;
|
||||
|
||||
constructor(settingsStore: SettingsStore, nodeInfoStore: NodeInfoStore) {
|
||||
this.settingsStore = settingsStore;
|
||||
this.nodeInfoStore = nodeInfoStore;
|
||||
this.realm = new Realm({
|
||||
path: `lnurl-${nodeInfoStore.nodeInfo.identity_pubkey}.realm`,
|
||||
schema: [
|
||||
LnurlPayTransactionSchema,
|
||||
LnurlPaySuccessActionSchema,
|
||||
LnurlPayMetadataSchema
|
||||
]
|
||||
});
|
||||
|
||||
when(
|
||||
() =>
|
||||
!!this.settingsStore.host &&
|
||||
!!this.settingsStore.port &&
|
||||
!!this.settingsStore.macaroonHex,
|
||||
() => this.checkPending()
|
||||
);
|
||||
constructor() {
|
||||
if (Math.random() < 0.1) {
|
||||
setTimeout(() => {
|
||||
this.deleteOld();
|
||||
}, 100000);
|
||||
}
|
||||
}
|
||||
|
||||
checkPending = () => {
|
||||
const { host, port, macaroonHex, sslVerification } = this.settingsStore;
|
||||
|
||||
// remove all pending stored lnurl-pay transactions if we can't find them on lnd
|
||||
// and remove their pending status if we find them as completed
|
||||
let pending = this.realm
|
||||
.objects('LnurlPayTransaction')
|
||||
.filtered('pending == true');
|
||||
|
||||
if (pending.length === 0) {
|
||||
// only if there's a pending tx on realm we'll do this expensive query
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `https://${host}${
|
||||
port ? ':' + port : ''
|
||||
}/v1/payments?include_incomplete=true`;
|
||||
|
||||
const headers = {
|
||||
'Grpc-Metadata-macaroon': macaroonHex
|
||||
};
|
||||
|
||||
RNFetchBlob.config({
|
||||
trusty: !sslVerification || true
|
||||
})
|
||||
.fetch('get', url)
|
||||
.then((response: any) => {
|
||||
const status = response.info().status;
|
||||
if (status == 200) {
|
||||
const data = response.json();
|
||||
let { payments } = data;
|
||||
for (let i = 0; i < pending.length; i++) {
|
||||
this.resolvePendingHash(
|
||||
payments,
|
||||
pending[i].paymentHash
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const error = response.json();
|
||||
const { message } = error;
|
||||
console.log(
|
||||
`error checking pending lnurl-pay transactions: ${err.message}`
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(
|
||||
`error checking pending lnurl-pay transactions: ${err.toString()}`
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
resolvePendingHash = (payments: Payment[], pendingHash: string) => {
|
||||
for (let j = 0; j < payments.length; j++) {
|
||||
let payment: Payment = payments[j];
|
||||
if (payment.payment_hash === pendingHash) {
|
||||
// a match!
|
||||
switch (payment.status) {
|
||||
case 'SUCCEEDED':
|
||||
this.acknowledge(pendingHash);
|
||||
return;
|
||||
case 'FAILED':
|
||||
this.clear(pendingHash);
|
||||
return;
|
||||
default:
|
||||
// leave it as is
|
||||
return;
|
||||
deleteOld = () => {
|
||||
// delete all lnurlpay keys older than 30 days
|
||||
const daysago30 = new Date().getTime() + 1000 * 60 * 60 * 24 * 30;
|
||||
const allKeys = await AsyncStorage.getAllKeys();
|
||||
var toRemove = [];
|
||||
for (let i = 0; i < allKeys.length; i++) {
|
||||
let key = allKeys[i];
|
||||
if (key.slice(0, 9) === 'lnurlpay:') {
|
||||
let item = JSON.parse(await AsyncStorage.getItem(key));
|
||||
if (
|
||||
(item.last_stored && item.last_stored < daysago30) ||
|
||||
(item.time && item.time < daysago30)
|
||||
) {
|
||||
toRemove.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here it's because there is no match / the payment is not on lnd
|
||||
this.clear(pendingHash);
|
||||
AsyncStorage.multiRemove(toRemove);
|
||||
};
|
||||
|
||||
@action
|
||||
public load = (paymentHash: string): LnurlPayTransaction => {
|
||||
return this.realm.objectForPrimaryKey(
|
||||
'LnurlPayTransaction',
|
||||
paymentHash
|
||||
);
|
||||
let lnurlpaytx = await AsyncStorage.getItem('lnurlpay:' + paymentHash);
|
||||
if (lnurlpaytx) {
|
||||
lnurlpaytx = JSON.parse(lnurlpaytx);
|
||||
let metadata = await AsyncStorage.getItem(lnurlpaytx.metadata_hash);
|
||||
if (metadata) {
|
||||
lnurlpaytx.metadata = JSON.parse(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return lnurlpaytx;
|
||||
};
|
||||
|
||||
@action
|
||||
@@ -181,51 +80,31 @@ export default class LnurlPayStore {
|
||||
paymentHash: string,
|
||||
domain: string,
|
||||
lnurl: string,
|
||||
metadata: any,
|
||||
metadata: string,
|
||||
descriptionHash: string,
|
||||
successAction: LNURLPaySuccessAction
|
||||
) => {
|
||||
this.realm.write(() => {
|
||||
this.realm.create(
|
||||
'LnurlPayTransaction',
|
||||
{
|
||||
const now = new Date().getTime();
|
||||
|
||||
AsyncStorage.multiSet([
|
||||
[
|
||||
'lnurlpay:' + paymentHash,
|
||||
JSON.stringify({
|
||||
paymentHash,
|
||||
domain,
|
||||
lnurl,
|
||||
metadata,
|
||||
successAction
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
successAction,
|
||||
time: now
|
||||
})
|
||||
],
|
||||
[
|
||||
'lnurlpay:' + descriptionHash,
|
||||
JSON.stringify({ metadata, last_stored: now })
|
||||
]
|
||||
]);
|
||||
|
||||
this.paymentHash = paymentHash;
|
||||
this.successAction = successAction;
|
||||
this.domain = domain;
|
||||
};
|
||||
|
||||
@action
|
||||
public acknowledge = (paymentHash: string) => {
|
||||
this.realm.write(() => {
|
||||
this.realm.create(
|
||||
'LnurlPayTransaction',
|
||||
{
|
||||
paymentHash,
|
||||
pending: false
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@action
|
||||
public clear = (paymentHash: string) => {
|
||||
this.realm.write(() => {
|
||||
this.realm.delete(
|
||||
this.realm.objectForPrimaryKey(
|
||||
'LnurlPayTransaction',
|
||||
paymentHash
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,13 +21,9 @@ export default class LnurlPayHistorical extends React.Component<
|
||||
LnurlPayHistoricalProps,
|
||||
LnurlPayHistoricalState
|
||||
> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showLnurlSuccess: false
|
||||
};
|
||||
}
|
||||
state = {
|
||||
showLnurlSuccess: false
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation, lnurlpaytx, preimage, SettingsStore } = this.props;
|
||||
|
||||
@@ -119,10 +119,8 @@ export default class LnurlPay extends React.Component<
|
||||
payment_hash,
|
||||
domain,
|
||||
lnurl.lnurlText,
|
||||
{
|
||||
metadata: lnurl.metadata,
|
||||
descriptionHash: description_hash
|
||||
},
|
||||
lnurl.metadata,
|
||||
description_hash,
|
||||
successAction
|
||||
);
|
||||
navigation.navigate('PaymentRequest');
|
||||
|
||||
+13
-1
@@ -26,6 +26,17 @@ interface PaymentProps {
|
||||
@inject('UnitsStore', 'SettingsStore', 'LnurlPayStore')
|
||||
@observer
|
||||
export default class PaymentView extends React.Component<PaymentProps> {
|
||||
state = {
|
||||
lnurlpaytx: null
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
let lnurlpaytx = await LnurlPayStore.load(payment_hash);
|
||||
if (lnurlpaytx) {
|
||||
this.setState({ lnurlpaytx });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
navigation,
|
||||
@@ -46,7 +57,6 @@ export default class PaymentView extends React.Component<PaymentProps> {
|
||||
enhancedPath
|
||||
} = payment;
|
||||
const date = getCreationTime;
|
||||
const lnurlpaytx = LnurlPayStore.load(payment_hash);
|
||||
|
||||
const BackButton = () => (
|
||||
<Icon
|
||||
@@ -64,6 +74,8 @@ export default class PaymentView extends React.Component<PaymentProps> {
|
||||
? PrivacyUtils.hideValue(getAmount(getFee), 3, true)
|
||||
: getAmount(getFee);
|
||||
|
||||
const lnurlpaytx = this.state.lnurlpaytx;
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={
|
||||
|
||||
@@ -29,20 +29,11 @@ export default class SendingLightning extends React.Component<
|
||||
{}
|
||||
> {
|
||||
componentDidMount = () => {
|
||||
const { TransactionsStore, LnurlPayStore } = this.props;
|
||||
const {
|
||||
payment_route,
|
||||
payment_hash,
|
||||
payment_error
|
||||
} = TransactionsStore;
|
||||
const { acknowledge, clear } = LnurlPayStore;
|
||||
|
||||
when(() => payment_route).then(() => {
|
||||
acknowledge(payment_hash);
|
||||
});
|
||||
when(() => payment_error).then(() => {
|
||||
clear(payment_hash);
|
||||
});
|
||||
};
|
||||
|
||||
getBackgroundColor() {
|
||||
|
||||
Reference in New Issue
Block a user