2018-03-24 22:24:20 +01:00
|
|
|
import { AsyncStorage } from 'react-native';
|
|
|
|
import { LegacyWallet, SegwitP2SHWallet, SegwitBech32Wallet } from './';
|
2018-03-31 02:03:58 +02:00
|
|
|
let encryption = require('../encryption');
|
2018-03-20 21:41:07 +01:00
|
|
|
|
|
|
|
export class AppStorage {
|
2018-03-31 02:03:58 +02:00
|
|
|
static FLAG_ENCRYPTED = 'data_encrypted';
|
2018-05-28 21:18:11 +02:00
|
|
|
static LANG = 'lang';
|
2018-03-31 02:03:58 +02:00
|
|
|
|
2018-03-20 21:41:07 +01:00
|
|
|
constructor() {
|
|
|
|
/** {Array.<AbstractWallet>} */
|
|
|
|
this.wallets = [];
|
|
|
|
this.tx_metadata = {};
|
2018-03-31 02:03:58 +02:00
|
|
|
this.cachedPassword = false;
|
2018-03-20 21:41:07 +01:00
|
|
|
this.settings = {
|
2018-06-25 00:22:46 +02:00
|
|
|
brandingColor: '#ffffff',
|
|
|
|
foregroundColor: '#0c2550',
|
|
|
|
buttonBackground: '#ffffff',
|
|
|
|
buttonTextColor: '#0c2550',
|
2018-03-20 21:41:07 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-03-30 20:31:10 +02:00
|
|
|
async storageIsEncrypted() {
|
2018-06-25 00:22:46 +02:00
|
|
|
// await AsyncStorage.clear();
|
2018-03-30 20:31:10 +02:00
|
|
|
let data;
|
|
|
|
try {
|
2018-03-31 02:03:58 +02:00
|
|
|
data = await AsyncStorage.getItem(AppStorage.FLAG_ENCRYPTED);
|
2018-03-30 20:31:10 +02:00
|
|
|
} catch (error) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-31 02:03:58 +02:00
|
|
|
return !!data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates through all values of `data` trying to
|
|
|
|
* decrypt each one, and returns first one successfully decrypted
|
|
|
|
*
|
|
|
|
* @param data String (serialized array)
|
|
|
|
* @param password
|
|
|
|
*/
|
|
|
|
decryptData(data, password) {
|
|
|
|
data = JSON.parse(data);
|
|
|
|
let decrypted;
|
|
|
|
for (let value of data) {
|
|
|
|
try {
|
|
|
|
decrypted = encryption.decrypt(value, password);
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (decrypted) {
|
|
|
|
return decrypted;
|
|
|
|
}
|
2018-03-30 20:31:10 +02:00
|
|
|
}
|
2018-03-31 02:03:58 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
async encryptStorage(password) {
|
|
|
|
// assuming the storage is not yet encrypted
|
|
|
|
await this.saveToDisk();
|
|
|
|
let data = await AsyncStorage.getItem('data');
|
|
|
|
// TODO: refactor ^^^ (should not save & load to fetch data)
|
|
|
|
|
|
|
|
let encrypted = encryption.encrypt(data, password);
|
|
|
|
data = [];
|
|
|
|
data.push(encrypted); // putting in array as we might have many buckets with storages
|
|
|
|
data = JSON.stringify(data);
|
2018-04-01 01:16:42 +02:00
|
|
|
this.cachedPassword = password;
|
2018-03-31 02:03:58 +02:00
|
|
|
await AsyncStorage.setItem('data', data);
|
|
|
|
await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, '1');
|
2018-03-30 20:31:10 +02:00
|
|
|
}
|
|
|
|
|
2018-04-01 01:16:42 +02:00
|
|
|
/**
|
|
|
|
* Cleans up all current application data (wallets, tx metadata etc)
|
|
|
|
* Encrypts the bucket and saves it storage
|
|
|
|
*
|
|
|
|
* @returns {Promise.<boolean>} Success or failure
|
|
|
|
*/
|
|
|
|
async createFakeStorage(fakePassword) {
|
|
|
|
this.wallets = [];
|
|
|
|
this.tx_metadata = {};
|
|
|
|
|
|
|
|
let data = {
|
|
|
|
wallets: [],
|
|
|
|
tx_metadata: {},
|
|
|
|
};
|
|
|
|
|
|
|
|
let buckets = await AsyncStorage.getItem('data');
|
|
|
|
buckets = JSON.parse(buckets);
|
|
|
|
buckets.push(encryption.encrypt(JSON.stringify(data), fakePassword));
|
|
|
|
this.cachedPassword = fakePassword;
|
|
|
|
|
|
|
|
return AsyncStorage.setItem('data', JSON.stringify(buckets));
|
|
|
|
}
|
|
|
|
|
2018-03-31 02:03:58 +02:00
|
|
|
/**
|
|
|
|
* Loads from storage all wallets and
|
|
|
|
* maps them to `this.wallets`
|
|
|
|
*
|
|
|
|
* @param password If present means storage must be decrypted before usage
|
|
|
|
* @returns {Promise.<boolean>}
|
|
|
|
*/
|
|
|
|
async loadFromDisk(password) {
|
2018-03-20 21:41:07 +01:00
|
|
|
try {
|
|
|
|
let data = await AsyncStorage.getItem('data');
|
2018-03-31 02:03:58 +02:00
|
|
|
if (password) {
|
|
|
|
data = this.decryptData(data, password);
|
2018-03-31 15:43:08 +02:00
|
|
|
if (data) {
|
|
|
|
// password is good, cache it
|
|
|
|
this.cachedPassword = password;
|
|
|
|
}
|
2018-03-31 02:03:58 +02:00
|
|
|
}
|
2018-03-20 21:41:07 +01:00
|
|
|
if (data !== null) {
|
|
|
|
data = JSON.parse(data);
|
|
|
|
if (!data.wallets) return false;
|
|
|
|
let wallets = data.wallets;
|
|
|
|
for (let key of wallets) {
|
|
|
|
// deciding which type is wallet and instatiating correct object
|
|
|
|
let tempObj = JSON.parse(key);
|
|
|
|
let unserializedWallet;
|
|
|
|
switch (tempObj.type) {
|
|
|
|
case 'segwitBech32':
|
|
|
|
unserializedWallet = SegwitBech32Wallet.fromJson(key);
|
|
|
|
break;
|
|
|
|
case 'segwitP2SH':
|
|
|
|
unserializedWallet = SegwitP2SHWallet.fromJson(key);
|
|
|
|
break;
|
|
|
|
case 'legacy':
|
|
|
|
default:
|
|
|
|
unserializedWallet = LegacyWallet.fromJson(key);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// done
|
|
|
|
this.wallets.push(unserializedWallet);
|
|
|
|
this.tx_metadata = data.tx_metadata;
|
|
|
|
}
|
2018-03-31 02:03:58 +02:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false; // failed loading data or loading/decryptin data
|
2018-03-20 21:41:07 +01:00
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-07-02 11:48:40 +02:00
|
|
|
* Lookup wallet in list by it's secret and
|
|
|
|
* remove it from `this.wallets`
|
2018-03-20 21:41:07 +01:00
|
|
|
*
|
|
|
|
* @param wallet {AbstractWallet}
|
|
|
|
*/
|
|
|
|
deleteWallet(wallet) {
|
|
|
|
let secret = wallet.getSecret();
|
|
|
|
let tempWallets = [];
|
|
|
|
for (let value of this.wallets) {
|
|
|
|
if (value.getSecret() === secret) {
|
|
|
|
// the one we should delete
|
|
|
|
// nop
|
|
|
|
} else {
|
|
|
|
// the one we must keep
|
|
|
|
tempWallets.push(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.wallets = tempWallets;
|
|
|
|
}
|
|
|
|
|
2018-03-31 02:03:58 +02:00
|
|
|
/**
|
|
|
|
* Serializes and saves to storage object data.
|
|
|
|
* If cached password is saved - finds the correct bucket
|
|
|
|
* to save to, encrypts and then saves.
|
|
|
|
*
|
2018-06-28 03:43:28 +02:00
|
|
|
* @returns {Promise} Result of AsyncStorage save
|
2018-03-31 02:03:58 +02:00
|
|
|
*/
|
|
|
|
async saveToDisk() {
|
2018-03-20 21:41:07 +01:00
|
|
|
let walletsToSave = [];
|
|
|
|
for (let key of this.wallets) {
|
|
|
|
walletsToSave.push(JSON.stringify(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
let data = {
|
|
|
|
wallets: walletsToSave,
|
|
|
|
tx_metadata: this.tx_metadata,
|
|
|
|
};
|
|
|
|
|
2018-03-31 02:03:58 +02:00
|
|
|
if (this.cachedPassword) {
|
|
|
|
// should find the correct bucket, encrypt and then save
|
|
|
|
let buckets = await AsyncStorage.getItem('data');
|
|
|
|
buckets = JSON.parse(buckets);
|
|
|
|
let newData = [];
|
|
|
|
for (let bucket of buckets) {
|
|
|
|
let decrypted = encryption.decrypt(bucket, this.cachedPassword);
|
|
|
|
if (!decrypted) {
|
|
|
|
// no luck decrypting, its not our bucket
|
|
|
|
newData.push(bucket);
|
|
|
|
} else {
|
|
|
|
// decrypted ok, this is our bucket
|
|
|
|
// we serialize our object's data, encrypt it, and add it to buckets
|
|
|
|
newData.push(
|
|
|
|
encryption.encrypt(JSON.stringify(data), this.cachedPassword),
|
|
|
|
);
|
2018-03-31 15:43:08 +02:00
|
|
|
await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, '1');
|
2018-03-31 02:03:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
data = newData;
|
2018-03-31 15:43:08 +02:00
|
|
|
} else {
|
|
|
|
await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, ''); // drop the flag
|
2018-03-31 02:03:58 +02:00
|
|
|
}
|
|
|
|
|
2018-03-20 21:41:07 +01:00
|
|
|
return AsyncStorage.setItem('data', JSON.stringify(data));
|
|
|
|
}
|
|
|
|
|
2018-06-17 12:46:19 +02:00
|
|
|
/**
|
|
|
|
* For each wallet, fetches balance from remote endpoint.
|
|
|
|
* Use getter for a specific wallet to get actual balance.
|
|
|
|
* Returns void.
|
2018-06-28 03:43:28 +02:00
|
|
|
* If index is present then fetch only from this specific wallet
|
2018-06-17 12:46:19 +02:00
|
|
|
*
|
|
|
|
* @return {Promise.<void>}
|
|
|
|
*/
|
2018-06-28 03:43:28 +02:00
|
|
|
async fetchWalletBalances(index) {
|
|
|
|
if (index || index === 0) {
|
|
|
|
let c = 0;
|
|
|
|
for (let wallet of this.wallets) {
|
|
|
|
if (c++ === index) {
|
|
|
|
await wallet.fetchBalance();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (let wallet of this.wallets) {
|
|
|
|
await wallet.fetchBalance();
|
|
|
|
}
|
2018-03-20 21:41:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-17 12:46:19 +02:00
|
|
|
/**
|
|
|
|
* Fetches from remote endpoint all transactions for each wallet.
|
|
|
|
* Returns void.
|
|
|
|
* To access transactions - get them from each respective wallet.
|
2018-06-28 03:43:28 +02:00
|
|
|
* If index is present then fetch only from this specific wallet.
|
2018-06-17 12:46:19 +02:00
|
|
|
*
|
2018-06-25 00:22:46 +02:00
|
|
|
* @param index {Integer} Index of the wallet in this.wallets array,
|
|
|
|
* blank to fetch from all wallets
|
2018-06-17 12:46:19 +02:00
|
|
|
* @return {Promise.<void>}
|
|
|
|
*/
|
2018-06-25 00:22:46 +02:00
|
|
|
async fetchWalletTransactions(index) {
|
|
|
|
if (index || index === 0) {
|
|
|
|
let c = 0;
|
|
|
|
for (let wallet of this.wallets) {
|
|
|
|
if (c++ === index) {
|
|
|
|
await wallet.fetchTransactions();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (let wallet of this.wallets) {
|
|
|
|
await wallet.fetchTransactions();
|
|
|
|
}
|
2018-03-20 21:41:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @returns {Array.<AbstractWallet>}
|
|
|
|
*/
|
|
|
|
getWallets() {
|
|
|
|
return this.wallets;
|
|
|
|
}
|
|
|
|
|
2018-06-17 12:46:19 +02:00
|
|
|
/**
|
2018-06-25 00:22:46 +02:00
|
|
|
* Getter for all transactions in all wallets.
|
|
|
|
* But if index is provided - only for wallet with corresponding index
|
2018-06-17 12:46:19 +02:00
|
|
|
*
|
2018-06-25 00:22:46 +02:00
|
|
|
* @param index {Integer} Wallet index in this.wallets. Empty for all wallets.
|
2018-06-17 12:46:19 +02:00
|
|
|
* @return {Array}
|
|
|
|
*/
|
2018-06-25 00:22:46 +02:00
|
|
|
getTransactions(index) {
|
|
|
|
if (index || index === 0) {
|
|
|
|
let txs = [];
|
|
|
|
let c = 0;
|
|
|
|
for (let wallet of this.wallets) {
|
|
|
|
if (c++ === index) {
|
|
|
|
txs = txs.concat(wallet.transactions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return txs;
|
|
|
|
}
|
|
|
|
|
2018-03-20 21:41:07 +01:00
|
|
|
let txs = [];
|
|
|
|
for (let wallet of this.wallets) {
|
|
|
|
txs = txs.concat(wallet.transactions);
|
|
|
|
}
|
|
|
|
return txs;
|
|
|
|
}
|
|
|
|
|
2018-06-17 12:46:19 +02:00
|
|
|
/**
|
|
|
|
* Getter for a sum of all balances of all wallets
|
|
|
|
*
|
|
|
|
* @return {number}
|
|
|
|
*/
|
2018-03-20 21:41:07 +01:00
|
|
|
getBalance() {
|
|
|
|
let finalBalance = 0;
|
|
|
|
for (let wal of this.wallets) {
|
|
|
|
finalBalance += wal.balance;
|
|
|
|
}
|
|
|
|
return finalBalance;
|
|
|
|
}
|
|
|
|
}
|