Wallet/src/js/services/profileService.js
2016-01-21 22:07:23 -03:00

739 lines
23 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, $location, $timeout, $filter, $log, lodash, storageService, bwcService, configService, notificationService, isChromeApp, isCordova, gettext, gettextCatalog, nodeWebkit, bwsError, uxLanguage, ledger, bitcore, trezor) {
var root = {};
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('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')
});
});
root.walletClients[credentials.walletId].started = true;
root.walletClients[credentials.walletId].doNotVerifyPayPro = isChromeApp;
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(credentials) {
root.setWalletClient(credentials);
});
$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');
root.isDisclaimerAccepted(function(val) {
if (!val) {
return cb(new Error('NONAGREEDDISCLAIMER: Non agreed disclaimer'));
} else {
return cb();
}
});
});
});
});
};
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 seed'));
}
} 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 seed: ' + e.message);
if (e.message.indexOf('language') > 0) {
$log.info('Using default language for mnemonic');
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;
$rootScope.$emit('Local/UnsubscribeNotifications', walletId, function() {
$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) {
if (config.pushNotifications.enabled)
$rootScope.$emit('Local/SubscribeNotifications');
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 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) console.log(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, 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.code == 'NOT_AUTHORIZED')
err.code = '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) {
storageService.getProfile(function(err, profile) {
profile.disclaimerAccepted = true;
storageService.storeProfile(profile, function(err) {
return cb(err);
});
});
};
root.isDisclaimerAccepted = function(cb) {
storageService.getProfile(function(err, profile) {
if (profile && profile.disclaimerAccepted)
return cb(true);
else if (profile && !profile.disclaimerAccepted) {
storageService.getCopayDisclaimerFlag(function(err, val) {
if (val) {
profile.disclaimerAccepted = true;
storageService.storeProfile(profile, function(err) {
if (err) $log.error(err);
return cb(true);
});
} else {
return cb();
}
});
} 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);
root.lockFC();
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.lockFC = function() {
var fc = root.focusedClient;
try {
fc.lock();
} catch (e) {};
};
root.unlockFC = function(cb) {
var fc = root.focusedClient;
if (!fc.isPrivKeyEncrypted()) return cb();
$log.debug('Wallet is encrypted');
$rootScope.$emit('Local/NeedsPassword', false, function(err2, password) {
if (err2 || !password) {
return cb({
message: (err2 || gettext('Password needed'))
});
}
try {
fc.unlock(password);
} catch (e) {
$log.debug(e);
return cb({
message: gettext('Wrong password')
});
}
$timeout(function() {
if (fc.hasPrivKeyEncrypted()) {
$log.debug('Locking wallet automatically');
root.lockFC();
};
}, 60000);
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');
};
root._signWithLedger = function(txp, cb) {
var fc = root.focusedClient;
$log.info('Requesting Ledger Chrome app to sign the transaction');
ledger.signTx(txp, fc.credentials.account, function(result) {
$log.debug('Ledger response', result);
if (!result.success)
return cb(result.message || result.error);
txp.signatures = lodash.map(result.signatures, function(s) {
return s.substring(0, s.length - 2);
});
return fc.signTxProposal(txp, cb);
});
};
root._signWithTrezor = function(txp, cb) {
var fc = root.focusedClient;
$log.info('Requesting Trezor to sign the transaction');
var xPubKeys = lodash.pluck(fc.credentials.publicKeyRing, 'xPubKey');
trezor.signTx(xPubKeys, txp, fc.credentials.account, function(err, result) {
if (err) return cb(err);
$log.debug('Trezor response', result);
txp.signatures = result.signatures;
return fc.signTxProposal(txp, cb);
});
};
root.signTxProposal = function(txp, cb) {
var fc = root.focusedClient;
if (fc.isPrivKeyExternal()) {
switch (fc.getPrivKeyExternalSourceName()) {
case 'ledger':
return root._signWithLedger(txp, cb);
case 'trezor':
return root._signWithTrezor(txp, cb);
default:
var msg = 'Unsupported External Key:' + fc.getPrivKeyExternalSourceName();
$log.error(msg);
return cb(msg);
}
} else {
return fc.signTxProposal(txp, function(err, signedTxp) {
root.lockFC();
return cb(err, signedTxp);
});
}
};
return root;
});