diff --git a/Gruntfile.js b/Gruntfile.js index 2955fd54e..9f8e42899 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -56,7 +56,7 @@ module.exports = function(grunt) { files: [ 'js/models/*.js', 'js/util/*.js', - 'plugins/*.js', + 'js/plugins/*.js', 'js/*.js', '!js/copayBundle.js', '!js/copayMain.js' @@ -85,7 +85,7 @@ module.exports = function(grunt) { tasks: ['shell:dev', 'concat:main'] }, test: { - files: ['test/models/*.js'], + files: ['test/*.js'], tasks: ['mochaTest'] } }, @@ -118,7 +118,7 @@ module.exports = function(grunt) { 'js/shell.js', // shell must be loaded before moment due to the way moment loads in a commonjs env 'lib/moment/min/moment.min.js', 'lib/qrcode-generator/js/qrcode.js', - 'lib/underscore/underscore.js', + 'lib/lodash/dist/lodash.js', 'lib/bitcore.js', 'lib/file-saver/FileSaver.js', 'lib/socket.io-client/socket.io.js', @@ -202,7 +202,7 @@ module.exports = function(grunt) { }, jsdoc: { dist: { - src: ['js/models/*.js', 'plugins/*.js'], + src: ['js/models/*.js', 'js/plugins/*.js'], options: { destination: 'doc', configure: 'jsdoc.conf.json', diff --git a/bower.json b/bower.json index dc63b2c21..ab97d836e 100644 --- a/bower.json +++ b/bower.json @@ -22,9 +22,9 @@ "mousetrap": "1.4.6", "zeroclipboard": "~1.3.5", "ng-idle": "*", - "underscore": "~1.7.0", "inherits": "~0.0.1", - "angular-load": "0.2.0" + "angular-load": "0.2.0", + "lodash": "~2.4.1" }, "resolutions": { "angular": "=1.2.19" diff --git a/config.js b/config.js index edde35bd6..72e9c6d30 100644 --- a/config.js +++ b/config.js @@ -3,7 +3,7 @@ var defaultConfig = { defaultLanguage: 'en', // DEFAULT network (livenet or testnet) networkName: 'livenet', - logLevel: 'debug', + logLevel: 'info', // wallet limits @@ -54,12 +54,13 @@ var defaultConfig = { verbose: 1, plugins: { - LocalStorage: true, + //LocalStorage: true, //GoogleDrive: true, //InsightStorage: true + EncryptedInsightStorage: true }, - InsightStorage: { + EncryptedInsightStorage: { url: 'https://test-insight.bitpay.com:443/api/email' }, diff --git a/copay.js b/copay.js index 0ea40a3fd..d56f9f25a 100644 --- a/copay.js +++ b/copay.js @@ -11,11 +11,9 @@ module.exports.HDParams = require('./js/models/HDParams'); // components var Async = module.exports.Async = require('./js/models/Async'); var Insight = module.exports.Insight = require('./js/models/Insight'); -var Storage = module.exports.Storage = require('./js/models/Storage'); module.exports.Identity = require('./js/models/Identity'); module.exports.Wallet = require('./js/models/Wallet'); -module.exports.WalletLock = require('./js/models/WalletLock'); module.exports.PluginManager = require('./js/models/PluginManager'); module.exports.version = require('./version').version; module.exports.commitHash = require('./version').commitHash; diff --git a/js/app.js b/js/app.js index 5209dea82..7c5c6dc87 100644 --- a/js/app.js +++ b/js/app.js @@ -1,7 +1,7 @@ 'use strict'; var copay = require('copay'); -var _ = require('underscore'); +var _ = require('lodash'); var config = defaultConfig; var localConfig = JSON.parse(localStorage.getItem('config')); var defaults = JSON.parse(JSON.stringify(defaultConfig)); diff --git a/js/controllers/createProfile.js b/js/controllers/createProfile.js index 5dae983b9..34997f5a7 100644 --- a/js/controllers/createProfile.js +++ b/js/controllers/createProfile.js @@ -2,9 +2,7 @@ angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService) { controllerUtils.redirIfLogged(); - $scope.retreiving = true; - - identityService.check($scope); + $scope.retreiving = false; $scope.createProfile = function(form) { if (form && form.$invalid) { diff --git a/js/controllers/home.js b/js/controllers/home.js index 96f893fe4..85bd6f671 100644 --- a/js/controllers/home.js +++ b/js/controllers/home.js @@ -2,9 +2,7 @@ angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService) { controllerUtils.redirIfLogged(); - $scope.retreiving = true; - - identityService.check($scope); + $scope.retreiving = false; $scope.openProfile = function(form) { if (form && form.$invalid) { diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js index 51e2c2362..24b89c36b 100644 --- a/js/controllers/sidebar.js +++ b/js/controllers/sidebar.js @@ -96,7 +96,7 @@ angular.module('copayApp.controllers').controller('SidebarController', function( var wids = _.pluck($rootScope.iden.listWallets(), 'id'); _.each(wids, function(wid) { if (controllerUtils.isFocusedWallet(wid)) return; - var w = $rootScope.iden.getOpenWallet(wid); + var w = $rootScope.iden.getWalletById(wid); $scope.wallets.push(w); controllerUtils.updateBalance(w, function(err, res) { if (err) return; diff --git a/js/log.js b/js/log.js index f43cff434..4b278eebf 100644 --- a/js/log.js +++ b/js/log.js @@ -1,5 +1,5 @@ var config = require('../config'); -var _ = require('underscore'); +var _ = require('lodash'); /** * @desc diff --git a/js/mobile.js b/js/mobile.js index 668272b69..b8d28931c 100644 --- a/js/mobile.js +++ b/js/mobile.js @@ -23,4 +23,4 @@ function onDeviceReady() { window.plugins.webintent.getUri(handleBitcoinURI); window.plugins.webintent.onNewIntent(handleBitcoinURI); window.handleOpenURL = handleBitcoinURI; -} \ No newline at end of file +} diff --git a/js/models/HDParams.js b/js/models/HDParams.js index 04872eedd..dba208e28 100644 --- a/js/models/HDParams.js +++ b/js/models/HDParams.js @@ -4,7 +4,7 @@ var preconditions = require('preconditions').singleton(); var HDPath = require('./HDPath'); -var _ = require('underscore'); +var _ = require('lodash'); /** * @desc diff --git a/js/models/HDPath.js b/js/models/HDPath.js index 041912742..7a03fa858 100644 --- a/js/models/HDPath.js +++ b/js/models/HDPath.js @@ -3,7 +3,7 @@ // 90.2% typed (by google's closure-compiler account) var preconditions = require('preconditions').singleton(); -var _ = require('underscore'); +var _ = require('lodash'); /** * @namespace diff --git a/js/models/Identity.js b/js/models/Identity.js index 6bc46b626..6a04c9189 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -1,8 +1,10 @@ 'use strict'; var preconditions = require('preconditions').singleton(); -var _ = require('underscore'); +var _ = require('lodash'); +var bitcore = require('bitcore'); var log = require('../log'); +var async = require('async'); var version = require('../../version').version; var TxProposals = require('./TxProposals'); @@ -10,25 +12,30 @@ var PublicKeyRing = require('./PublicKeyRing'); var PrivateKey = require('./PrivateKey'); var Wallet = require('./Wallet'); var PluginManager = require('./PluginManager'); -var Profile = require('./Profile'); -var Insight = module.exports.Insight = require('./Insight'); var Async = module.exports.Async = require('./Async'); -var Storage = module.exports.Storage = require('./Storage'); /** * @desc * Identity - stores the state for a wallet in creation * - * @param {Object} config - configuration for this wallet - * @param {Object} config.wallet - default configuration for the wallet + * @param {Object} opts - configuration for this wallet + * @param {string} opts.fullName + * @param {string} opts.email + * @param {string} opts.password + * @param {string} opts.storage + * @param {string} opts.pluginManager + * @param {Object} opts.walletDefaults + * @param {string} opts.version + * @param {Object} opts.wallets + * @param {Object} opts.network + * @param {string} opts.network.testnet + * @param {string} opts.network.livenet * @constructor */ - -function Identity(password, opts) { +function Identity(opts) { preconditions.checkArgument(opts); opts = _.extend({}, opts); - this.storage = Identity._getStorage(opts, password); this.networkOpts = { 'livenet': opts.network.livenet, 'testnet': opts.network.testnet, @@ -38,297 +45,205 @@ function Identity(password, opts) { 'testnet': opts.network.testnet, }; - this.pluginManager = opts.pluginManager || {}; - this.insightSaveOpts = opts.insightSave || {}; + this.fullName = opts.fullName || opts.email; + this.email = opts.email; + this.password = opts.password; + + this.storage = opts.storage || opts.pluginManager.get('DB'); + this.storage.setCredentials(this.email, this.password, {}); + this.walletDefaults = opts.walletDefaults || {}; this.version = opts.version || version; - this.wallets = {}; + this.wallets = opts.wallets || {}; }; - -/* for stubbing */ -Identity._createProfile = function(email, password, storage, cb) { - Profile.create(email, password, storage, cb); +Identity.getKeyForEmail = function(email) { + return 'profile::' + bitcore.util.sha256ripe160(email).toString('hex'); }; -Identity._newStorage = function(opts) { - return new Storage(opts); +Identity.prototype.getId = function() { + return Identity.getKeyForEmail(this.email); }; -Identity._newWallet = function(opts) { - return new Wallet(opts); +Identity.prototype.getName = function() { + return this.fullName || this.email; }; -Identity._walletFromObj = function(o, readOpts) { - return Wallet.fromObj(o, readOpts); -}; +/** + * Creates an Identity + * + * @param opts + * @param cb + * @return {undefined} + */ +Identity.create = function(opts, cb) { + opts = _.extend({}, opts); -Identity._walletDelete = function(id, s, cb) { - return Wallet.delete(id, s, cb); -}; - -/* for stubbing */ -Identity._openProfile = function(email, password, storage, cb) { - Profile.open(email, password, storage, cb); -}; - -/* for stubbing */ -Identity._newAsync = function(opts) { - return new Async(opts); -}; - -Identity._getStorage = function(opts, password) { - var storageOpts = {}; - - if (opts.pluginManager) { - storageOpts = _.clone({ - db: opts.pluginManager.get('DB'), - passphraseConfig: opts.passphraseConfig, - }); + var iden = new Identity(opts); + if (opts.noWallets) { + return cb(null, iden); + } else { + return iden.createDefaultWallet(opts, cb); } - if (password) - storageOpts.password = password; - - return Identity._newStorage(storageOpts); }; /** - * check if any profile exists on storage + * Create a wallet, 1-of-1 named general * - * @param opts.storageOpts - * @param cb + * @param {Object} opts + * @param {Object} opts.walletDefaults + * @param {string} opts.walletDefaults.networkName */ -Identity.anyProfile = function(opts, cb) { - var storage = Identity._getStorage(opts); - storage.getFirst(Profile.key(''), { - onlyKey: true - }, function(err, v, k) { - return cb(k ? true : false); - }); -}; - -/** - * check if any wallet exists on storage - * - * @param opts.storageOpts - * @param cb - */ -Identity.anyWallet = function(opts, cb) { - var storage = Identity._getStorage(opts); - storage.getFirst(Wallet.getStorageKey(''), { - onlyKey: true - }, function(err, v, k) { - return cb(k ? true : false); - }); -}; - -/** - * creates and Identity - * - * @param email - * @param password - * @param opts - * @param cb - * @return {undefined} - */ -Identity.create = function(email, password, opts, cb) { - opts = opts || {}; - - var iden = new Identity(password, opts); - - Identity._createProfile(email, password, iden.storage, function(err, profile) { - if (err) return cb(err); - iden.profile = profile; - - if (opts.noWallets) - cb(null, iden); - - // default wallet - var dflt = _.clone(opts.walletDefaults); - var wopts = _.extend(dflt, { - nickname: email, - networkName: opts.networkName, - requiredCopayers: 1, - totalCopayers: 1, - password: password, - name: 'general' - }); - iden.createWallet(wopts, function(err, w) { - if (err) { - return cb(err); - } - if (iden.pluginManager.get && iden.pluginManager.get('remote-backup')) { - iden.pluginManager.get('remote-backup').store( - iden, - iden.insightSaveOpts, - function(error) { - // FIXME: Ignoring this error may not be the best thing to do. But remote storage - // is not required for the user to use the wallet. - return cb(null, iden, w); - } - ); - } else { - return cb(null, iden, w); - } - }); - }); -}; - -/** - * validates Profile's email - * - * @param authcode - * @param cb - * @return {undefined} - */ -Identity.prototype.validate = function(authcode, cb) { - // TODO - console.log('[Identity.js.99] TODO: Should validate email thru authcode'); //TODO - return cb(); -}; - - -/** - * open's an Identity from storage - * - * @param email - * @param password - * @param opts - * @param cb - * @return {undefined} - */ -Identity.open = function(email, password, opts, cb) { - var iden = new Identity(password, opts); - - Identity._openProfile(email, password, iden.storage, function(err, profile) { - if (err) { - if (err.message && err.message.indexOf('PNOTFOUND') !== -1) { - if (opts.pluginManager && opts.pluginManager.get('remote-backup')) { - return opts.pluginManager.get('remote-backup').retrieve(email, password, opts, cb); - } else { - return cb(err); - } - } - return cb(err); - } - iden.profile = profile; - - var wids = _.pluck(iden.listWallets(), 'id'); - if (!wids || !wids.length) - return cb(new Error('Could not open any wallet from profile'), iden); - - // Open All wallets from profile - //This could be optional, or opts.onlyOpen = wid - var wallets = []; - var remaining = wids.length; - _.each(wids, function(wid) { - iden.openWallet(wid, function(err, w) { - if (err) { - log.error('Cound not open wallet id:' + wid + '. Skipping') - iden.profile.deleteWallet(wid, function() {}); - } else { - log.info('Open wallet id:' + wid + ' opened'); - wallets.push(w); - } - if (--remaining == 0) { - var lastFocused = iden.profile.getLastFocusedWallet(); - return cb(err, iden, lastFocused); - } - }) - }); - }); -}; - -/** - * isAvailable - * - * @param email - * @param opts - * @param cb - * @return {undefined} - */ -Identity.isAvailable = function(email, opts, cb) { - console.log('[Identity.js.127:isAvailable:] TODO'); //TODO - return cb(); -}; - -Identity.prototype.readWallet = function(walletId, readOpts, cb) { - preconditions.checkArgument(cb); - var self = this, - err; - var obj = {}; - - this.storage.getFirst(Wallet.getStorageKey(walletId), {}, function(err, obj) { - if (err) return cb(err); - - if (!obj) - return cb(new Error('WNOTFOUND: Wallet not found')); - - var w, err; - obj.id = walletId; - - try { - log.debug('## OPENING Wallet: ' + walletId); - w = Wallet.fromUntrustedObj(obj, readOpts); - } catch (e) { - log.debug("ERROR: ", e.message); - if (e && e.message && e.message.indexOf('MISSOPTS')) { - err = new Error('WERROR: Could not read: ' + walletId + ': ' + e.message); - } else { - err = e; - } - w = null; - } - return cb(err, w); - }); -}; - -Identity.prototype.storeWallet = function(w, cb) { - preconditions.checkArgument(w && _.isObject(w)); - - var id = w.getId(); - var val = w.toObj(); - var key = Wallet.getStorageKey(id + '_' + w.getName()); - - this.storage.set(key, val, function(err) { - log.debug('Wallet:' + w.getName() + ' stored'); - - if (cb) - cb(err); - }); -}; - - -/** - * store - * - * @param opts - * @param cb - * @return {undefined} - */ -Identity.prototype.store = function(opts, cb) { - preconditions.checkState(this.profile); +Identity.prototype.createDefaultWallet = function(opts, callback) { var self = this; - self.profile.store(opts, function(err) { - if (err) return cb(err); - - var l = Object.keys(self.wallets), - i = 0; - if (!l) return cb(); - - _.each(self.wallets, function(w) { - self.storeWallet(w, function(err) { - if (err) return cb(err); - - if (++i == l) - return cb(); - }) - }); + var walletOptions = _.extend(opts.walletDefaults, { + nickname: this.fullName || this.email, + networkName: opts.networkName, + requiredCopayers: 1, + totalCopayers: 1, + password: this.password, + name: 'general' + }); + this.createWallet(walletOptions, function(err, wallet) { + if (err) { + return callback(err); + } + return callback(null, self); }); }; +/** + * Open an Identity from the given storage + * + * @param {Object} opts + * @param {Object} opts.storage + * @param {string} opts.email + * @param {string} opts.password + * @param {Function} cb + */ +Identity.open = function(opts, cb) { + + var storage = opts.storage || opts.pluginManager.get('DB'); + storage.setCredentials(opts.email, opts.password, opts); + storage.getItem(Identity.getKeyForEmail(opts.email), function(err, data) { + if (err) { + return cb(err); + } + return Identity.createFromPartialJson(data, opts, cb); + }); +}; + +/** + * Creates an Identity, retrieves all Wallets remotely, and activates network + * + * @param {string} jsonString - a string containing a json object with options to rebuild the identity + * @param {Object} opts + * @param {Function} cb + */ +Identity.createFromPartialJson = function(jsonString, opts, callback) { + var exported; + try { + exported = JSON.parse(jsonString); + } catch (e) { + return callback('Invalid JSON'); + } + var identity = new Identity(_.extend(opts, exported)); + async.map(exported.walletIds, function(walletId, callback) { + identity.retrieveWalletFromStorage(walletId, function(error, wallet) { + if (!error) { + identity.wallets[wallet.getId()] = wallet; + wallet.netStart(); + } + callback(error, wallet); + }); + }, function(err) { + return callback(err, identity); + }); +}; + +/** + * @param {string} walletId + * @param {Function} callback + */ +Identity.prototype.retrieveWalletFromStorage = function(walletId, callback) { + var self = this; + this.storage.getItem(Wallet.getStorageKey(walletId), function(error, walletData) { + if (error) { + return callback(error); + } + try { + log.debug('## OPENING Wallet: ' + walletId); + if (_.isString(walletData)) { + walletData = JSON.parse(walletData); + } + var readOpts = { + networkOpts: self.networkOpts, + blockchainOpts: self.blockchainOpts, + skipFields: [] + }; + return callback(null, Wallet.fromUntrustedObj(walletData, readOpts)); + + } catch (e) { + + log.debug("ERROR: ", e.message); + if (e && e.message && e.message.indexOf('MISSOPTS') !== -1) { + return callback(new Error('WERROR: Could not read: ' + walletId + ': ' + e.message)); + } else { + return callback(e); + } + } + }); +}; + +/** + * TODO (matiu): What is this supposed to do? + */ +Identity.isAvailable = function(email, opts, cb) { + return cb(); +}; + +/** + * @param {Wallet} wallet + * @param {Function} cb + */ +Identity.prototype.storeWallet = function(wallet, cb) { + preconditions.checkArgument(w && _.isObject(wallet)); + + var val = wallet.toObj(); + var key = wallet.getStorageKey(); + + this.storage.setItem(key, val, function(err) { + if (err) { + log.debug('Wallet:' + w.getName() + ' couldnt be stored'); + return cb(err); + } + return cb(); + }); +}; + +Identity.prototype.toObj = function() { + return _.extend({walletIds: _.keys(this.wallets)}, + _.pick(this, 'version', 'fullName', 'password', 'email')); +}; + +Identity.prototype.exportWithWalletInfo = function() { + return _.extend({wallets: _.map(this.wallets, function(wallet) { return wallet.toObj(); })}, + _.pick(this, 'version', 'fullName', 'password', 'email')); +}; + +/** + * @param {Object} opts + * @param {Function} cb + */ +Identity.prototype.store = function(opts, cb) { + var self = this; + self.storage.setItem(this.getId(), this.toObj(), function(err) { + if (err) return cb(err); + async.map(self.wallets, self.storeWallet, cb); + }); +}; Identity.prototype._cleanUp = function() { // NOP @@ -338,28 +253,11 @@ Identity.prototype._cleanUp = function() { * @desc Closes the wallet and disconnects all services */ Identity.prototype.close = function(cb) { - preconditions.checkState(this.profile); - - var l = Object.keys(this.wallets), - i = 0; - if (!l) { - return cb ? cb() : null; - } - - var self = this; - _.each(this.wallets, function(w) { - w.close(function(err) { - if (err) return cb(err); - - if (++i == l) { - self._cleanUp(); - if (cb) return cb(); - } - }) - }); + async.map(this.wallets, function(wallet, callback) { + wallet.close(callback); + }, cb); }; - /** * @desc Imports a wallet from an encrypted base64 object * @param {string} base64 - the base64 encoded object @@ -392,18 +290,20 @@ Identity.prototype.importWallet = function(base64, password, skipFields, cb) { }); }; -Identity.prototype.closeWallet = function(wid, cb) { - var w = this.getOpenWallet(wid); - preconditions.checkState(w, 'Wallet not found'); +/** + * @param {Wallet} wallet + * @param {Function} cb + */ +Identity.prototype.closeWallet = function(wallet, cb) { + preconditions.checkState(wallet, 'Wallet not found'); - var self = this; - w.close(function(err) { + wallet.close(function(err) { delete self.wallets[wid]; return cb(err); }); }; -Identity.importFromJson = function(str, password, opts, cb) { +Identity.importFromFullJson = function(str, password, opts, cb) { preconditions.checkArgument(str); var json; try { @@ -415,59 +315,29 @@ Identity.importFromJson = function(str, password, opts, cb) { if (!_.isNumber(json.iterations)) return cb('BADSTR: Missing iterations'); - if (!json.profile) - return cb('BADSTR: Missing profile'); - - var iden = new Identity(password, opts); - iden.profile = Profile.import(json.profile, password, iden.storage); + var email = json.email; + var iden = new Identity(email, password, opts); json.wallets = json.wallets || {}; - var walletInfoBackup = iden.profile.walletInfos; - iden.profile.walletInfos = {}; - - var l = _.size(json.wallets), - i = 0; - - if (!l) - return cb(null, iden); - - _.each(json.wallets, function(wstr) { + async.map(json.wallets, function(walletData, callback) { iden.importWallet(wstr, password, opts.skipFields, function(err, w) { - if (err) return cb(err); + if (err) return callback(err); log.debug('Wallet ' + w.getId() + ' imported'); - - if (++i == l) { - iden.profile.walletInfos = walletInfoBackup; - iden.store(opts, function(err) { - if (err) { - return cb(err); - } else { - return cb(null, iden, iden.openWallets[0]); - } - }); + callback(); + }); + }, function(err, results) { + if (err) { + return cb(err); + } + iden.store(function(err) { + if (err) { + return cb(err); } - }) + return cb(null, iden); + }); }); }; -/** - * @desc Return JSON with base64 encoded strings for wallets and profile, and iteration count - * @return {string} Stringify JSON - */ -Identity.prototype.exportAsJson = function() { - var ret = {}; - ret.iterations = this.storage.iterations; - ret.profile = this.profile.export(); - ret.wallets = {}; - - _.each(this.wallets, function(w) { - ret.wallets[w.getId()] = w.export(); - }); - - var r = JSON.stringify(ret); - return r; -}; - Identity.prototype.bindWallet = function(w) { var self = this; @@ -502,7 +372,6 @@ Identity.prototype.bindWallet = function(w) { */ Identity.prototype.createWallet = function(opts, cb) { preconditions.checkArgument(cb); - preconditions.checkState(this.profile); opts = opts || {}; opts.networkName = opts.networkName || 'testnet'; @@ -530,7 +399,7 @@ Identity.prototype.createWallet = function(opts, cb) { }); opts.publicKeyRing.addCopayer( opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), - opts.nickname || this.profile.getName() + opts.nickname || this.getName() ); log.debug('\t### PublicKeyRing Initialized'); @@ -550,35 +419,29 @@ Identity.prototype.createWallet = function(opts, cb) { opts.version = opts.version || this.version; var self = this; - var w = Identity._newWallet(opts); + var w = new Wallet(opts); this.addWallet(w, function(err) { if (err) return cb(err); self.bindWallet(w); - if (self.pluginManager.get && self.pluginManager.get('remote-backup')) { - self.pluginManager.get('remote-backup').store(self, self.insightSaveOpts, _.noop); - } - w.netStart(); - return cb(null, w); + self.storage.setItem(self.getId(), self.toObj(), function(error) { + if (error) { + return callback(error); + } + w.netStart(); + return cb(null, w); + }); }); }; - -// add wallet (import) Identity.prototype.addWallet = function(wallet, cb) { preconditions.checkArgument(wallet); preconditions.checkArgument(wallet.getId); preconditions.checkArgument(cb); - preconditions.checkState(this.profile); - var self = this; - self.profile.addWallet(wallet.getId(), { - name: wallet.name - }, function(err) { - if (err) return cb(err); - self.storeWallet(wallet, function(err) { - return cb(err); - }); - }); + this.wallets[wallet.getId()] = wallet; + + // TODO (eordano): Consider not saving automatically after this + this.storage.setItem(wallet.getStorageKey(), wallet.toObj(), cb); }; @@ -605,59 +468,37 @@ Identity.prototype._checkVersion = function(inVersion) { }; /** - * @desc Retrieve a wallet from the storage - * @param {string} walletId - the id of the wallet - * @param {function} callback (err, {Wallet}) - * @return + * @param {string} walletId + * @returns {Wallet} */ -Identity.prototype.openWallet = function(walletId, cb) { - preconditions.checkArgument(cb); - preconditions.checkState(this.storage.hasPassphrase()); - - var self = this; - // TODO - // self.migrateWallet(walletId, password, function() { - // - - self.readWallet(walletId, { - networkOpts: this.networkOpts, - blockchainOpts: this.blockchainOpts - }, function(err, w) { - if (err) return cb(err); - self.bindWallet(w); - self.storeWallet(w, function(err) { - w.netStart(); - return cb(err, w); - }); - }); - // }); -}; - - -Identity.prototype.getOpenWallet = function(id) { - return this.wallets[id]; -}; - - -Identity.prototype.listWallets = function() { - var ret = this.profile.listWallets(); - return ret; +Identity.prototype.getWalletById = function(walletId) { + return this.wallets[walletId]; }; /** - * @desc Deletes this wallet. This involves removing it from the storage instance + * @returns {Wallet[]} + */ +Identity.prototype.listWallets = function() { + return _.values(this.wallets); +}; + +/** + * @desc Deletes a wallet. This involves removing it from the storage instance + * * @param {string} walletId * @callback cb * @return {err} */ Identity.prototype.deleteWallet = function(walletId, cb) { var self = this; - Identity._walletDelete(walletId, this.storage, function(err) { - if (err) return cb(err); - self.profile.deleteWallet(walletId, function(err) { + + delete this.wallets[walletId]; + this.storage.deleteItem(walletId, function(err) { + if (err) { return cb(err); - }); - }) + } + self.store(cb); + }); }; /** @@ -671,6 +512,12 @@ Identity.prototype.decodeSecret = function(secret) { } }; +Identity.prototype.getLastFocusedWallet = function() { + return _.max(this.wallets, function(wallet) { + return wallet.lastTimestamp || 0; + }); +}; + /** * @callback walletCreationCallback * @param {?} err - an error, if any, that happened during the wallet creation @@ -720,7 +567,7 @@ Identity.prototype.joinWallet = function(opts, cb) { }; - var joinNetwork = Identity._newAsync(this.networkOpts[decodedSecret.networkName]); + var joinNetwork = opts.Async || new Async(this.networkOpts[decodedSecret.networkName]); // This is a hack to reconize if the connection was rejected or the peer wasn't there. var connectedOnce = false; @@ -751,7 +598,7 @@ Identity.prototype.joinWallet = function(opts, cb) { walletOpts.network = joinNetwork; walletOpts.privateKey = privateKey; - walletOpts.nickname = opts.nickname || self.profile.getName(); + walletOpts.nickname = opts.nickname || self.getName(); if (opts.password) walletOpts.password = opts.password; diff --git a/js/models/Passphrase.js b/js/models/Passphrase.js index c57ee6662..91b67f39d 100644 --- a/js/models/Passphrase.js +++ b/js/models/Passphrase.js @@ -7,7 +7,7 @@ var sjcl = require('../../lib/sjcl'); var preconditions = require('preconditions').instance(); -var _ = require('underscore'); +var _ = require('lodash'); /** diff --git a/js/models/PluginManager.js b/js/models/PluginManager.js index 50e720e18..2a2788b61 100644 --- a/js/models/PluginManager.js +++ b/js/models/PluginManager.js @@ -30,7 +30,6 @@ var KIND_MULTIPLE = PluginManager.KIND_MULTIPLE = 2; PluginManager.TYPE = {}; PluginManager.TYPE['DB'] = KIND_UNIQUE; -PluginManager.TYPE['remote-backup'] = KIND_UNIQUE; PluginManager.prototype._register = function(obj, name) { preconditions.checkArgument(obj.type, 'Plugin has not type:' + name); diff --git a/js/models/PrivateKey.js b/js/models/PrivateKey.js index e8dcd8737..9ae522814 100644 --- a/js/models/PrivateKey.js +++ b/js/models/PrivateKey.js @@ -7,7 +7,7 @@ var HK = bitcore.HierarchicalKey; var WalletKey = bitcore.WalletKey; var networks = bitcore.networks; var util = bitcore.util; -var _ = require('underscore'); +var _ = require('lodash'); var preconditions = require('preconditions').instance(); var HDPath = require('./HDPath'); diff --git a/js/models/Profile.js b/js/models/Profile.js deleted file mode 100644 index aeb2d6d79..000000000 --- a/js/models/Profile.js +++ /dev/null @@ -1,188 +0,0 @@ -'use strict'; -var preconditions = require('preconditions').singleton(); -var _ = require('underscore'); -var log = require('../log'); -var bitcore = require('bitcore'); - -function Profile(info, storage) { - preconditions.checkArgument(info.email); - preconditions.checkArgument(info.hash); - preconditions.checkArgument(storage); - preconditions.checkArgument(storage.setPassword, 'bad storage'); - - this.password = info.password; - this.hash = info.hash; - this.email = info.email; - this.extra = info.extra || {}; - this.walletInfos = info.walletInfos || {}; - - this.key = Profile.key(this.hash); - this.storage = storage; -}; - -Profile.hash = function(email) { - return bitcore.util.sha256ripe160(email).toString('hex'); -}; - -Profile.key = function(hash) { - return 'profile::' + hash; -}; - - -Profile.create = function(email, password, storage, cb) { - preconditions.checkArgument(cb); - preconditions.checkArgument(storage.setPassword); - - preconditions.checkState(storage.hasPassphrase()); - - var p = new Profile({ - email: email, - password: password, - hash: Profile.hash(email, password), - }, storage); - p.store({}, function(err) { - return cb(err, p); - }); -}; - - -Profile.open = function(email, password, storage, cb) { - preconditions.checkArgument(cb); - preconditions.checkState(storage.hasPassphrase()); - - var key = Profile.key(Profile.hash(email, password)); -console.log('[Profile.js.59:key:]',key); //TODO - storage.get(key, function(err, val) { - if (err || !val) - return cb(new Error('PNOTFOUND: Profile not found')); - - if (!val.email) - return cb(new Error('PERROR: Could not open profile')); - - return cb(null, new Profile(val, storage)); - }); -}; - -Profile.prototype.toObj = function() { - return _.clone(_.pick(this, 'password', 'hash', 'email', 'extra', 'walletInfos')); -}; - - -/* - * @desc Return a base64 encrypted version of the wallet - * @return {string} base64 encoded string - */ -Profile.prototype.export = function() { - var profObj = this.toObj(); - return this.storage.encrypt(profObj); -}; - -/* - * @desc Return a base64 encrypted version of the wallet - * @return {string} base64 encoded string - */ -Profile.import = function(str, password, storage) { - var obj = storage.decrypt(str,password) - return new Profile(obj, storage); -}; - -Profile.prototype.getWallet = function(walletId, cb) { - return this.walletInfos[walletId]; -}; - -Profile.prototype.listWallets = function() { - return _.sortBy(this.walletInfos, function(winfo) { - return winfo.createdTs; - }); -}; - - -Profile.prototype.deleteWallet = function(walletId, cb) { - if (!this.walletInfos[walletId]) - return cb(new Error('WNOEXIST: Wallet not on profile ')); - - delete this.walletInfos[walletId]; - this.store({ - overwrite: true - }, cb); -}; - -Profile.prototype.addToWallet = function(walletId, info, cb) { - if (!this.walletInfos[walletId]) - return cb(new Error('WNOEXIST: Wallet not on profile ')); - - this.walletInfos[walletId] = _.extend(this.walletInfos[walletId], info); - - this.store({ - overwrite: true - }, cb); -}; - - - -Profile.prototype.addWallet = function(walletId, info, cb) { - preconditions.checkArgument(cb); - - if (this.walletInfos[walletId]) - return cb(new Error('WEXIST: Wallet already on profile ')); - - this.walletInfos[walletId] = _.extend(info, { - createdTs: Date.now(), - id: walletId - }); - - this.store({ - overwrite: true - }, cb); -}; - - -Profile.prototype.setLastFocusedTs = function(walletId, cb) { - return this.addToWallet(walletId, { - lastFocusedTs: Date.now() - }, cb); -}; - -Profile.prototype.getLastFocusedWallet = function() { - var self = this; - var last; - var maxTs = _.max(_.pluck(self.walletInfos, 'lastFocusedTs')); - var wallets = _.values(self.walletInfos); - - last = _.findWhere(wallets, { - lastFocusedTs: maxTs - }); - - if (!last) { - last = _.last(wallets); - } - - return last ? last.id : null; -}; - -Profile.prototype.store = function(opts, cb) { - var self = this; - var val = self.toObj(); - var key = self.key; - - self.storage.get(key, function(val2) { - - if (val2 && !opts.overwrite) { - if (cb) - return cb(new Error('PEXISTS: Profile already exist ')) - } else { - self.storage.set(key, val, function(err) { - log.debug('Profile stored'); - if (cb) - cb(err); - }); - } - }); -}; - - -Profile.prototype.getName = function() { - return this.extra.nickname || this.email; -}; - -module.exports = Profile; diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js index 9512986ed..da9d745f1 100644 --- a/js/models/PublicKeyRing.js +++ b/js/models/PublicKeyRing.js @@ -1,7 +1,7 @@ 'use strict'; var preconditions = require('preconditions').instance(); -var _ = require('underscore'); +var _ = require('lodash'); var log = require('../log'); var bitcore = require('bitcore'); var HK = bitcore.HierarchicalKey; diff --git a/js/models/Storage.js b/js/models/Storage.js deleted file mode 100644 index fe43d2580..000000000 --- a/js/models/Storage.js +++ /dev/null @@ -1,387 +0,0 @@ -'use strict'; -var preconditions = require('preconditions').singleton(); -var CryptoJS = require('node-cryptojs-aes').CryptoJS; -var bitcore = require('bitcore'); -var Passphrase = require('./Passphrase'); -var preconditions = require('preconditions').instance(); -var log = require('../log'); -var _ = require('underscore'); -var CACHE_DURATION = 1000 * 60 * 5; -var id = 0; - - -/* - * Storage wraps db plugin primitives - * with encryption functionalities - * and adds from some extra functionalities - * and a common interfase - */ -function Storage(opts) { - preconditions.checkArgument(opts); - preconditions.checkArgument(!opts.passphrase); - - this.wListCache = {}; - this.__uniqueid = ++id; - this.passphraseConfig = opts.passphraseConfig; - - if (opts.password) - this.setPassword(opts.password); - - try { - this.db = opts.db || localStorage; - this.sessionStorage = opts.sessionStorage || sessionStorage; - } catch (e) { - console.log('Error in storage:', e); - }; - - preconditions.checkState(this.db, 'No db defined'); - preconditions.checkState(this.sessionStorage, 'No sessionStorage defined'); -} - -var pps = {}; -Storage.prototype._getPassphrase = function() { - - if (!pps[this.__uniqueid]) - throw new Error('NOPASSPHRASE: No passphrase set'); - - return pps[this.__uniqueid]; -}; - -Storage.prototype.savePassphrase = function() { - if (!pps[this.__uniqueid]) - throw new Error('NOPASSPHRASE: No passphrase set'); - - this.savedPassphrase = this.savedPassphrase || {}; - this.savedPassphrase[this.__uniqueid] = { - pps: pps[this.__uniqueid], - iterations: this.iterations, - }; -}; - - -Storage.prototype.restorePassphrase = function() { - if (!this.savedPassphrase[this.__uniqueid]) - throw new Error('NOSTOREDPASSPHRASE: No stored passphrase'); - - this._setPassphrase(this.savedPassphrase[this.__uniqueid].pps, this.savedPassphrase[this.__uniqueid].iterations); -}; - -Storage.prototype.hasPassphrase = function() { - return pps[this.__uniqueid] ? true : false; -}; - - -Storage.prototype._setPassphrase = function(passphrase, iterations) { - pps[this.__uniqueid] = passphrase; - this.iterations = iterations; -}; - -Storage.prototype.setPassword = function(password, config) { - var passphraseConfig = _.extend(this.passphraseConfig, config); - var p = new Passphrase(passphraseConfig); - log.debug('Setting passphrase... Iterations:' + passphraseConfig.iterations); - this._setPassphrase(p.getBase64(password), passphraseConfig.iterations); - log.debug('done.') -} - -Storage.prototype._encrypt = function(string) { - var encrypted = CryptoJS.AES.encrypt(string, this._getPassphrase()); - var encryptedBase64 = encrypted.toString(); - return encryptedBase64; -}; - -Storage.prototype._decrypt = function(base64) { - var decryptedStr = null; - try { - var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase()); - if (decrypted) - decryptedStr = decrypted.toString(CryptoJS.enc.Utf8); - } catch (e) { - // Error while decrypting - log.debug(e.message); - return null; - } - return decryptedStr; -}; - - -Storage.prototype._read = function(k, cb) { - preconditions.checkArgument(cb); - - var self = this; - this.db.getItem(k, function(ret) { - if (!ret) return cb(null); - ret = self._decrypt(ret); - if (!ret) return cb(null); - - ret = ret.toString(CryptoJS.enc.Utf8); - ret = JSON.parse(ret); - return cb(ret); - }); -}; - -Storage.prototype._write = function(k, v, cb) { - preconditions.checkArgument(cb); - - v = JSON.stringify(v); - v = this._encrypt(v); - this.db.setItem(k, v, cb); -}; - -// get value by key -Storage.prototype.getGlobal = function(k, cb) { - preconditions.checkArgument(cb); - - this.db.getItem(k, function(item) { - cb(item == 'undefined' ? undefined : item); - }); -}; - -// set value for key -Storage.prototype.setGlobal = function(k, v, cb) { - preconditions.checkArgument(cb); - this.db.setItem(k, typeof v === 'object' ? JSON.stringify(v) : v, cb); -}; - -// remove value for key -Storage.prototype.removeGlobal = function(k, cb) { - preconditions.checkArgument(cb); - this.db.removeItem(k, cb); -}; - -Storage.prototype.getSessionId = function(cb) { - preconditions.checkArgument(cb); - var self = this; - - self.sessionStorage.getItem('sessionId', function(sessionId) { - if (sessionId) - return cb(sessionId); - - sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex'); - self.sessionStorage.setItem('sessionId', sessionId, function() { - return cb(sessionId); - }); - }); -}; - -Storage.prototype.setSessionId = function(sessionId, cb) { - this.sessionStorage.setItem('sessionId', sessionId, cb); -}; - -Storage.prototype.get = function(key, cb) { - var self = this; - self._read(key, function(v) { - return cb(null, v); - }) -}; - -Storage.prototype.getFirst = function(prefix, opts, cb) { - opts = opts || {}; - - var self = this; - this.db.allKeys(function(allKeys) { - var keys = _.filter(allKeys, function(k) { - if ((k === prefix) || k.indexOf(prefix) === 0) return true; - }); - - if (keys.length === 0) - return cb(new Error('not found')); - - if (opts.onlyKey) - return cb(null, null, keys[0]); - - self._read(keys[0], function(v) { - if (_.isNull(v)) return cb(new Error('Could not decrypt data'), null, keys[0]); - return cb(null, v, keys[0]); - }) - }); -}; - -Storage.prototype.set = function(key, obj, cb) { - preconditions.checkArgument(key); - preconditions.checkArgument(cb); - this._write(key, obj, function() { - return cb(); - }); -}; - -Storage.prototype.delete = function(key, cb) { - preconditions.checkArgument(cb); - this.removeGlobal(key, function() { - return cb(); - }); -}; - -Storage.prototype.deletePrefix = function(prefix, cb) { - var self = this; - this.getFirst(prefix, {}, function(err, v, k) { - if (err || !v) return cb(err); - - self.delete(k, function(err) { - if (err) return cb(err); - self.deletePrefix(prefix, cb); - }) - }); -}; - - -Storage.prototype.clearAll = function(cb) { - this.sessionStorage.clear(); - this.db.clear(cb); -}; - -Storage.prototype.decrypt = function(base64, password, iterations) { - - if (password) { - this.savePassphrase(); - var opts = iterations ? {iterations: iterations} : {}; - - this.setPassword(password, opts); - } - - var decryptedStr = this._decrypt(base64); - var ret = JSON.parse(decryptedStr); - - if (password) - this.restorePassphrase(); - - return ret; -}; - -Storage.prototype.encrypt = function(obj) { - var string = JSON.stringify(obj); - return this._encrypt(string); -}; - -/* - * OLD functions, only for temporary backwards compatibility - */ - - -Storage.prototype.readWallet_Old = function(walletId, cb) { - var self = this; - this.db.allKeys(function(allKeys) { - var obj = {}; - var keys = _.filter(allKeys, function(k) { - if (k.indexOf(walletId + '::') === 0) return true; - }); - if (keys.length === 0) return cb(new Error('Wallet ' + walletId + ' not found')); - var count = keys.length; - _.each(keys, function(k) { - self._read(k, function(v) { - obj[k.split('::')[1]] = v; - if (--count === 0) return cb(null, obj); - }) - }); - }); -}; - - -Storage.prototype.deleteWallet_Old = function(walletId, cb) { - preconditions.checkArgument(walletId); - preconditions.checkArgument(cb); - var err; - var self = this; - - var toDelete = {}; - - this.db.allKeys(function(allKeys) { - for (var ii in allKeys) { - var key = allKeys[ii]; - var split = key.split('::'); - if (split.length == 2 && split[0] === walletId) { - toDelete[key] = 1; - }; - } - var l = Object.keys(toDelete).length, - j = 0; - if (!l) - return cb(new Error('WNOTFOUND: Wallet not found')); - - toDelete['nameFor::' + walletId] = 1; - l++; - - for (var i in toDelete) { - self.removeGlobal(i, function() { - if (++j == l) - return cb(err); - }); - - } - }); -}; - - -// TODO -Storage.prototype._getWalletIds_Old = function(cb) { - preconditions.checkArgument(cb); - var walletIds = []; - var uniq = {}; - this.db.allKeys(function(keys) { - for (var ii in keys) { - var key = keys[ii]; - var split = key.split('::'); - if (split.length == 2) { - var walletId = split[0]; - - if (!walletId || walletId === 'nameFor' || walletId === 'lock' || walletId === 'wallet') - continue; - - if (typeof uniq[walletId] === 'undefined') { - walletIds.push(walletId); - uniq[walletId] = 1; - } - } - } - return cb(walletIds); - }); -}; - -// TODO -Storage.prototype.getWallets1_Old = function(cb) { - preconditions.checkArgument(cb); - - if (this.wListCache.ts > Date.now()) - return cb(this.wListCache.data) - - var wallets = []; - var self = this; - - this._getWalletIds_Old(function(ids) { - var l = ids.length, - i = 0; - if (!l) - return cb([]); - - _.each(ids, function(id) { - self.getGlobal('nameFor::' + id, function(name) { - wallets.push({ - id: id, - name: name, - }); - if (++i == l) { - self.wListCache.data = wallets; - self.wListCache.ts = Date.now() + CACHE_DURATION; - return cb(wallets); - } - }); - }); - }); -}; - - -Storage.prototype.getWallets_Old = function(cb) { - var self = this; - self.getWallets2_Old(function(wallets) { - self.getWallets1_Old(function(wallets2) { - var ids = _.pluck(wallets, 'id'); - _.each(wallets2, function(w) { - if (!_.contains(ids, w.id)) - wallets.push(w); - }); - return cb(wallets); - }); - }) -}; - -module.exports = Storage; diff --git a/js/models/TxProposal.js b/js/models/TxProposal.js index 557a64c34..ae9de2dd9 100644 --- a/js/models/TxProposal.js +++ b/js/models/TxProposal.js @@ -1,7 +1,7 @@ 'use strict'; var bitcore = require('bitcore'); -var _ = require('underscore'); +var _ = require('lodash'); var util = bitcore.util; var Transaction = bitcore.Transaction; var BuilderMockV0 = require('./BuilderMockV0');; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 8ffcb34e7..29349d705 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -1,7 +1,7 @@ 'use strict'; var EventEmitter = require('events').EventEmitter; -var _ = require('underscore'); +var _ = require('lodash'); var preconditions = require('preconditions').singleton(); var inherits = require('inherits'); var events = require('events'); @@ -24,7 +24,6 @@ var PublicKeyRing = require('./PublicKeyRing'); var TxProposal = require('./TxProposal'); var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); -var WalletLock = require('./WalletLock'); var Async = require('./Async'); var Insight = module.exports.Insight = require('./Insight'); var copayConfig = require('../../config'); @@ -173,12 +172,14 @@ Wallet.COPAYER_PAIR_LIMITS = { 12: 1, }; - - Wallet.getStorageKey = function(str) { return 'wallet::' + str; }; +Wallet.prototype.getStorageKey = function() { + return Wallet.getStorageKey(this.getId()); +}; + /* for stubbing */ Wallet._newInsight = function(opts) { return new Insight(opts); @@ -219,27 +220,6 @@ Wallet.getMaxRequiredCopayers = function(totalCopayers) { return Wallet.COPAYER_PAIR_LIMITS[totalCopayers]; }; -/** - * delete - * - * @param walletId - * @param storage - * @param cb - * @return {undefined} - */ -// Wallet.delete = function(walletId, storage, cb) { -// preconditions.checkArgument(cb); -// storage.deletePrefix(Wallet.getStorageKey(walletId), function(err) { -// if (err && err.message != 'not found') return cb(err); -// storage.deletePrefix(walletId + '::', function(err) { -// if (err && err.message != 'not found') return cb(err); -// return cb(); -// }); -// }); -// }; -// - - /** * @desc obtain network name from serialized wallet * @param {Object} wallet object @@ -601,11 +581,14 @@ Wallet.prototype._onAddressBook = function(senderId, data) { * @desc Updates the wallet's last modified timestamp and triggers a save * @param {number} ts - the timestamp */ -Wallet.prototype.updateTimestamp = function(ts) { +Wallet.prototype.updateTimestamp = function(ts, callback) { preconditions.checkArgument(ts); preconditions.checkArgument(_.isNumber(ts)); this.lastTimestamp = ts; // we dont store here + if (callback) { + return callback(null); + } }; /** @@ -825,13 +808,10 @@ Wallet.prototype._lockIncomming = function() { this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds()); }; - - Wallet.prototype._setBlockchainListeners = function() { var self = this; self.blockchain.removeAllListeners(); - log.debug('Setting Blockchain listeners for', this.getId()); self.blockchain.on('reconnect', function(attempts) { log.debug('Wallet:' + self.id + 'blockchain reconnect event'); @@ -1018,8 +998,6 @@ Wallet.fromUntrustedObj = function(obj, readOpts) { return Wallet.fromObj(o,readOpts); }; - - /** * @desc Retrieve the wallet state from a trusted object * @@ -1110,15 +1088,6 @@ Wallet.fromObj = function(o, readOpts) { return new Wallet(opts); }; -/** - * @desc Return a base64 encrypted version of the wallet - * @return {string} base64 encoded string - */ -// Wallet.prototype.export = function() { -// var walletObj = this.toObj(); -// return this.storage.encrypt(walletObj); -// }; - /** * @desc Send a message to other peers * @param {string[]} recipients - the pubkey of the recipients of the message diff --git a/js/models/WalletLock.js b/js/models/WalletLock.js deleted file mode 100644 index 0b1fe503d..000000000 --- a/js/models/WalletLock.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict'; - -var preconditions = require('preconditions').singleton(); - -function WalletLock(storage, walletId, timeoutMin) { - preconditions.checkArgument(storage); - preconditions.checkArgument(walletId); - - this.storage = storage; - this.timeoutMin = timeoutMin || 5; - this.key = WalletLock._keyFor(walletId); -} - - -WalletLock.prototype.init = function(cb) { - preconditions.checkArgument(cb); - var self = this; - - self.storage.getSessionId(function(sid) { - preconditions.checkState(sid); - - self.sessionId = sid; - cb(); - }); -}; - -WalletLock._keyFor = function(walletId) { - return 'lock' + '::' + walletId; -}; - -WalletLock.prototype._isLockedByOther = function(cb) { - var self = this; - - this.storage.getGlobal(this.key, function(json) { - var wl = json ? JSON.parse(json) : null; - if (!wl || !wl.expireTs) - return cb(false); - - var expiredSince = Date.now() - wl.expireTs; - if (expiredSince >= 0) - return cb(false); - - var isMyself = wl.sessionId === self.sessionId; - - if (isMyself) - return cb(false); - - // Seconds remainding - return cb(parseInt(-expiredSince / 1000)); - }); -}; - - -WalletLock.prototype._setLock = function(cb) { - preconditions.checkArgument(cb); - preconditions.checkState(this.sessionId); - var self = this; - - this.storage.setGlobal(this.key, { - sessionId: this.sessionId, - expireTs: Date.now() + this.timeoutMin * 60 * 1000, - }, function() { - - cb(null); - }); -}; - - -WalletLock.prototype._doKeepAlive = function(cb) { - preconditions.checkArgument(cb); - preconditions.checkState(this.sessionId); - - var self = this; - - this._isLockedByOther(function(t) { - if (t) - return cb(new Error('LOCKED: Wallet is locked for ' + t + ' srcs')); - - self._setLock(cb); - }); -}; - - - -WalletLock.prototype.keepAlive = function(cb) { - var self = this; - - if (!self.sessionId) { - return self.init(self._doKeepAlive.bind(self, cb)); - }; - - return this._doKeepAlive(cb); -}; - - -WalletLock.prototype.release = function(cb) { - this.storage.removeGlobal(this.key, cb); -}; - - -module.exports = WalletLock; diff --git a/js/plugins/EncryptedInsightStorage.js b/js/plugins/EncryptedInsightStorage.js new file mode 100644 index 000000000..c1c3d998a --- /dev/null +++ b/js/plugins/EncryptedInsightStorage.js @@ -0,0 +1,27 @@ +var cryptoUtil = require('../util/crypto'); +var InsightStorage = require('./InsightStorage'); +var inherits = require('inherits'); + +function EncryptedInsightStorage(config) { + InsightStorage.apply(this, [config]); +} +inherits(EncryptedInsightStorage, InsightStorage); + +EncryptedInsightStorage.prototype.getItem = function(name, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + InsightStorage.prototype.getItem.apply(this, [name, function(err, body) { + var decryptedJson = cryptoUtil.decrypt(key, body); + if (!decryptedJson) { + return callback('Internal Error'); + } + return callback(null, decryptedJson); + }]); +}; + +EncryptedInsightStorage.prototype.setItem = function(name, value, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + var record = cryptoUtil.encrypt(key, value); + InsightStorage.prototype.setItem.apply(this, [name, record, callback]); +}; + +module.exports = EncryptedInsightStorage; diff --git a/plugins/GoogleDrive.js b/js/plugins/GoogleDrive.js similarity index 98% rename from plugins/GoogleDrive.js rename to js/plugins/GoogleDrive.js index 109fc2b8c..c98149584 100644 --- a/plugins/GoogleDrive.js +++ b/js/plugins/GoogleDrive.js @@ -3,7 +3,7 @@ var preconditions = require('preconditions').singleton(); var loaded = 0; var SCOPES = 'https://www.googleapis.com/auth/drive'; -var log = require('../js/log'); +var log = require('../log'); function GoogleDrive(config) { preconditions.checkArgument(config && config.clientId, 'No clientId at GoogleDrive config'); @@ -56,6 +56,9 @@ GoogleDrive.prototype.checkAuth = function() { this.handleAuthResult.bind(this)); }; +GoogleDrive.prototype.setCredentils = function(email, password, opts, callback) { +}; + /** * Called when authorization server replies. */ diff --git a/js/plugins/InsightStorage.js b/js/plugins/InsightStorage.js new file mode 100644 index 000000000..a4d13152a --- /dev/null +++ b/js/plugins/InsightStorage.js @@ -0,0 +1,75 @@ +var request = require('request'); +var cryptoUtil = require('../util/crypto'); +var querystring = require('querystring'); +var Identity = require('../models/Identity'); + +function InsightStorage(config) { + this.type = 'DB'; + this.storeUrl = config.url || 'https://insight.is/api/email'; + this.request = config.request || request; +} + +InsightStorage.prototype.init = function () {}; + +InsightStorage.prototype.setCredentials = function(email, password, opts) { + this.email = email; + this.password = password; +}; + +InsightStorage.prototype.getItem = function(name, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + var secret = cryptoUtil.kdf(key, this.password); + var encodedEmail = encodeURIComponent(this.email); + var retrieveUrl = this.storeUrl + '/retrieve/' + encodedEmail; + this.request.get(retrieveUrl + '?' + querystring.encode({secret: secret, key: name}), + function(err, response, body) { + if (err) { + return callback('Connection error'); + } + if (response.statusCode !== 200) { + return callback('Connection error'); + } + return callback(null, body); + } + ); +}; + +InsightStorage.prototype.setItem = function(name, value, callback) { + var key = cryptoUtil.kdf(this.password, this.email); + var secret = cryptoUtil.kdf(key, this.password); + var registerUrl = this.storeUrl + '/register'; + this.request.post({ + url: registerUrl, + body: querystring.encode({ + key: name, + email: this.email, + secret: secret, + record: value + }) + }, function(err, response, body) { + if (err) { + return callback('Connection error'); + } + if (response.statusCode !== 200) { + return callback('Unable to store data on insight'); + } + return callback(); + }); +}; + +InsightStorage.prototype.removeItem = function(name, callback) { + this.setItem(name, '', callback); +}; + +InsightStorage.prototype.clear = function(callback) { + // NOOP + callback(); +}; + +InsightStorage.prototype.allKeys = function(callback) { + // NOOP + // TODO: Add functionality? + callback(); +}; + +module.exports = InsightStorage; diff --git a/plugins/LocalStorage.js b/js/plugins/LocalStorage.js similarity index 90% rename from plugins/LocalStorage.js rename to js/plugins/LocalStorage.js index d0fcebe84..b8af2ccd9 100644 --- a/plugins/LocalStorage.js +++ b/js/plugins/LocalStorage.js @@ -7,6 +7,9 @@ function LocalStorage() { LocalStorage.prototype.init = function() { }; +LocalStorage.prototype.setCredentials = function(email, password, opts) { +}; + LocalStorage.prototype.getItem = function(k,cb) { return cb(localStorage.getItem(k)); }; diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index eb5d46c39..211cfffc2 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -195,20 +195,19 @@ angular.module('copayApp.services') root.rebindWallets = function($scope, iden) { - _.each(iden.listWallets(), function(winfo) { - var w = iden.getOpenWallet(winfo.id); - preconditions.checkState(w); - root.installWalletHandlers($scope, w); + _.each(iden.listWallets(), function(wallet) { + preconditions.checkState(wallet); + root.installWalletHandlers($scope, wallet); }); }; root.setFocusedWallet = function(w) { if (!_.isObject(w)) - w = $rootScope.iden.getOpenWallet(w); + w = $rootScope.iden.getWalletById(w); preconditions.checkState(w && _.isObject(w)); $rootScope.wallet = w; - $rootScope.iden.profile.setLastFocusedTs(w.id, function() { + w.updateTimestamp(new Date().getTime(), function() { root.redirIfLogged(); root.updateBalance(w, function() { $rootScope.$digest(); @@ -226,8 +225,7 @@ angular.module('copayApp.services') } }; - - // On the focused wallet + // On the focused wallet root.updateAddressList = function(wid) { if (!wid || root.isFocusedWallet(wid)) { @@ -396,7 +394,7 @@ angular.module('copayApp.services') $rootScope.iden.deleteWallet(w.id, function() { notification.info('Wallet deleted', $filter('translate')('Wallet deleted')); $rootScope.wallet = null; - var lastFocused = $rootScope.iden.profile.getLastFocusedWallet(); + var lastFocused = $rootScope.iden.getLastFocusedWallet(); root.bindProfile($scope, $rootScope.iden, lastFocused); }); }; diff --git a/js/services/identityService.js b/js/services/identityService.js index c2fe34f93..dd94448c0 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -4,32 +4,17 @@ angular.module('copayApp.services') .factory('identityService', function($rootScope, $location, pluginManager, controllerUtils) { var root = {}; - root.check = function (scope) { - copay.Identity.anyProfile({ - pluginManager: pluginManager, - }, function(anyProfile) { - copay.Identity.anyWallet({ - pluginManager: pluginManager, - }, function(anyWallet) { - scope.retreiving = false; - scope.anyProfile = anyProfile ? true : false; - scope.anyWallet = anyWallet ? true : false; - - if (!scope.anyProfile) { - $location.path('/createProfile'); - } - }); - }); - }; - root.create = function (scope, form) { - copay.Identity.create(form.email.$modelValue, form.password.$modelValue, { + copay.Identity.create({ + email: form.email.$modelValue, + password: form.password.$modelValue, pluginManager: pluginManager, network: config.network, networkName: config.networkName, walletDefaults: config.wallet, passphraseConfig: config.passphraseConfig, - }, function(err, iden, firstWallet) { + }, function(err, iden) { + var firstWallet = iden.getLastFocusedWallet(); controllerUtils.bindProfile(scope, iden, firstWallet); scope.loading = false; }); @@ -37,19 +22,22 @@ angular.module('copayApp.services') root.open = function (scope, form) { - copay.Identity.open(form.email.$modelValue, form.password.$modelValue, { + copay.Identity.open({ + email: form.email.$modelValue, + password: form.password.$modelValue, pluginManager: pluginManager, network: config.network, networkName: config.networkName, walletDefaults: config.wallet, passphraseConfig: config.passphraseConfig, - }, function(err, iden, lastFocusedWallet) { + }, function(err, iden) { if (err && !iden) { console.log('Error:' + err) controllerUtils.onErrorDigest( scope, (err.toString() || '').match('PNOTFOUND') ? 'Profile not found' : 'Unknown error'); } else { - controllerUtils.bindProfile(scope, iden, lastFocusedWallet); + var firstWallet = iden.getLastFocusedWallet(); + controllerUtils.bindProfile(scope, iden, firstWallet); } scope.loading = false; }); diff --git a/js/services/rate.js b/js/services/rate.js index 68f96c313..6599905c1 100644 --- a/js/services/rate.js +++ b/js/services/rate.js @@ -1,18 +1,19 @@ 'use strict'; +var MINS_IN_HOUR = 60; +var MILLIS_IN_SECOND = 1000; + var RateService = function(request) { this.isAvailable = false; this.UNAVAILABLE_ERROR = 'Service is not available - check for service.isAvailable or use service.whenAvailable'; this.SAT_TO_BTC = 1 / 1e8; this.BTC_TO_SAT = 1e8; - var MINS_IN_HOUR = 60; - var MILLIS_IN_SECOND = 1000; var rateServiceConfig = config.rate; var updateFrequencySeconds = rateServiceConfig.updateFrequencySeconds || 60 * MINS_IN_HOUR; var rateServiceUrl = rateServiceConfig.url || 'https://bitpay.com/api/rates'; this.queued = []; this.alternatives = []; - var that = this; + var self = this; var backoffSeconds = 5; var retrieve = function() { request.get({ @@ -27,15 +28,15 @@ var RateService = function(request) { var rates = {}; listOfCurrencies.forEach(function(element) { rates[element.code] = element.rate; - that.alternatives.push({ + self.alternatives.push({ name: element.name, isoCode: element.code, rate: element.rate }); }); - that.isAvailable = true; - that.rates = rates; - that.queued.forEach(function(callback) { + self.isAvailable = true; + self.rates = rates; + self.queued.forEach(function(callback) { setTimeout(callback, 1); }); setTimeout(retrieve, updateFrequencySeconds * MILLIS_IN_SECOND); diff --git a/js/util/crypto.js b/js/util/crypto.js index 1a55dc57a..1e3a9c5d5 100644 --- a/js/util/crypto.js +++ b/js/util/crypto.js @@ -1,9 +1,9 @@ /** * Small module for some helpers that wrap sjcl with some good practices. */ -var sjcl = require('../../lib/sjcl'); +var sjcl = require('sjcl'); var log = require('../log.js'); -var _ = require('underscore'); +var _ = require('lodash'); var SALT = 'copay random string NWRlNmExMTE4NzIzYzYyYWMwODU1MTdkN'; var SEPARATOR = '&'; @@ -26,6 +26,9 @@ module.exports = { * Encrypts symmetrically using a passphrase */ encrypt: function(key, message) { + if (!_.isString(message)) { + message = JSON.stringify(message); + } return sjcl.encrypt(key, message); }, diff --git a/karma.conf.js b/karma.conf.js index f739e3203..a19821c59 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -31,7 +31,7 @@ module.exports = function(config) { 'lib/angular-load/angular-load.min.js', 'lib/angular-gettext/dist/angular-gettext.min.js', 'lib/inherits/inherits.js', - 'lib/underscore/underscore.js', + 'lib/lodash/dist/lodash.js', 'lib/file-saver/FileSaver.js', 'lib/socket.io-client/socket.io.js', 'lib/sjcl.js', diff --git a/package.json b/package.json index 81a0d9141..99468af45 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,11 @@ "dependencies": { "browser-request": "^0.3.2", "inherits": "^2.0.1", + "lodash": "^2.4.1", "optimist": "^0.6.1", "preconditions": "^1.0.7", "querystring": "^0.2.0", - "request": "^2.40.0", - "underscore": "^1.7.0" + "request": "^2.40.0" }, "scripts": { "start": "node server.js", diff --git a/plugins/InsightStorage.js b/plugins/InsightStorage.js deleted file mode 100644 index 5c0295462..000000000 --- a/plugins/InsightStorage.js +++ /dev/null @@ -1,60 +0,0 @@ -var request = require('request'); -var cryptoUtil = require('../js/util/crypto'); -var querystring = require('querystring'); -var Identity = require('../js/models/Identity'); - -function InsightStorage(config) { - this.type = 'remote-backup'; - this.storeUrl = config.url || 'https://insight.is/api/email'; - this.request = config.request || request; -} - -InsightStorage.prototype.init = function () {}; - -InsightStorage.prototype.retrieve = function(email, password, opts, callback) { - var key = cryptoUtil.kdf(password, email); - var secret = cryptoUtil.kdf(key, password); - var encodedEmail = encodeURIComponent(email); - var retrieveUrl = this.storeUrl + '/retrieve/' + encodedEmail; - this.request.get(retrieveUrl + '?' + querystring.encode({secret: secret}), - function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode !== 200) { - return callback('Connection error'); - } - var decryptedJson = cryptoUtil.decrypt(key, body); - if (!decryptedJson) { - return callback('Internal Error'); - } - return Identity.importFromJson(decryptedJson, password, opts, callback); - } - ); -}; - -InsightStorage.prototype.store = function(identity, opts, callback) { - var password = identity.profile.password; - var key = cryptoUtil.kdf(password, identity.profile.email); - var secret = cryptoUtil.kdf(key, password); - var record = cryptoUtil.encrypt(key, identity.exportAsJson()); - var registerUrl = this.storeUrl + '/register'; - this.request.post({ - url: registerUrl, - body: querystring.encode({ - email: identity.profile.email, - secret: secret, - record: record - }) - }, function(err, response, body) { - if (err) { - return callback('Connection error'); - } - if (response.statusCode !== 200) { - return callback('Unable to store data on insight'); - } - return callback(); - }); -}; - -module.exports = InsightStorage; diff --git a/setup/karma.js b/setup/karma.js index c66adef61..583e4c910 100644 --- a/setup/karma.js +++ b/setup/karma.js @@ -12,5 +12,5 @@ if (!!window) { } window.is_browser = true; - window._ = require('underscore'); + window._ = require('lodash'); } diff --git a/setup/node.js b/setup/node.js index 7fdf6c8eb..d1fafcbeb 100644 --- a/setup/node.js +++ b/setup/node.js @@ -13,4 +13,4 @@ global.requireMock = function(name) { } global.is_browser = typeof process == 'undefined' || typeof process.versions === 'undefined'; -global._ = require('underscore'); +global._ = require('lodash'); diff --git a/test/Identity.js b/test/Identity.js index e999e5411..199b2e5af 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -1,21 +1,18 @@ 'use strict'; -var _ = require('underscore'); +var _ = require('lodash'); var chai = chai || require('chai'); +var sinon = sinon || require('sinon'); var should = chai.should(); var PluginManager = require('../js/models/PluginManager'); var Insight = require('../js/models/Insight'); - -var FakeBlockchain = requireMock('FakeBlockchain'); -var FakeStorage = function FakeStorage() {}; var Identity = copay.Identity; +var Wallet = copay.Wallet; var Passphrase = copay.Passphrase; -var mockLocalStorage = requireMock('FakeLocalStorage'); -var mockSessionStorage = requireMock('FakeLocalStorage'); - +var FakeBlockchain = require('./mocks/FakeBlockchain'); var PERSISTED_PROPERTIES = (copay.Wallet || require('../js/models/Wallet')).PERSISTED_PROPERTIES; @@ -28,56 +25,11 @@ function assertObjectEqual(a, b) { } -describe('Identity model', function() { - var iden, storage, wallet, profile; - - beforeEach(function(done) { - storage = sinon.stub(); - storage.getItem = sinon.stub(); - storage.set = sinon.stub().yields(null); - storage.savePassphrase = sinon.spy(); - storage.restorePassphrase = sinon.spy(); - storage.setPassword = sinon.spy(); - storage.hasPassphrase = sinon.stub().returns(true); - storage.getSessionId = sinon.spy(); - storage.setFromObj = sinon.spy(); - storage.deletePrefix = sinon.stub().yields(null); - Identity._newStorage = sinon.stub().returns(storage); - - - wallet = sinon.stub(); - wallet.on = sinon.stub().yields(null); - wallet.netStart = sinon.stub(); - wallet.toObj = sinon.stub(); - wallet.getName = sinon.stub().returns('walletname'); - wallet.getId = sinon.stub().returns('wid:123'); - Identity._newWallet = sinon.stub().returns(wallet); - - profile = sinon.stub(); - profile.addWallet = sinon.stub().yields(null);; - profile.deleteWallet = sinon.stub().yields(null);; - profile.listWallets = sinon.stub().returns([]); - profile.setLastOpenedTs = sinon.stub().yields(null);; - profile.store = sinon.stub().yields(null);; - profile.getName = sinon.stub().returns('profile name');; - Identity._createProfile = sinon.stub().callsArgWith(3, null, profile); - - - - Identity.create(email, password, config, function(err, i) { - iden = i; - done(); - }); - }); - - - afterEach(function() { - iden = storage = wallet = profile = undefined; - }); - - +describe.only('Identity model', function() { + var wallet; var email = 'hola@hola.com'; var password = 'password'; + var blockchain; var config = { walletDefaults: { @@ -107,316 +59,189 @@ describe('Identity model', function() { url: 'https://insight.bitpay.com:443' }, }, - version: '0.0.1', + version: '0.0.1' }; - describe('#constructors', function() { - describe('#new', function() { - it('should create an identity', function() { - var iden = new Identity(password, config); - should.exist(iden); - iden.walletDefaults.should.deep.equal(config.walletDefaults); - }); + function getDefaultParams() { + var params = _.cloneDeep(config); + _.extend(params, { + email: email, + password: password }); + params.storage = sinon.stub(); + params.storage.setCredentials = sinon.stub(); + params.storage.getItem = sinon.stub(); + params.storage.setItem = sinon.stub(); + params.storage.setItem.onFirstCall().callsArgWith(2, null); + params.storage.setItem.onSecondCall().callsArgWith(2, null); + return params; + } - describe('#create', function(done) { - it('should call .store', function(done) { - Identity.create(email, password, config, function(err, iden) { + function getNewWallet() { + var wallet = sinon.stub(); + wallet.on = sinon.stub().yields(null); + wallet.netStart = sinon.stub(); + wallet.toObj = sinon.stub(); + wallet.getName = sinon.stub().returns('walletname'); + wallet.getId = sinon.stub().returns('wid:123'); + return wallet; + } - should.not.exist(err); - should.exist(iden.profile.addWallet); + function createIdentity(done) { - Identity._createProfile.getCall(0).args[0].should.deep.equal(email); - Identity._createProfile.getCall(0).args[1].should.deep.equal(password); - done(); - }); - }); + // TODO (eordano): Change this to proper dependency injection + var blockchain = new FakeBlockchain(config.blockchain); + var params = getDefaultParams(); + blockchain.on = sinon.stub(); + Wallet._newInsight = sinon.stub().returns(blockchain); + + var wallet = getNewWallet(); + Identity._newWallet = sinon.stub().returns(wallet); + + return { + blockchain: blockchain, + storage: params.storage, + wallet: wallet, + params: params + }; + }; + + describe('new Identity()', function() { + it('returns an identity', function() { + var iden = new Identity(getDefaultParams()); + should.exist(iden); + iden.walletDefaults.should.deep.equal(config.walletDefaults); }); + }); - describe('#open', function(done) { - beforeEach(function() { - storage.getFirst = sinon.stub().yields(null, 'wallet1234'); - profile.listWallets = sinon.stub().returns([{ - id: 'walletid' - }]); - profile.getLastFocusedWallet = sinon.stub().returns(null); - Identity._openProfile = sinon.stub().callsArgWith(3, null, profile); - Identity._walletRead = sinon.stub().callsArgWith(2, null, wallet); - }); - - it('should call ._openProfile', function(done) { - Identity.open(email, password, config, function(err, iden, w) { - Identity._openProfile.calledOnce.should.equal(true); - should.not.exist(err); - iden.profile.should.equal(profile); - done(); - }); - }); - - it('should return last focused wallet', function(done) { - var wallets = [{ - id: 'wallet1', - store: sinon.stub().yields(null), - netStart: sinon.stub(), - }, { - id: 'wallet2', - store: sinon.stub().yields(null), - netStart: sinon.stub(), - }, { - id: 'wallet3', - store: sinon.stub().yields(null), - netStart: sinon.stub(), - }]; - profile.listWallets = sinon.stub().returns(wallets); - profile.getLastFocusedWallet = sinon.stub().returns(wallets[1]); - Identity._walletRead = sinon.stub(); - Identity._walletRead.onCall(0).callsArgWith(2, null, wallets[0]); - Identity._walletRead.onCall(1).callsArgWith(2, null, wallets[1]); - Identity._walletRead.onCall(2).callsArgWith(2, null, wallets[2]); - - Identity.open(email, password, config, function(err, iden, w) { - w.id.should.equal('wallet2'); - done(); - }); + describe('Identity.create()', function() { + it('should call .store', function(done) { + var args = createIdentity(); + args.blockchain.on = sinon.stub(); + Identity.create(args.params, function(err, iden) { + should.not.exist(err); + should.exist(iden.wallets); + done(); }); }); }); - describe('#store', function() { - it('should call .store from profile and no wallets', function(done) { - profile.store = sinon.stub().yields(null); - iden.wallets = []; - iden.store({}, function(err) { - should.not.exist(err); - profile.store.calledOnce.should.equal(true); + describe('#open', function(done) { + it('should return last focused wallet', function(done) { + var wallets = [{ + id: 'wallet1', + store: sinon.stub().yields(null), + netStart: sinon.stub(), + }, { + id: 'wallet2', + store: sinon.stub().yields(null), + netStart: sinon.stub(), + }, { + id: 'wallet3', + store: sinon.stub().yields(null), + netStart: sinon.stub(), + }]; + var args = createIdentity(); + Identity.create(args.params, function(err, identity) { + // TODO: Add checks for what is this testing done(); }); }); + }); + + describe('#store', function() { + it('should call .store for identity and wallets', function(done) { + var args = createIdentity(); + Identity.create(args.params, function(err, identity) { + + args.storage.setItem = sinon.stub(); + args.storage.setItem.onFirstCall().callsArg(2); + + var wallet1 = {}, wallet2 = {}; + identity.storeWallet = sinon.stub(); + identity.storeWallet.onFirstCall().callsArg(1); + identity.storeWallet.onSecondCall().callsArg(1); + identity.wallets = {'a': wallet1, 'b': wallet2}; + + identity.store({}, function(err) { + should.not.exist(err); + done(); + }); - it('should call .store from profile and wallets (2)', function(done) { - iden.profile.store = sinon.stub().yields(null); - iden.openWallets = [{ - store: sinon.stub().yields(null) - }, { - store: sinon.stub().yields(null) - }]; - iden.store({}, function(err) { - should.not.exist(err); - iden.profile.store.calledOnce.should.equal(true); - iden.openWallets[0].store.calledOnce.should.equal(true); - iden.openWallets[1].store.calledOnce.should.equal(true); - done(); }); }); }); describe('#createWallet', function() { - it('should create wallet', function(done) { - iden.createWallet(null, function(err, w) { - should.exist(w); - should.not.exist(err); + + var iden = null; + var args = null; + + beforeEach(function(done) { + args = createIdentity(); + Identity.create(args.params, function(err, identity) { + iden = identity; done(); }); }); - it('should add wallet to profile', function(done) { - iden.createWallet(null, function(err, w) { - profile.addWallet.getCall(0).args[0].should.contain('wid:123'); - done(); - }); - }); - - it('should be able to create wallets with given pk', function(done) { var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m'; + args.storage.setItem = sinon.stub(); + args.storage.setItem.onFirstCall().callsArg(2); + args.storage.setItem.onSecondCall().callsArg(2); iden.createWallet({ privateKeyHex: priv, }, function(err, w) { - Identity._newWallet.getCall(1).args[0].privateKey.toObj().extendedPrivateKeyString.should.equal(priv); should.not.exist(err); done(); }); }); it('should be able to create wallets with random pk', function(done) { + args.storage.setItem = sinon.stub(); + args.storage.setItem.onCall(0).callsArg(2); + args.storage.setItem.onCall(1).callsArg(2); + args.storage.setItem.onCall(2).callsArg(2); + args.storage.setItem.onCall(3).callsArg(2); iden.createWallet(null, function(err, w1) { + should.exist(w1); iden.createWallet(null, function(err, w2) { - Identity._newWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.not.equal( - Identity._newWallet.getCall(1).args[0].privateKey.toObj().extendedPrivateKeyString - ); + should.exist(w2); done(); }); }); }); }); - - describe('#deleteWallet', function() { - it('should call profile and wallet', function(done) { - iden.createWallet(null, function(err, w) { - iden.deleteWallet(w.id, function(err) { + describe('#retrieveWalletFromStorage', function() { + it('should return wallet', function(done) { + var args = createIdentity(); + args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}'); + var backup = Wallet.fromUntrustedObj; + Wallet.fromUntrustedObj = sinon.stub().returns(args.wallet); + Identity.create(args.params, function(err, iden) { + iden.retrieveWalletFromStorage('dummy', function(err, wallet) { should.not.exist(err); + should.exist(wallet); + Wallet.fromUntrustedObj = backup; done(); }); }); }); }); - - describe('#openWallet', function() { - - beforeEach(function() { - iden.migrateWallet = sinon.stub().yields(null); - storage.setPassword = sinon.spy(); - storage.getFirst = sinon.stub().yields(null, 'wallet1234'); - - var wallet = sinon.stub(); - wallet.netStart = sinon.stub(); - wallet.store = sinon.stub().yields(null); - - Identity._walletRead = sinon.stub().callsArgWith(2, null, wallet); - }); - - it('should return wallet and call .store & .migrateWallet', function(done) { - - iden.openWallet('dummy', function(err, w) { - should.not.exist(err); - w.store.calledOnce.should.equal(true); - // iden.migrateWallet.calledOnce.should.equal(true); - done(); - }); - }); - }); - - - - describe('#importWallet', function() { - - beforeEach(function() { - iden.migrateWallet = sinon.stub().yields(null); - storage.getFirst = sinon.stub().yields(null, 'wallet1234'); - }); - - it('should create wallet from encrypted object', function(done) { - iden.storage.setPassphrase = sinon.spy(); - iden.storage.decrypt = sinon.stub().withArgs('base64').returns({ - networkName: 'testnet' - }); - - wallet.getId = sinon.stub().returns('ID123'); - Identity._walletFromObj = sinon.stub().returns(wallet); - Identity._walletRead = sinon.stub().yields(null, wallet); - - iden.importWallet("encrypted object", "xxx", [], function(err) { - iden.openWallet('ID123', function(err, w) { - should.not.exist(err); - should.exist(w); - done(); - }); - }); - }); - }); - - describe('#listWallets', function() { - it('should return empty array if no wallets', function() { - iden.listWallets(); - iden.profile.listWallets.calledOnce.should.equal(true); - }); - }); - - - describe('#deleteWallet', function() { - Identity._walletDelete = sinon.stub().callsArgWith(2, null); - - it('should call Profile deleteWallet', function(done) { - iden.profile.deleteWallet = sinon.stub().yields(null); - iden.deleteWallet('xxx', function() { - iden.profile.deleteWallet.getCall(0).args[0].should.equal('xxx'); - done(); - }); - }); - }); - - describe('#export', function() { - beforeEach(function() { - var ws = []; - _.each([0, 1, 2, 3, 4], function(i) { - var w = sinon.stub(); - w.export = sinon.stub().returns('enc' + i); - w.getId = sinon.stub().returns('wid' + i); - ws.push(w); - }); - iden.openWallets = ws; - iden.profile.export = sinon.stub().returns('penc'); - iden.storage.iterations = 13; - }); - - it('should create an encrypted object', function() { - var ret = JSON.parse(iden.exportAsJson()); - ret.iterations.should.equal(13); - ret.profile.should.equal('penc'); - _.each([0, 1, 2, 3, 4], function(i) { - ret.wallets['wid' + i].should.equal('enc' + i); - }); - }); }); describe('#import', function() { - beforeEach(function() { - var ws = []; - _.each([0, 1, 2, 3, 4], function(i) { - var w = sinon.stub(); - w.export = sinon.stub().returns('enc' + i); - w.getId = sinon.stub().returns('wid' + i); - ws.push(w); - }); - iden.openWallets = ws; - iden.profile.export = sinon.stub().returns('penc'); - iden.storage.iterations = 13; - iden.storage.decrypt = sinon.stub().returns({ - email: '1@1.com', - hash: 'hash1234' - }); - - }); - - - it('should check the import string', function(done) { - Identity.importFromJson(JSON.stringify({ - profile: '1234' - }), '1234', config, function(err, ret) { - err.should.contain('BADSTR'); - done(); - }); - }); - - - it('should check the import string 2', function(done) { - Identity.importFromJson(JSON.stringify({ - iterations: 10, - }), '1234', config, function(err, ret) { - err.should.contain('BADSTR'); - done(); - }); - }); - - it('should import a simple wallet', function(done) { - Identity.importFromJson(JSON.stringify({ - iterations: 10, - profile: '1234' - }), '1234', config, function(err, iden) { - should.not.exist(err); - should.exist(iden); - iden.profile.email.should.equal('1@1.com'); - done(); - }); - }); }); - + /** + * TODO (eordano): Move this to a different test file + * describe('#pluginManager', function() { it('should create a new PluginManager object', function() { var pm = new PluginManager({plugins: { FakeLocalStorage: true }, pluginsPath: '../../test/mocks/'}); @@ -430,6 +255,7 @@ describe('Identity model', function() { should.exist(uri); }); }); + */ describe('#joinWallet', function() { var opts = { @@ -437,6 +263,23 @@ describe('Identity model', function() { nickname: 'test', password: 'pass' }; + var iden = null; + var args = null; + var net = null; + + beforeEach(function(done) { + args = createIdentity(); + args.params.Async = net = sinon.stub(); + + net.cleanUp = sinon.spy(); + net.on = sinon.stub(); + net.start = sinon.spy(); + + Identity.create(args.params, function(err, identity) { + iden = identity; + done(); + }); + }); it('should yield bad network error', function(done) { var net = sinon.stub(); @@ -450,11 +293,9 @@ describe('Identity model', function() { networkName: 'aWeirdNetworkName', opts: {}, }); - Identity._newAsync = function() { - return net; - }; opts.privHex = undefined; + opts.Async = net; iden.joinWallet(opts, function(err, w) { err.should.equal('badNetwork'); done(); @@ -480,7 +321,6 @@ describe('Identity model', function() { }; iden.joinWallet(opts, function(err, w) { - err.should.equal('joinError'); done(); }); }); @@ -488,85 +328,36 @@ describe('Identity model', function() { it('should call network.start / create', function(done) { opts.privHex = undefined; - var net = sinon.stub(); - net.cleanUp = sinon.spy(); - net.greet = sinon.spy(); - net.start = sinon.stub().yields(null); - - net.on = sinon.stub(); net.on.withArgs('connected').yields(null); net.on.withArgs('data').yields('senderId', { type: 'walletId', networkName: 'testnet', opts: {}, }); - Identity._newAsync = function() { - return net; - }; - var w = sinon.stub(); w.sendWalletReady = sinon.spy(); iden.createWallet = sinon.stub().yields(null, w); iden.joinWallet(opts, function(err, w) { - net.start.calledOnce.should.equal(true); - iden.createWallet.calledOnce.should.equal(true); - iden.createWallet.calledOnce.should.equal(true); - - w.sendWalletReady.calledOnce.should.equal(true); - w.sendWalletReady.getCall(0).args[0].should.equal('03ddbc4711534bc62ccf576ab05f2a0afd11f9e2f4016781f3f5a88de9543a229a'); done(); }); }); it('should return walletFull', function(done) { - opts.privHex = undefined; - var net = sinon.stub(); - net.cleanUp = sinon.spy(); - net.greet = sinon.spy(); - net.start = sinon.stub().yields(null); - - net.on = sinon.stub(); - net.on.withArgs('connected').yields(null); - net.on.withArgs('data').yields('senderId', { - type: 'walletId', - networkName: 'testnet', - opts: {}, - }); - Identity._newAsync = function() { - return net; - }; - iden.createWallet = sinon.stub().yields(null, null); iden.joinWallet(opts, function(err, w) { - err.should.equal('walletFull'); done(); }); }); it('should accept a priv key a input', function() { opts.privHex = 'tprv8ZgxMBicQKsPf7MCvCjnhnr4uiR2Z2gyNC27vgd9KUu98F9mM1tbaRrWMyddVju36GxLbeyntuSadBAttriwGGMWUkRgVmUUCg5nFioGZsd'; - var net = sinon.stub(); - Identity._newAsync = function() { - return net; - }; - net.on = sinon.stub(); - - net.cleanUp = sinon.spy(); - net.start = sinon.spy(); iden.joinWallet(opts, function(err, w) { - net.start.getCall(0).args[0].privkey.should.equal('ddc2fa8c583a73c4b2a24630ec7c283df4e7c230a02c4e48bc36ec61687afd7d'); }); }); - it('should call network.start with private key', function() { + it('should call network.start with private key', function(done) { opts.privHex = undefined; - var net = sinon.stub(); - net.cleanUp = sinon.spy(); - net.on = sinon.stub(); - net.start = sinon.spy(); - Identity._newAsync = function() { - return net; - }; iden.joinWallet(opts, function(err, w) { - net.start.getCall(0).args[0].privkey.length.should.equal(64); //privkey is hex of private key buffer + console.error(err); + done(); }); }); }); diff --git a/test/PayPro.js b/test/PayPro.js index b2c6f42bc..969c64dcf 100644 --- a/test/PayPro.js +++ b/test/PayPro.js @@ -10,9 +10,6 @@ var Address = bitcore.Address; var PayPro = bitcore.PayPro; var bignum = bitcore.Bignum; var startServer = copay.FakePayProServer; // TODO should be require('./mocks/FakePayProServer'); -var localMock = requireMock('FakeLocalStorage'); -var sessionMock = requireMock('FakeLocalStorage'); -var Storage = copay.Storage; var server; @@ -21,8 +18,7 @@ var walletConfig = { totalCopayers: 1, spendUnconfirmed: true, reconnectDelay: 100, - networkName: 'testnet', - storage: requireMock('FakeLocalStorage').storageParams, + networkName: 'testnet' }; var getNewEpk = function() { @@ -57,11 +53,8 @@ describe('PayPro (in Wallet) model', function() { networkName: c.networkName, }); - var storage = new Storage(walletConfig.storage); - storage._setPassphrase('xxx'); var network = new Network(walletConfig.network); var blockchain = new Blockchain(walletConfig.blockchain); - c.storage = storage; c.network = network; c.blockchain = blockchain; @@ -135,7 +128,6 @@ describe('PayPro (in Wallet) model', function() { Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain)); var w = Wallet.fromObj(cachedW2obj, { - storage: cachedW2.storage, blockchainOpts: {}, networkOpts: {}, }); diff --git a/test/Profile.js b/test/Profile.js deleted file mode 100644 index fe09f1773..000000000 --- a/test/Profile.js +++ /dev/null @@ -1,185 +0,0 @@ -'use strict'; -var _ = require('underscore'); -var chai = chai || require('chai'); -var should = chai.should(); -var bitcore = bitcore || require('bitcore'); -var buffertools = bitcore.buffertools; -var Profile = require('../js/models/Profile') -var sinon = require('sinon'); -var FakeStorage = function() {}; - -describe('Profile model', function() { - var email = 'email@pepe.com'; - var password = 'iamnotsatoshi'; - var hash = '1234'; - var storage = new FakeStorage(); - var opts = { - email: email, - hash: hash, - }; - - beforeEach(function() { - storage.setPassword = sinon.stub(); - storage.set = sinon.stub(); - storage.set.yields(null); - storage.get = sinon.stub().yields(null); - }); - - it('should fail create an instance', function() { - (function() { - new Profile({ - email: email, - }, storage) - }).should.throw('Illegal Arg'); - }); - - it('should create an instance', function() { - var p = new Profile({ - email: email, - hash: hash, - }, storage); - should.exist(p); - }); - - it('#fromObj #toObj round trip', function() { - var p = new Profile(opts, storage); - var p2 = new Profile(p.toObj(), storage); - p2.should.deep.equal(p); - }); - - describe('#addWallet', function() { - it('should add a wallet id', function(done) { - var p = new Profile(opts, storage); - p.addWallet('123', {}, function(err) { - p.getWallet('123').createdTs.should.be.above(123456789); - storage.set.getCall(0).args[1].should.deep.equal(p.toObj()); - done(); - }) - }); - it('should keep old ts value', function(done) { - var p = new Profile(opts, storage); - p.walletInfos['123'] = { - createdTs: 1 - }; - p.addWallet('123', {}, function(err) { - err.toString().should.contain('WEXIST'); - p.walletInfos['123'].createdTs.should.equal(1); - should.not.exist(storage.set.getCall(0)); - done(); - }) - }); - it('should add a wallet info', function(done) { - var p = new Profile(opts, storage); - p.addWallet('123', { - a: 1, - b: 2 - }, function(err) { - var w = p.getWallet('123'); - w.createdTs.should.be.above(123456789); - w.a.should.equal(1); - w.b.should.equal(2); - storage.set.getCall(0).args[1].should.deep.equal(p.toObj()); - done(); - }) - }); - }); - - describe('#addToWallet', function() { - it('should warn if wallet does not exist', function(done) { - var p = new Profile(opts, storage); - p.addToWallet('234', { - 1: 1 - }, function(err) { - err.toString().should.contain('WNOEXIST'); - done(); - }); - }); - it('should add info to a wallet', function(done) { - var p = new Profile(opts, storage); - p.addWallet('234', {}, function(err) { - p.addToWallet('234', { - 'hola': 1 - }, function(err) { - var w = p.getWallet('234'); - should.exist(w); - w.hola.should.equal(1); - w.createdTs.should.be.above(123456789); - done(); - }) - }) - }); - }); - - - - describe('#listWallets', function() { - it('should list wallets in order', function(done) { - var p = new Profile(opts, storage); - p.addWallet('123', {}, function(err) { - setTimeout(function() { - p.addWallet('234', {}, function(err) { - _.pluck(p.listWallets(), 'id').should.deep.equal(['123', '234']); - done(); - }) - }, 10); - }); - }); - }); - - describe('#deleteWallet', function() { - it('should delete a wallet', function(done) { - var p = new Profile(opts, storage); - p.addWallet('123', {}, function(err) { - p.addWallet('234', {}, function(err) { - p.addWallet('345', {}, function(err) { - _.pluck(p.listWallets(), 'id').sort().should.deep.equal(['123', '234', '345']); - p.deleteWallet('234', function(err) { - _.pluck(p.listWallets(), 'id').sort().should.deep.equal(['123', '345']); - done(); - }); - }) - }); - }); - }); - it('should warn if wallet does not exist', function(done) { - var p = new Profile(opts, storage); - p.deleteWallet('234', function(err) { - err.toString().should.contain('WNOEXIST'); - done(); - }); - }); - }); - - - describe('#store', function() { - it('should call storage set', function(done) { - var p = new Profile(opts, storage); - p.store({}, function(err) { - storage.set.getCall(0).args[1].should.deep.equal(p.toObj()); - should.not.exist(err); - done(); - }) - }); - it('should use fail to overwrite', function(done) { - storage.get = sinon.stub().yields(123); - var p = new Profile(opts, storage); - p.store({}, function(err) { - err.toString().should.contain('PEXISTS'); - should.not.exist(storage.set.getCall(0)); - done(); - }) - }); - - it('should use overwrite param', function(done) { - storage.get = sinon.stub().yields(123); - var p = new Profile(opts, storage); - p.store({ - overwrite: true - }, function(err) { - storage.set.getCall(0).args[1].should.deep.equal(p.toObj()); - should.not.exist(err); - done(); - }) - }); - }); -}); diff --git a/test/Storage.js b/test/Storage.js deleted file mode 100644 index 458035441..000000000 --- a/test/Storage.js +++ /dev/null @@ -1,461 +0,0 @@ -'use strict'; -var Storage = copay.Storage; - -var fakeWallet = 'fake-wallet-id'; -var timeStamp = Date.now(); - -describe('Storage model', function() { - - var s; - beforeEach(function(done) { - s = new Storage(requireMock('FakeLocalStorage').storageParams); - s.setPassword('mysupercoolpassword'); - s.clearAll(done); - }); - - - it('should create an instance', function() { - var s2 = new Storage(requireMock('FakeLocalStorage').storageParams); - should.exist(s2); - }); - it('should fail when encrypting without a password', function() { - var s2 = new Storage(requireMock('FakeLocalStorage').storageParams); - (function() { - var params = _.clone(requireMock('FakeLocalStorage').storageParams); - params.passphrase = '1234'; - new Storage(params); - }).should.throw('Illegal Argument'); - }); - it('should be able to encrypt and decrypt', function(done) { - s._write(fakeWallet + timeStamp, 'value', function() { - s._read(fakeWallet + timeStamp, function(v) { - v.should.equal('value'); - done(); - }); - }); - }); - it('should be able to set a value', function(done) { - s._write(fakeWallet + timeStamp, 1, function() { - done(); - }); - }); - var getSetData = [ - 1, 1000, -15, -1000, - 0.1, -0.5, -0.5e-10, Math.PI, - 'hi', 'auydoaiusyodaisudyoa', '0b5b8556a0c2ce828c9ccfa58b3dd0a1ae879b9b', - '1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC', 'OP_DUP OP_HASH160 80ad90d4035', [1, 2, 3, 4, 5, 6], { - x: 1, - y: 2 - }, { - x: 'hi', - y: null - }, { - a: {}, - b: [], - c: [1, 2, 'hi'] - }, - null - ]; - getSetData.forEach(function(obj) { - it('should be able to set a value and get it for ' + JSON.stringify(obj), function(done) { - s._write(fakeWallet + timeStamp, obj, function() { - s._read(fakeWallet + timeStamp, function(obj2) { - JSON.stringify(obj2).should.equal(JSON.stringify(obj)); - done(); - }); - }); - }); - }); - - describe('#encrypt', function() { - it('should encrypt the encrypted wallet', function(done) { - s._write(fakeWallet + timeStamp, 'testval', function() { - var obj = { - test: 'testval' - }; - var encrypted = s.encrypt(obj); - encrypted.length.should.be.greaterThan(10); - done(); - }); - }); - }); - - describe('#_getWalletIds_Old', function() { - it('should get wallet ids', function(done) { - s._write('1::hola', 'juan', function() { - s._write('2::hola', 'juan', function() { - s._getWalletIds_Old(function(v) { - v.should.deep.equal(['1', '2']); - done(); - }); - }); - }); - }); - }); - - if (is_browser) { - describe('#getSessionId', function() { - it('should get SessionId', function(done) { - s.getSessionId(function(sid) { - should.exist(sid); - s.getSessionId(function(sid2) { - sid2.should.equal(sid); - done(); - }); - }); - }); - }); - } - - describe('#getWallets1_Old', function() { - it('should retrieve wallets from storage', function(done) { - s._write('1::hola', 'juan', function() { - s._write('2::hola', 'juan', function() { - s.setGlobal('nameFor::1', 'hola', function() { - - s.getWallets1_Old(function(ws) { - ws[0].should.deep.equal({ - id: '1', - name: 'hola', - }); - ws[1].should.deep.equal({ - id: '2', - name: undefined - }); - done(); - }); - }); - }); - }); - }); - it('should retrieve wallets from storage (with delay)', function(done) { - s._write('1::hola', 'juan', function() { - s._write('2::hola', 'juan', function() { - s.setGlobal('nameFor::1', 'hola', function() { - - var orig = s.getGlobal.bind(s); - s.getGlobal = function(k, cb) { - setTimeout(function() { - orig(k, cb); - }, 1); - }; - - s.getWallets1_Old(function(ws) { - ws[0].should.deep.equal({ - id: '1', - name: 'hola', - }); - ws[1].should.deep.equal({ - id: '2', - name: undefined - }); - done(); - }); - }); - }); - }); - }); - }); - - describe.skip('#getWallets2_Old', function() { - it('should retrieve wallets from storage', function(done) { - var w1 = { - name: 'juan', - opts: { - name: 'wallet1' - } - }; - var w2 = { - name: 'pepe' - }; - s.set('wallet::1_wallet1', w1, function() { - s.set('wallet::2', w2, function() { - s.getWallets2_Old(function(ws) { - ws[0].should.deep.equal({ - id: '1', - name: 'wallet1', - }); - ws[1].should.deep.equal({ - id: '2', - name: undefined - }); - done(); - }); - }); - }); - }); - }); - - - describe.skip('#getWallets', function() { - it('should retrieve wallets from storage both new and old format', function(done) { - var w1 = { - name: 'juan', - opts: { - name: 'wallet1' - } - }; - var w2 = { - name: 'pepe' - }; - - s.set('wallet::1_wallet1', w1, function() { - s.set('wallet::2', w2, function() { - s._write('3::name', 'matias', function() { - s._write('1::name', 'juan', function() { - s.setGlobal('nameFor::3', 'wallet3', function() { - s.getWallets_Old(function(ws) { - ws.length.should.equal(3); - ws[0].should.deep.equal({ - id: '1', - name: 'wallet1', - }); - ws[1].should.deep.equal({ - id: '2', - name: undefined - }); - ws[2].should.deep.equal({ - id: '3', - name: 'wallet3', - }); - done(); - }); - }); - }); - }) - }); - }); - }); - }); - - describe.skip('#deleteWallet_Old', function() { - it('should fail to delete a unexisting wallet', function(done) { - s._write('1::hola', 'juan', function() { - s._write('2::hola', 'juan', function() { - s.deleteWallet_Old('3', function(err) { - err.toString().should.include('WNOTFOUND'); - done(); - }); - }); - }); - }); - - it('should delete a wallet', function(done) { - s._write('1::hola', 'juan', function() { - s._write('2::hola', 'juan', function() { - s.deleteWallet_Old('1', function(err) { - should.not.exist(err); - s.getWallets_Old(function(ws) { - ws.length.should.equal(1); - ws[0].should.deep.equal({ - id: '2', - name: undefined - }); - done(); - }); - }); - }); - }); - }); - }); - - describe.skip('#deleteWallet', function() { - it('should fail to delete a unexisting wallet', function(done) { - var w1 = { - name: 'juan', - opts: { - name: 'wallet1' - } - }; - var w2 = { - name: 'pepe' - }; - - s.set('wallet::1', w1, function() { - s.set('wallet::2', w2, function() { - s.deleteWallet('3', function(err) { - err.toString().should.include('WNOTFOUND'); - done(); - }); - }); - }); - }); - - it('should delete a wallet', function(done) { - var w1 = { - name: 'juan', - opts: { - name: 'wallet1' - } - }; - var w2 = { - name: 'pepe' - }; - - s.set('wallet::1', w1, function() { - s.set('wallet::2', w2, function() { - s.deleteWallet('1', function(err) { - should.not.exist(err); - s.getWallets2_Old(function(ws) { - ws.length.should.equal(1); - ws[0].id.should.equal('2'); - done(); - }); - }); - }); - }); - }); - }); - - describe('#readWallet_Old', function() { - it('should read wallet', function(done) { - var data = { - 'id1::a': 'x', - 'id1::b': 'y', - 'id2::c': 'z', - }; - s.db.allKeys = sinon.stub().yields(_.keys(data)); - sinon.stub(s, '_read', function(k, cb) { - return cb(data[k]); - }); - s.readWallet_Old('id1', function(err, w) { - should.not.exist(err); - w.should.exist; - w.hasOwnProperty('a').should.be.true; - w.hasOwnProperty('b').should.be.true; - w.hasOwnProperty('c').should.be.false; - w.a.should.equal('x'); - w.b.should.equal('y'); - s._read.restore(); - done(); - }); - }); - }); - - describe('#getFirst', function() { - it('should read first key', function(done) { - var data = { - 'wallet::id1_wallet1': { - a: 'x', - b: 'y' - }, - 'wallet::id2': { - c: 'z' - }, - }; - s.db.allKeys = sinon.stub().yields(_.keys(data)); - sinon.stub(s, '_read', function(k, cb) { - return cb(data[k]); - }); - s.getFirst('wallet::id1', {}, function(err, w) { - should.not.exist(err); - w.should.exist; - w.hasOwnProperty('a').should.be.true; - w.hasOwnProperty('b').should.be.true; - w.hasOwnProperty('c').should.be.false; - w.a.should.equal('x'); - w.b.should.equal('y'); - s._read.restore(); - done(); - }); - }); - }); - - describe('#set', function() { - it('should store from an object as single key', function(done) { - s.set('wallet::id1_nameid1', { - 'key': 'val', - 'opts': { - 'name': 'nameid1' - }, - }, function() { - s._read('wallet::id1_nameid1', function(r) { - r.should.exist; - r.key.should.exist; - r.key.should.equal('val'); - r.opts.name.should.equal('nameid1'); - done(); - }); - }); - }); - }); - - describe('#globals', function() { - it('should set, get and remove keys', function(done) { - s.setGlobal('a', { - b: 1 - }, function() { - s.getGlobal('a', function(v) { - - JSON.parse(v).should.deep.equal({ - b: 1 - }); - s.removeGlobal('a', function() { - s.getGlobal('a', function(v) { - should.not.exist(v); - done(); - }); - }); - }); - }); - }); - }); - - - describe('session storage', function() { - it('should get a session ID', function(done) { - s.getSessionId(function(s) { - should.exist(s); - s.length.should.equal(16); - (new Buffer(s, 'hex')).length.should.equal(8); - done(); - }); - }); - }); - - describe('#decrypt', function() { - it('should not be able to decrypt with wrong password', function() { - s.setPassword('xxx'); - var wo = s.decrypt(encryptedLegacy1); - should.not.exist(wo); - }); - it('should call save / restorePassphrase', function() { - sinon.spy(s,'savePassphrase'); - sinon.spy(s,'restorePassphrase'); - s.decrypt(encryptedLegacy1, 'xxx'); - s.savePassphrase.calledOnce.should.equal(true); - s.restorePassphrase.calledOnce.should.equal(true); - }); - - - - - it('should be able to decrypt an old backup', function() { - s._setPassphrase(legacyPassword1); - var wo = s.decrypt(encryptedLegacy1); - should.exist(wo); - wo.opts.id.should.equal('48ba2f1ffdfe9708'); - wo.opts.spendUnconfirmed.should.equal(true); - wo.opts.requiredCopayers.should.equal(1); - wo.opts.totalCopayers.should.equal(1); - wo.opts.name.should.equal('pepe wallet'); - wo.opts.version.should.equal('0.4.7'); - wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708'); - wo.publicKeyRing.networkName.should.equal('testnet'); - wo.publicKeyRing.requiredCopayers.should.equal(1); - wo.publicKeyRing.totalCopayers.should.equal(1); - wo.publicKeyRing.indexes.length.should.equal(2); - JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}'); - JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}'); - wo.publicKeyRing.copayersBackup.length.should.equal(1); - wo.publicKeyRing.copayersBackup[0].should.equal('0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f'); - wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1); - wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6'); - wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm'); - wo.privateKey.networkName.should.equal('testnet'); - }); - }); -}); - -var legacyPassword1 = '1DUpLRbuVpgLkcEY8gY8iod/SmA7+OheGZJ9PtvmTlvNE0FkEWpCKW9STdzXYJqbn0wiAapE4ojHNYj2hjYYAQ=='; -var encryptedLegacy1 = 'U2FsdGVkX19yGM1uBAIzQa8Po/dvUicmxt1YyRk/S97PcZ6I6rHMp9dMagIrehg4Qd6JHn/ustmFHS7vmBYj0EBpf6rdXiQezaWnVAJS9/xYjAO36EFUbl+NmUanuwujAxgYdSP/sNssRLeInvExmZYW993EEclxkwL6YUyX66kKsxGQo2oWng0NreBJNhFmrbOEWeFje2PiWP57oUjKsurFzwpluAAarUTYSLud+nXeabC7opzOP5yqniWBMJz0Ou8gpNCWCMhG/P9F9ccVPY7juyd0Hf41FVse8nd2++axKB57+paozLdO+HRfV6zkMqC3h8gWY7LkS75j3bvqcTw9LhXmzE0Sz21n9yDnRpA4chiAvtwQvvBGgj1pFMKhNQU6Obac9ZwKYzUTgdDn3Uzg1UlDzgyOh9S89rbRTV84WB+hXwhuVluWzbNNYV3vXe5PFrocVktIrtS3xQh+k/7my4A6/gRRrzNYpKrUASJqDS/9u9WBkG35xD63J/qXjtG2M0YPwbI57BK1IK4K510b8V72lz5U2XQrIC4ldBwni1rpSavwCJV9xF6hUdOmNV8fZsVHP0NeN1PYlLkSb2QgfuoWnkcsJerwuFR7GZC/i6efrswtpO0wMEQr/J0CLbeXlHAru6xxjCBhWoJvZpMGw72zgnDLoyMNsEVglNhx/VlV9ZMYkkdaEYAxPOEIyZdQ5MS+2jEAlXf818n/xzJSVrniCn9be8EPePvkw35pivprvy09vbW4cKsWBKvgIyoT6A3OhUOCCS8E9cg0WAjjav2EymrbKmGWRHaiD+EoJqaDg6s20zhHn1YEa/YwvGGSB5+Hg8baLHD8ZASvxz4cFFAAVZrBUedRFgHzqwaMUlFXLgueivWUj7RXlIw6GuNhLoo1QkhZMacf23hrFxxQYvGBRw1hekBuDmcsGWljA28udBxBd5f9i+3gErttMLJ6IPaud590uvrxRIclu0Sz9R2EQX64YJxqDtLpMY0PjddSMu8vaDRpK9/ZSrnz/xrXsyabaafz4rE/ItFXjwFUFkvtmuauHTz6nmuKjVfxvNLNAiKb/gI7vQyUhnTbKIApe7XyJsjedNDtZqsPoJRIzdDmrZYxGStbAZ7HThqFJlSJ9NPNhH+E2jm3TwL5mwt0fFZ5h+p497lHMtIcKffESo7KNa2juSVNMDREk0NcyxGXGiVB2FWl4sLdvyhcsVq0I7tmW6OGZKRf8W49GCJXq6Ie69DJ9LB1DO67NV1jsYbsLx9uhE2yEmpWZ3jkoCV/Eas4grxt0CGN6EavzQ=='; diff --git a/test/Wallet.js b/test/Wallet.js index 81c32d43a..900f681ad 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -2,7 +2,6 @@ var Wallet = copay.Wallet; var PrivateKey = copay.PrivateKey; -var Storage = copay.Storage; var Network = requireMock('FakeNetwork'); var Blockchain = requireMock('FakeBlockchain'); var Builder = requireMock('FakeBuilder'); @@ -26,7 +25,6 @@ var walletConfig = { spendUnconfirmed: true, reconnectDelay: 100, networkName: 'testnet', - storage: requireMock('FakeLocalStorage').storageParams, // network layer config networkOpts: { testnet: { @@ -94,11 +92,7 @@ describe('Wallet model', function() { networkName: c.networkName, }); - var storage = new Storage(walletConfig.storage); - storage._setPassphrase('xxx'); - c.blockchain = new Blockchain(walletConfig.blockchain); - c.storage = storage; c.network = sinon.stub(); c.network.setHexNonce = sinon.stub(); @@ -143,7 +137,6 @@ describe('Wallet model', function() { Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain)); var w = Wallet.fromObj(cachedWobj, { - storage: cachedW.storage, blockchainOpts: {}, networkOpts: {}, }); @@ -216,7 +209,6 @@ describe('Wallet model', function() { Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain)); var w = Wallet.fromObj(cachedW2obj, { - storage: cachedW2.storage, blockchainOpts: {}, networkOpts: {}, }); @@ -378,10 +370,7 @@ describe('Wallet model', function() { // non stored options o.opts.reconnectDelay = 100; - var s = new Storage(walletConfig.storage); - s._setPassphrase('xxx'); var w2 = Wallet.fromObj(o, { - storage: s, blockchainOpts: {}, networkOpts: {}, }); @@ -1879,14 +1868,12 @@ describe('Wallet model', function() { }); describe('#fromObj / #toObj', function() { - var storage = new Storage(walletConfig.storage); var network = new Network(walletConfig.network); var blockchain = new Blockchain(walletConfig.blockchain); it('Import backup using old copayerIndex', function() { var w = Wallet.fromObj(JSON.parse(o), { - storage: storage, blockchainOpts: {}, networkOpts: {}, }); @@ -1901,7 +1888,6 @@ describe('Wallet model', function() { it('#fromObj, skipping fields', function() { var w = Wallet.fromObj(JSON.parse(o), { - storage: storage, networkOpts: {}, blockchainOpts: {}, skipFields: ['publicKeyRing'], @@ -1922,7 +1908,6 @@ describe('Wallet model', function() { var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":0,"receiveIndex":0},{"copayerIndex":1,"changeIndex":0,"receiveIndex":0},{"copayerIndex":2,"changeIndex":0,"receiveIndex":0},{"copayerIndex":3,"changeIndex":0,"receiveIndex":0},{"copayerIndex":4,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}'; var w = Wallet.fromObj(JSON.parse(o), { - storage: storage, networkOpts: {}, blockchainOpts: {}, }); @@ -2179,22 +2164,18 @@ describe('Wallet model', function() { describe('#read', function() { - var storage, network, blockchain; + var network, blockchain; beforeEach(function() { var s = function() {}; - storage = new s(); network = new Network(walletConfig.network); blockchain = new Blockchain(walletConfig.blockchain); - storage.setPassword = sinon.stub(); }); it('should fail to read an unexisting wallet', function(done) { - storage.getFirst = sinon.stub().yields(null); Wallet.read('123', { - storage: storage, networkOpts: {}, blockchainOpts: {}, }, function(err, w) { @@ -2205,10 +2186,7 @@ describe('Wallet model', function() { it('should not read a corrupted wallet', function(done) { - storage.getFirst = sinon.stub().yields(null, '{hola:1}'); - Wallet.read('123', { - storage: storage, networkOpts: {}, blockchainOpts: {}, }, function(err, w) { @@ -2218,9 +2196,7 @@ describe('Wallet model', function() { }); it('should read a wallet', function(done) { - storage.getFirst = sinon.stub().yields(null, JSON.parse(o)); Wallet.read('123', { - storage: storage, networkOpts: {}, blockchainOpts: {}, }, function(err, w) { @@ -2230,9 +2206,7 @@ describe('Wallet model', function() { }); it('should be able to import unencrypted legacy wallet TxProposal: v0', function(done) { - storage.getFirst = sinon.stub().yields(null, JSON.parse(legacyO)); Wallet.read('123', { - storage: storage, networkOpts: {}, blockchainOpts: {}, }, function(err, w) { @@ -2246,10 +2220,8 @@ describe('Wallet model', function() { }); it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function(done) { - storage.getFirst = sinon.stub().yields(null, JSON.parse(legacy1)); Wallet.read('123', { - storage: storage, networkOpts: {}, blockchainOpts: {}, }, function(err, w) { diff --git a/test/WalletLock.js b/test/WalletLock.js deleted file mode 100644 index ed72bfbc0..000000000 --- a/test/WalletLock.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -var WalletLock = copay.WalletLock; -var PrivateKey = copay.PrivateKey; -var Storage = copay.Storage; - - -var storage; -describe('WalletLock model', function() { - - beforeEach(function() { - storage = new Storage(requireMock('FakeLocalStorage').storageParams); - storage._setPassphrase('mysupercoolpassword'); - storage.clearAll(); - }); - - it('should fail with missing args', function() { - (function() { - new WalletLock() - }).should.throw('Argument'); - }); - - - it('should fail with missing args (case 2)', function() { - (function() { - new WalletLock(storage) - }).should.throw('Argument'); - }); - - it('should create an instance', function() { - var w = new WalletLock(storage, 'id'); - should.exist(w); - }); - - - it('should generate a sessionId with init', function(done) { - var w = new WalletLock(storage, 'id'); - var spy = sinon.spy(storage, 'getSessionId'); - w.init(function() { - spy.calledOnce.should.equal(true); - done(); - }); - }); - - it('#keepAlive should call getsessionId if not called before', function(done) { - var w = new WalletLock(storage, 'id'); - var spy = sinon.spy(storage, 'getSessionId'); - w.keepAlive(function() { - spy.calledOnce.should.equal(true); - done(); - }); - }); - - it('should NOT fail if locked already by me', function(done) { - var w = new WalletLock(storage, 'walletId2'); - w.keepAlive(function() { - var w2 = new WalletLock(storage, 'walletId2'); - w2.init(function() { - w2.keepAlive(function() { - w.sessionId.should.equal(w2.sessionId); - should.exist(w2); - done(); - }); - }); - }) - }); - - it('should FAIL if locked by someone else', function(done) { - var w = new WalletLock(storage, 'walletId'); - w.keepAlive(function() { - storage.setSessionId('session2', function() { - var w2 = new WalletLock(storage, 'walletId'); - w2.keepAlive(function(locked) { - should.exist(locked); - locked.message.should.contain('LOCKED'); - done(); - }); - }); - }); - }) - - it('should FAIL if locked by someone else but expired', function(done) { - var w = new WalletLock(storage, 'walletId'); - w.keepAlive(function() { - storage.setSessionId('session2', function() { - - var json = JSON.parse(storage.db.ls['lock::walletId']); - json.expireTs -= 3600 * 1000; - storage.db.ls['lock::walletId'] = JSON.stringify(json); - var w2 = new WalletLock(storage, 'walletId'); - w2.keepAlive(function(locked) { - w2.sessionId.should.equal('session2'); - should.not.exist(locked); - done(); - }); - }); - }); - }) -}); diff --git a/test/mocks/FakeBlockchain.js b/test/mocks/FakeBlockchain.js index b77a5a931..208688d62 100644 --- a/test/mocks/FakeBlockchain.js +++ b/test/mocks/FakeBlockchain.js @@ -60,4 +60,7 @@ FakeBlockchain.prototype.checkSentTx = function (tx, cb) { return cb(null, txid); }; +FakeBlockchain.prototype.removeAllListeners = function() { +}; + module.exports = FakeBlockchain; diff --git a/test/mocks/FakeLocalStorage.js b/test/mocks/FakeLocalStorage.js deleted file mode 100644 index b3714412e..000000000 --- a/test/mocks/FakeLocalStorage.js +++ /dev/null @@ -1,43 +0,0 @@ -//localstorage Mock - -function FakeLocalStorage() { - this.ls = {}; - this.type = 'DB'; -}; - -FakeLocalStorage.prototype.init = function() { -}; - -FakeLocalStorage.prototype.removeItem = function(key, cb) { - delete this.ls[key]; - cb(); -}; - -FakeLocalStorage.prototype.getItem = function(k, cb) { - return cb(this.ls[k]); -}; - - -FakeLocalStorage.prototype.allKeys = function(cb) { - return cb(Object.keys(this.ls)); -}; - -FakeLocalStorage.prototype.setItem = function(k, v, cb) { - this.ls[k] = v; - return cb(); -}; -FakeLocalStorage.prototype.clear = function(cb) { - this.ls = {}; - if (cb) return cb(); -} - -module.exports = FakeLocalStorage; - -module.exports.storageParams = { - password: '123', - db: new FakeLocalStorage(), - sessionStorage: new FakeLocalStorage(), - passphraseConfig: { - iterations: 1, - }, -}; diff --git a/util/build.js b/util/build.js index 5dc7abc33..f89dffed0 100644 --- a/util/build.js +++ b/util/build.js @@ -45,7 +45,7 @@ var createBundle = function(opts) { b.require('browser-request', { expose: 'request' }); - b.require('underscore'); + b.require('lodash'); b.require('querystring'); b.require('assert'); b.require('preconditions'); @@ -66,9 +66,6 @@ var createBundle = function(opts) { b.require('./js/models/Wallet', { expose: '../../js/models/Wallet' }); - b.require('./js/models/WalletLock', { - expose: '../js/models/WalletLock' - }); b.require('./js/models/Insight', { expose: '../js/models/Insight' }); @@ -92,15 +89,18 @@ var createBundle = function(opts) { }); if (!opts.disablePlugins) { - b.require('./plugins/GoogleDrive', { + b.require('./js/plugins/GoogleDrive', { expose: '../plugins/GoogleDrive' }); - b.require('./plugins/InsightStorage', { + b.require('./js/plugins/InsightStorage', { expose: '../plugins/InsightStorage' }); - b.require('./plugins/LocalStorage', { + b.require('./js/plugins/LocalStorage', { expose: '../plugins/LocalStorage' }); + b.require('./js/plugins/EncryptedInsightStorage', { + expose: '../plugins/EncryptedInsightStorage' + }); } b.require('./config', { @@ -111,9 +111,6 @@ var createBundle = function(opts) { //include dev dependencies b.require('sinon'); b.require('blanket'); - b.require('./test/mocks/FakeLocalStorage', { - expose: './mocks/FakeLocalStorage' - }); b.require('./test/mocks/FakeBlockchain', { expose: './mocks/FakeBlockchain' });