Wallet/src/js/services/profileService.js
Gustavo Maximiliano Cortez 98471e952a
Bug/select wallet incomplete / Refactor wallet Services (#4159)
* Addressbook: display error if select an incomplete wallet

* Check if wallet is complete/needs_backup for Glidera and Coinbase

* Ref/create a walletService

* Ref. walletService

* Fix Glidera and Coinbase

* Removes txService

* Fix glidera connection for mobile. Fix bitcode for xcode

* Fix duplicated entry

* Revert "Bump bwc version 2.3.1"

* adds karma-mocha

* Refactor

* Refactor lock function

* Refactor reject, remove and broadcast tx

* add walletService tests WIP

* add walletService tests WIP 2

* merge

* update tests to mocha

* fix tests. Angular 1.5?

* Fix test

* Generate angular-bwc before testing

* Rever gitignore

* Wording
2016-05-13 13:00:54 -03:00

690 lines
22 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
angular.module('copayApp.services')
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, notificationService, pushNotificationsService, isChromeApp, isCordova, isMobile, gettext, gettextCatalog, nodeWebkit, bwsError, uxLanguage, bitcore) {
var root = {};
var errors = bwcService.getErrors();
var usePushNotifications = isCordova && !isMobile.Windows();
var FOREGROUND_UPDATE_PERIOD = 5;
var BACKGROUND_UPDATE_PERIOD = 30;
root.profile = null;
root.focusedClient = null;
root.walletClients = {};
root.Utils = bwcService.getUtils();
root.formatAmount = function(amount) {
var config = configService.getSync().wallet.settings;
if (config.unitCode == 'sat') return amount;
//TODO : now only works for english, specify opts to change thousand separator and decimal separator
return this.Utils.formatAmount(amount, config.unitCode);
};
root._setFocus = function(walletId, cb) {
$log.debug('Set focus:', walletId);
// Set local object
if (walletId)
root.focusedClient = root.walletClients[walletId];
else
root.focusedClient = [];
if (lodash.isEmpty(root.focusedClient)) {
root.focusedClient = root.walletClients[lodash.keys(root.walletClients)[0]];
}
// Still nothing?
if (lodash.isEmpty(root.focusedClient)) {
$rootScope.$emit('Local/NoWallets');
} else {
$rootScope.$emit('Local/NewFocusedWallet');
// Set update period
lodash.each(root.walletClients, function(client, id) {
client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD);
});
root.focusedClient.setNotificationsInterval(FOREGROUND_UPDATE_PERIOD);
}
return cb();
};
root.setAndStoreFocus = function(walletId, cb) {
root._setFocus(walletId, function() {
storageService.storeFocusedWalletId(walletId, cb);
});
};
root.setBaseURL = function(walletId) {
var config = configService.getSync();
var defaults = configService.getDefaults();
bwcService.setBaseUrl((config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url);
bwcService.setTransports(['polling']);
}
root.setWalletClient = function(credentials) {
if (root.walletClients[credentials.walletId] &&
root.walletClients[credentials.walletId].started) {
return;
}
root.setBaseURL(credentials.walletId);
var client = bwcService.getClient(JSON.stringify(credentials));
root.walletClients[credentials.walletId] = client;
client.removeAllListeners();
client.on('report', function(n) {
$log.info('BWC Report:'+ n);
});
client.on('notification', function(n) {
$log.debug('BWC Notification:', n);
notificationService.newBWCNotification(n,
client.credentials.walletId, client.credentials.walletName);
if (root.focusedClient.credentials.walletId == client.credentials.walletId) {
$rootScope.$emit(n.type, n);
} else {
$rootScope.$apply();
}
});
client.on('walletCompleted', function() {
$log.debug('Wallet completed');
root.updateCredentialsFC(function() {
$rootScope.$emit('Local/WalletCompleted', client.credentials.walletId);
});
});
root.walletClients[credentials.walletId].started = true;
root.walletClients[credentials.walletId].doNotVerifyPayPro = isChromeApp;
if (client.hasPrivKeyEncrypted() && !client.isPrivKeyEncrypted()) {
$log.warn('Auto locking unlocked wallet:' + credentials.walletId);
client.lock();
}
client.initialize({}, function(err) {
if (err) {
$log.error('Could not init notifications err:', err);
return;
}
client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD);
});
}
root.setWalletClients = function() {
var credentials = root.profile.credentials;
lodash.each(credentials, function(credential) {
//$log.info("Credentials:", credentials);
root.setWalletClient(credential);
});
$rootScope.$emit('Local/WalletListUpdated');
};
root.bindProfile = function(profile, cb) {
root.profile = profile;
configService.get(function(err) {
$log.debug('Preferences read');
if (err) return cb(err);
root.setWalletClients();
storageService.getFocusedWalletId(function(err, focusedWalletId) {
if (err) return cb(err);
root._setFocus(focusedWalletId, function() {
$rootScope.$emit('Local/ProfileBound');
if (usePushNotifications)
root.pushNotificationsInit();
root.isDisclaimerAccepted(function(val) {
if (!val) {
return cb(new Error('NONAGREEDDISCLAIMER: Non agreed disclaimer'));
} else {
return cb();
}
});
});
});
});
};
root.pushNotificationsInit = function() {
var defaults = configService.getDefaults();
var push = pushNotificationsService.init(root.walletClients);
push.on('notification', function(data) {
if (!data.additionalData.foreground) {
window.ignoreMobilePause = true;
$log.debug('Push notification event: ', data.message);
$timeout(function() {
var wallets = root.getWallets();
var walletToFind = data.additionalData.walletId;
var walletFound = lodash.find(wallets, function(w) {
return (lodash.isEqual(walletToFind, sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(w.id))));
});
if (!walletFound) return $log.debug('Wallet not found');
root.setAndStoreFocus(walletFound.id, function() {});
}, 100);
}
});
};
root.getProfile = function(cb) {
storageService.getProfile(function(err, profile) {
return cb(err, profile);
});
};
root.loadAndBindProfile = function(cb) {
storageService.getProfile(function(err, profile) {
if (err) {
$rootScope.$emit('Local/DeviceError', err);
return cb(err);
}
if (!profile) {
// Migration??
storageService.tryToMigrate(function(err, migratedProfile) {
if (err) return cb(err);
if (!migratedProfile)
return cb(new Error('NOPROFILE: No profile'));
profile = migratedProfile;
return root.bindProfile(profile, cb);
})
} else {
$log.debug('Profile read');
return root.bindProfile(profile, cb);
}
});
};
root._seedWallet = function(opts, cb) {
opts = opts || {};
if (opts.bwsurl)
bwcService.setBaseUrl(opts.bwsurl);
var walletClient = bwcService.getClient();
var network = opts.networkName || 'livenet';
if (opts.mnemonic) {
try {
opts.mnemonic = root._normalizeMnemonic(opts.mnemonic);
walletClient.seedFromMnemonic(opts.mnemonic, {
network: network,
passphrase: opts.passphrase,
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
});
} catch (ex) {
$log.info(ex);
return cb(gettext('Could not create: Invalid wallet recovery phrase'));
}
} else if (opts.extendedPrivateKey) {
try {
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey);
} catch (ex) {
$log.warn(ex);
return cb(gettext('Could not create using the specified extended private key'));
}
} else if (opts.extendedPublicKey) {
try {
walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
});
} catch (ex) {
$log.warn("Creating wallet from Extended Public Key Arg:", ex, opts);
return cb(gettext('Could not create using the specified extended public key'));
}
} else {
var lang = uxLanguage.getCurrentLanguage();
try {
walletClient.seedFromRandomWithMnemonic({
network: network,
passphrase: opts.passphrase,
language: lang,
account: 0,
});
} catch (e) {
$log.info('Error creating recovery phrase: ' + e.message);
if (e.message.indexOf('language') > 0) {
$log.info('Using default language for recovery phrase');
walletClient.seedFromRandomWithMnemonic({
network: network,
passphrase: opts.passphrase,
account: 0,
});
} else {
return cb(e);
}
}
}
return cb(null, walletClient);
};
root._createNewProfile = function(opts, cb) {
if (opts.noWallet) {
return cb(null, Profile.create());
}
root._seedWallet({}, function(err, walletClient) {
if (err) return cb(err);
var walletName = gettextCatalog.getString('Personal Wallet');
var me = gettextCatalog.getString('me');
walletClient.createWallet(walletName, me, 1, 1, {
network: 'livenet'
}, function(err) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
var p = Profile.create({
credentials: [JSON.parse(walletClient.export())],
});
return cb(null, p);
});
})
};
root.createWallet = function(opts, cb) {
$log.debug('Creating Wallet:', opts);
root._seedWallet(opts, function(err, walletClient) {
if (err) return cb(err);
walletClient.createWallet(opts.name, opts.myName || 'me', opts.m, opts.n, {
network: opts.networkName
}, function(err, secret) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
root._addWalletClient(walletClient, opts, cb);
})
});
};
root.joinWallet = function(opts, cb) {
var walletClient = bwcService.getClient();
$log.debug('Joining Wallet:', opts);
try {
var walletData = bwcService.parseSecret(opts.secret);
// check if exist
if (lodash.find(root.profile.credentials, {
'walletId': walletData.walletId
})) {
return cb(gettext('Cannot join the same wallet more that once'));
}
} catch (ex) {
$log.debug(ex);
return cb(gettext('Bad wallet invitation'));
}
opts.networkName = walletData.network;
$log.debug('Joining Wallet:', opts);
root._seedWallet(opts, function(err, walletClient) {
if (err) return cb(err);
walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) {
if (err) return bwsError.cb(err, gettext('Could not join wallet'), cb);
root._addWalletClient(walletClient, opts, cb);
});
});
};
root.getClient = function(walletId) {
return root.walletClients[walletId];
};
root.deleteWalletFC = function(opts, cb) {
var fc = root.focusedClient;
var walletId = fc.credentials.walletId;
pushNotificationsService.unsubscribe(root.getClient(walletId), function(err) {
if (err) $log.warn('Unsubscription error: ' + err.message);
else $log.debug('Unsubscribed from push notifications service');
$log.debug('Deleting Wallet:', fc.credentials.walletName);
fc.removeAllListeners();
root.profile.credentials = lodash.reject(root.profile.credentials, {
walletId: walletId
});
delete root.walletClients[walletId];
root.focusedClient = null;
storageService.clearLastAddress(walletId, function(err) {
if (err) $log.warn(err);
});
storageService.removeTxHistory(walletId, function(err) {
if (err) $log.warn(err);
});
storageService.clearBackupFlag(walletId, function(err) {
if (err) $log.warn(err);
});
$timeout(function() {
root.setWalletClients();
root.setAndStoreFocus(null, function() {
storageService.storeProfile(root.profile, function(err) {
if (err) return cb(err);
return cb();
});
});
});
});
};
root.setMetaData = function(walletClient, addressBook, historyCache, cb) {
storageService.getAddressbook(walletClient.credentials.network, function(err, localAddressBook) {
var localAddressBook1 = {};
try {
localAddressBook1 = JSON.parse(localAddressBook);
} catch (ex) {
$log.warn(ex);
}
var mergeAddressBook = lodash.merge(addressBook, localAddressBook1);
storageService.setAddressbook(walletClient.credentials.network, JSON.stringify(addressBook), function(err) {
if (err) return cb(err);
storageService.setTxHistory(JSON.stringify(historyCache), walletClient.credentials.walletId, function(err) {
if (err) return cb(err);
return cb(null);
});
});
});
}
root._addWalletClient = function(walletClient, opts, cb) {
var walletId = walletClient.credentials.walletId;
// check if exist
var w = lodash.find(root.profile.credentials, {
'walletId': walletId
});
if (w) {
return cb(gettext('Wallet already in Copay' + ": ") + w.walletName);
}
var config = configService.getSync();
var defaults = configService.getDefaults();
var bwsFor = {};
bwsFor[walletId] = opts.bwsurl || defaults.bws.url;
configService.set({
bwsFor: bwsFor,
}, function(err) {
if (err) console.log(err);
root.profile.credentials.push(JSON.parse(walletClient.export()));
root.setWalletClients();
var handleImport = function(cb) {
var isImport = opts.mnemonic || opts.externalSource || opts.extendedPrivateKey;
if (!isImport)
return cb();
$rootScope.$emit('Local/BackupDone', walletId);
if (!walletClient.isComplete())
return cb();
storageService.setCleanAndScanAddresses(walletId, cb);
};
handleImport(function() {
root.setAndStoreFocus(walletId, function() {
storageService.storeProfile(root.profile, function(err) {
$rootScope.$emit('Local/ProfileCreated');
if (config.pushNotifications.enabled)
pushNotificationsService.enableNotifications(root.walletClients);
return cb(err, walletId);
});
});
});
});
};
root.importWallet = function(str, opts, cb) {
if (opts.bwsurl)
bwcService.setBaseUrl(opts.bwsurl);
var walletClient = bwcService.getClient();
$log.debug('Importing Wallet:', opts);
try {
walletClient.import(str, {
compressed: opts.compressed,
password: opts.password
});
} catch (err) {
return cb(gettext('Could not import. Check input file and spending password'));
}
str = JSON.parse(str);
var addressBook = str.addressBook || {};
var historyCache = str.historyCache ||  [];
root._addWalletClient(walletClient, opts, function(err, walletId) {
if (err) return cb(err);
root.setMetaData(walletClient, addressBook, historyCache, function(error) {
if (error) $log.warn(error);
return cb(err, walletId);
});
});
};
root.importExtendedPrivateKey = function(xPrivKey, opts, cb) {
if (opts.bwsurl)
bwcService.setBaseUrl(opts.bwsurl);
var walletClient = bwcService.getClient();
$log.debug('Importing Wallet xPrivKey');
walletClient.importFromExtendedPrivateKey(xPrivKey, opts, function(err) {
if (err)
return bwsError.cb(err, gettext('Could not import'), cb);
root._addWalletClient(walletClient, opts, cb);
});
};
root._normalizeMnemonic = function(words) {
var isJA = words.indexOf('\u3000') > -1;
var wordList = words.split(/[\u3000\s]+/);
return wordList.join(isJA ? '\u3000' : ' ');
};
root.importMnemonic = function(words, opts, cb) {
if (opts.bwsurl)
bwcService.setBaseUrl(opts.bwsurl);
var walletClient = bwcService.getClient();
$log.debug('Importing Wallet Mnemonic');
words = root._normalizeMnemonic(words);
walletClient.importFromMnemonic(words, {
network: opts.networkName,
passphrase: opts.passphrase,
account: opts.account || 0,
}, function(err) {
if (err)
return bwsError.cb(err, gettext('Could not import'), cb);
root._addWalletClient(walletClient, opts, cb);
});
};
root.importExtendedPublicKey = function(opts, cb) {
if (opts.bwsurl)
bwcService.setBaseUrl(opts.bwsurl);
var walletClient = bwcService.getClient();
$log.debug('Importing Wallet XPubKey');
walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
}, function(err) {
if (err) {
// in HW wallets, req key is always the same. They can't addAccess.
if (err instanceof errors.NOT_AUTHORIZED)
err.name = 'WALLET_DOES_NOT_EXIST';
return bwsError.cb(err, gettext('Could not import'), cb);
}
root._addWalletClient(walletClient, opts, cb);
});
};
root.create = function(opts, cb) {
$log.info('Creating profile');
var defaults = configService.getDefaults();
configService.get(function(err) {
bwcService.setBaseUrl(defaults.bws.url);
bwcService.setTransports(['polling']);
root._createNewProfile(opts, function(err, p) {
if (err) return cb(err);
root.bindProfile(p, function(err) {
storageService.storeNewProfile(p, function(err) {
return cb(err);
});
});
});
});
};
root.setDisclaimerAccepted = function(cb) {
root.profile.disclaimerAccepted = true;
storageService.storeProfile(root.profile, function(err) {
return cb(err);
});
};
root.isDisclaimerAccepted = function(cb) {
var disclaimerAccepted = root.profile && root.profile.disclaimerAccepted;
if (disclaimerAccepted)
return cb(true);
// OLD flag
storageService.getCopayDisclaimerFlag(function(err, val) {
if (val) {
root.profile.disclaimerAccepted = true;
return cb(true);
} else {
return cb();
}
});
};
root.importLegacyWallet = function(username, password, blob, cb) {
var walletClient = bwcService.getClient();
walletClient.createWalletFromOldCopay(username, password, blob, function(err, existed) {
if (err) return cb(gettext('Error importing wallet: ') + err);
if (root.walletClients[walletClient.credentials.walletId]) {
$log.debug('Wallet:' + walletClient.credentials.walletName + ' already imported');
return cb(gettext('Wallet Already Imported: ') + walletClient.credentials.walletName);
};
$log.debug('Creating Wallet:', walletClient.credentials.walletName);
root.profile.credentials.push(JSON.parse(walletClient.export()));
root.setWalletClients();
root.setAndStoreFocus(walletClient.credentials.walletId, function() {
storageService.storeProfile(root.profile, function(err) {
return cb(null, walletClient.credentials.walletId, walletClient.credentials.walletName, existed);
});
});
});
};
root.updateCredentialsFC = function(cb) {
var fc = root.focusedClient;
var newCredentials = lodash.reject(root.profile.credentials, {
walletId: fc.credentials.walletId
});
newCredentials.push(JSON.parse(fc.export()));
root.profile.credentials = newCredentials;
storageService.storeProfile(root.profile, cb);
};
root.setPrivateKeyEncryptionFC = function(password, cb) {
var fc = root.focusedClient;
$log.debug('Encrypting private key for', fc.credentials.walletName);
fc.setPrivateKeyEncryption(password);
fc.lock();
root.updateCredentialsFC(function() {
$log.debug('Wallet encrypted');
return cb();
});
};
root.disablePrivateKeyEncryptionFC = function(cb) {
var fc = root.focusedClient;
$log.debug('Disabling private key encryption for', fc.credentials.walletName);
try {
fc.disablePrivateKeyEncryption();
} catch (e) {
return cb(e);
}
root.updateCredentialsFC(function() {
$log.debug('Wallet encryption disabled');
return cb();
});
};
root.getWallets = function(network) {
if (!root.profile) return [];
var config = configService.getSync();
config.colorFor = config.colorFor || {};
config.aliasFor = config.aliasFor || {};
var ret = lodash.map(root.profile.credentials, function(c) {
return {
m: c.m,
n: c.n,
name: config.aliasFor[c.walletId] || c.walletName,
id: c.walletId,
network: c.network,
color: config.colorFor[c.walletId] || '#4A90E2',
copayerId: c.copayerId
};
});
if (network) {
ret = lodash.filter(ret, function(w) {
return (w.network == network);
});
}
return lodash.sortBy(ret, 'name');
};
return root;
});