diff --git a/index.html b/index.html index b7b0c4eb2..c49932b8d 100644 --- a/index.html +++ b/index.html @@ -52,22 +52,20 @@ - + + + +
+ class="sidebar" + ng-if="$root.iden"> -
+
diff --git a/js/controllers/create.js b/js/controllers/create.js new file mode 100644 index 000000000..4f9b55740 --- /dev/null +++ b/js/controllers/create.js @@ -0,0 +1,54 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('CreateController', + function($scope, $rootScope, $location, $timeout, controllerUtils, backupService, notification) { + + $rootScope.fromSetup = true; + $scope.loading = false; + $scope.walletPassword = $rootScope.walletPassword; + $scope.isMobile = !!window.cordova; + $scope.hideAdv = true; + $scope.networkName = config.networkName; + + // ng-repeat defined number of times instead of repeating over array? + $scope.getNumber = function(num) { + return new Array(num); + } + + $scope.totalCopayers = config.wallet.totalCopayers; + $scope.TCValues = _.range(1, config.limits.totalCopayers + 1); + + var updateRCSelect = function(n) { + var maxReq = copay.Wallet.getMaxRequiredCopayers(n); + $scope.RCValues = _.range(1, maxReq + 1); + $scope.requiredCopayers = Math.min(parseInt(n / 2 + 1), maxReq); + }; + + updateRCSelect($scope.totalCopayers); + + $scope.$watch('totalCopayers', function(tc) { + updateRCSelect(tc); + }); + + $scope.create = function(form) { + if (form && form.$invalid) { + notification.error('Error', 'Please enter the required fields'); + return; + } + $scope.loading = true; + var opts = { + requiredCopayers: $scope.requiredCopayers, + totalCopayers: $scope.totalCopayers, + name: $scope.walletName, + privateKeyHex: $scope.private, + networkName: $scope.networkName, + }; + $rootScope.iden.createWallet(opts, function(err, w) { + $rootScope.iden.closeWallet($rootScope.wallet.id, function() { + $scope.loading = false; + $rootScope.wallet = w; + controllerUtils.bindWallet(w, $scope); + }); + }); + }; + }); diff --git a/js/controllers/createProfile.js b/js/controllers/createProfile.js index 7fc321fe4..14d003a29 100644 --- a/js/controllers/createProfile.js +++ b/js/controllers/createProfile.js @@ -13,10 +13,8 @@ angular.module('copayApp.controllers').controller('CreateProfileController', fun walletDefaults: config.wallet, passphrase: config.passphrase, }, function(err, iden ,w) { - $scope.loading = false; $rootScope.iden = iden; $rootScope.wallet = w; - controllerUtils.bindWallet(w, $scope); }); } diff --git a/js/controllers/home.js b/js/controllers/home.js index 39c28adb1..418119526 100644 --- a/js/controllers/home.js +++ b/js/controllers/home.js @@ -18,7 +18,6 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc controllerUtils.onErrorDigest( $scope, (err.toString()||'').match('PNOTFOUND') ? 'Profile not found' : 'Unknown error'); } else { - $scope.loading = false; $rootScope.iden = iden; $rootScope.wallet = w; controllerUtils.bindWallet(w, $scope); diff --git a/js/controllers/join.js b/js/controllers/join.js index f6f5e5d66..931c53104 100644 --- a/js/controllers/join.js +++ b/js/controllers/join.js @@ -1,8 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('JoinController', - function($scope, $rootScope, $timeout, identity, controllerUtils, Passphrase, notification) { - controllerUtils.redirIfLogged(); + function($scope, $rootScope, $timeout, controllerUtils, notification) { $rootScope.fromSetup = false; $scope.loading = false; $scope.isMobile = !!window.cordova; @@ -120,31 +119,32 @@ angular.module('copayApp.controllers').controller('JoinController', $scope.loading = true; - Passphrase.getBase64Async($scope.joinPassword, function(passphrase) { - identity.joinCreateSession({ - secret: $scope.connectionId, - nickname: $scope.nickname, - passphrase: passphrase, - privateHex: $scope.private, - }, function(err, w) { + $rootScope.iden.joinWallet({ + secret: $scope.connectionId, + nickname: $scope.nickname, + privateHex: $scope.private, + }, function(err, w) { - $scope.loading = false; - if (err || !w) { - if (err === 'joinError') - notification.error('Fatal error connecting to Insight server'); - else if (err === 'walletFull') - notification.error('The wallet is full'); - else if (err === 'badNetwork') - notification.error('Network Error', 'Wallet network configuration missmatch'); - else if (err === 'badSecret') - notification.error('Bad secret', 'The secret string you entered is invalid'); - else - notification.error('Unknown error'); - controllerUtils.onErrorDigest(); - } else { - controllerUtils.startNetwork(w, $scope); - } - }); + $scope.loading = false; + if (err || !w) { + if (err === 'joinError') + notification.error('Fatal error connecting to Insight server'); + else if (err === 'walletFull') + notification.error('The wallet is full'); + else if (err === 'badNetwork') + notification.error('Network Error', 'Wallet network configuration missmatch'); + else if (err === 'badSecret') + notification.error('Bad secret', 'The secret string you entered is invalid'); + else + notification.error('Unknown error'); + controllerUtils.onErrorDigest(); + } else { + $rootScope.iden.closeWallet($rootScope.wallet.id, function() { + $scope.loading = false; + $rootScope.wallet = w; + controllerUtils.bindWallet(w, $scope); + }); + } }); } }); diff --git a/js/controllers/more.js b/js/controllers/more.js index c251bb7b5..dd7c6b1c7 100644 --- a/js/controllers/more.js +++ b/js/controllers/more.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('MoreController', - function($scope, $rootScope, $location, $filter, backupService, identity, controllerUtils, notification, rateService) { + function($scope, $rootScope, $location, $filter, backupService, controllerUtils, notification, rateService) { var w = $rootScope.wallet; $scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; @@ -81,7 +81,7 @@ angular.module('copayApp.controllers').controller('MoreController', }; $scope.deleteWallet = function() { - identity.delete(w.id, function() { + $rootScope.iden.deleteWallet(w.id, function() { controllerUtils.logout(); }); }; diff --git a/js/controllers/open.js b/js/controllers/open.js index 278e2d889..b9e88bd52 100644 --- a/js/controllers/open.js +++ b/js/controllers/open.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('OpenController', function($scope, $rootScope, $location, identity, controllerUtils, Passphrase, notification) { +angular.module('copayApp.controllers').controller('OpenController', function($scope, $rootScope, $location, controllerUtils, Passphrase, notification) { controllerUtils.redirIfLogged(); if ($rootScope.pendingPayment) { diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js index 6cccc126f..b52545e43 100644 --- a/js/controllers/sidebar.js +++ b/js/controllers/sidebar.js @@ -58,9 +58,9 @@ angular.module('copayApp.controllers').controller('SidebarController', function( } if ($rootScope.wallet) { - $scope.$on('$idleWarn', function(a,countdown) { - if (!(countdown%5)) - notification.warning('Session will be closed', $filter('translate')('Your session is about to expire due to inactivity in') + ' ' + countdown + ' ' + $filter('translate')('seconds')); + $scope.$on('$idleWarn', function(a, countdown) { + if (!(countdown % 5)) + notification.warning('Session will be closed', $filter('translate')('Your session is about to expire due to inactivity in') + ' ' + countdown + ' ' + $filter('translate')('seconds')); }); $scope.$on('$idleTimeout', function() { @@ -71,4 +71,21 @@ angular.module('copayApp.controllers').controller('SidebarController', function( $rootScope.wallet.keepAlive(); }); } + + $scope.switchWallet = function(id) { + var iden = $rootScope.iden; + controllerUtils.unbindWallet($scope); + + iden.openWallet(id, null, function(err, w) { + if (err) { + notification.warning('Could not open wallet'); + } else { + iden.closeWallet($rootScope.wallet.id, function() { + $scope.loading = false; + $rootScope.wallet = w; + controllerUtils.bindWallet(w, $scope); + }); + } + }); + }; }); diff --git a/js/directives.js b/js/directives.js index d9a9a860a..658ad012e 100644 --- a/js/directives.js +++ b/js/directives.js @@ -111,14 +111,13 @@ angular.module('copayApp.directives') }; } ]) - .directive('walletSecret', ['walletFactory', - function(walletFactory) { + .directive('walletSecret', function() { return { require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var validator = function(value) { var a = new Address(value); - ctrl.$setValidity('walletSecret', !a.isValid() && Boolean(walletFactory.decodeSecret(value))); + ctrl.$setValidity('walletSecret', !a.isValid() && Boolean(copay.Wallet.decodeSecret(value))); return value; }; @@ -126,7 +125,7 @@ angular.module('copayApp.directives') } }; } - ]) + ) .directive('loading', function() { return { restrict: 'A', diff --git a/js/models/Identity.js b/js/models/Identity.js index fe0d5a6b1..d64fab6f3 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -94,8 +94,8 @@ Identity._walletRead = function(id, s, n, b, skip, cb) { return Wallet.read(id, s, n, b, skip, cb); }; -Identity._walletDelete = function(id, cb) { - return Wallet.delete(id, cb); +Identity._walletDelete = function(id, s, cb) { + return Wallet.delete(id, s, cb); }; /* for stubbing */ @@ -133,6 +133,7 @@ Identity.create = function(email, password, opts, cb) { requiredCopayers: 1, totalCopayers: 1, password: password, + name: 'general', }); iden.createWallet(wopts, function(err, w) { return cb(null, iden, w); @@ -170,10 +171,22 @@ Identity.open = function(email, password, opts, cb) { Identity._openProfile(email, password, iden.storage, function(err, profile) { if (err) return cb(err); iden.profile = profile; - var wid = iden.listWallets()[0].id; - iden.openWallet(wid, password, function(err, w) { - return cb(err, iden, w); - }) + + var wids = _.pluck(iden.listWallets(), 'id'); + + + while (1) { + var wid = wids.shift(); + if (!wid) + return new Error('Could not open any wallet from profile'); + + iden.openWallet(wid, password, function(err, w) { + if (err) + log.info('Cound not open wallet id:' + wid + '. Skipping') + else + return cb(err, iden, w); + }) + } }); }; @@ -222,6 +235,16 @@ Identity.prototype.store = function(opts, cb) { }; +Identity.prototype._cleanUp = function() { + log.info('Cleaning Network connections') + var self = this; + + _.each(['livenet', 'testnet'], function(n) { + self.networks[n].cleanUp(); + self.blockchains[n].destroy(); + }); +}; + /** * @desc Closes the wallet and disconnects all services */ @@ -234,12 +257,17 @@ Identity.prototype.close = function(cb) { return cb ? cb() : null; } + var self = this; _.each(this.openWallets, function(w) { w.close(function(err) { + console.log('[Identity.js.239:err:]', err); //TODO if (err) return cb(err); - if (++i == l && cb) - return cb(); + console.log('[Identity.js.241]', i, l); //TODO + if (++i == l) { + self._cleanUp(); + if (cb) return cb(); + } }) }); }; @@ -268,6 +296,22 @@ Identity.prototype.importWallet = function(base64, password, skipFields, cb) { w.store(cb); }); }; + +Identity.prototype.closeWallet = function(wid, cb) { + var w = _.findWhere(this.openWallets, function(w) { + w.id === wid; + }); + preconditions.checkState(w); + + var self = this; + w.close(function(err) { + self.openWallets = _.without(self.openWallets, function(id) { + id === wid + }); + return cb(err); + }); +}; + /** * @desc This method prepares options for a new Wallet * @@ -345,13 +389,15 @@ Identity.prototype.createWallet = function(opts, cb) { var w = Identity._newWallet(opts); this.addWallet(w, function(err) { if (err) return cb(err); + self.openWallets.push(w); + self.profile.setLastOpenedTs(w.id, function(err) { return cb(err, w); }); }); }; -// add open wallet? +// add wallet (import) Identity.prototype.addWallet = function(wallet, cb) { preconditions.checkArgument(wallet); preconditions.checkArgument(wallet.getId); @@ -359,10 +405,11 @@ Identity.prototype.addWallet = function(wallet, cb) { preconditions.checkState(this.profile); var self = this; - self.profile.addWallet(wallet.id, {}, function(err) { + self.profile.addWallet(wallet.getId(), { + name: wallet.name + }, function(err) { if (err) return cb(err); - self.openWallets.push(wallet); wallet.store(function(err) { return cb(err); }); @@ -400,15 +447,19 @@ Identity.prototype._checkVersion = function(inVersion) { * @return */ Identity.prototype.openWallet = function(walletId, password, cb) { + console.log('[Identity.js.434:openWallet:]', walletId); //TODO preconditions.checkArgument(cb); var self = this; - self.storage.setPassword(password); + if (password) + self.storage.setPassword(password); + // TODO // self.migrateWallet(walletId, password, function() { Identity._walletRead(walletId, self.storage, self.networks, self.blockchains, [], function(err, w) { if (err) return cb(err); + self.openWallets.push(w); w.store(function(err) { self.profile.setLastOpenedTs(walletId, function() { @@ -421,7 +472,8 @@ Identity.prototype.openWallet = function(walletId, password, cb) { Identity.prototype.listWallets = function(a) { - return this.profile.listWallets(); + var ret = this.profile.listWallets(); + return ret; }; /** @@ -468,7 +520,6 @@ Identity.prototype.decodeSecret = function(secret) { * * @param {object} opts * @param {string} opts.secret - the wallet secret - * @param {string} opts.password - a password to use to encrypt the wallet for persistance * @param {string} opts.nickname - a nickname for the current user * @param {string} opts.privateHex - the private extended master key * @param {walletCreationCallback} cb - a callback @@ -476,8 +527,6 @@ Identity.prototype.decodeSecret = function(secret) { Identity.prototype.joinWallet = function(opts, cb) { preconditions.checkArgument(opts); preconditions.checkArgument(opts.secret); - preconditions.checkArgument(opts.password); - preconditions.checkArgument(opts.nickname); preconditions.checkArgument(cb); var self = this; var decodedSecret = this.decodeSecret(opts.secret); @@ -534,8 +583,10 @@ Identity.prototype.joinWallet = function(opts, cb) { walletOpts.id = data.walletId; walletOpts.privateKey = privateKey; - walletOpts.nickname = opts.nickname; - walletOpts.password = opts.password; + walletOpts.nickname = opts.nickname || self.profile.name; + + if (opts.password) + walletOpts.password = opts.password; self.createWallet(walletOpts, function(err, w) { diff --git a/js/models/Profile.js b/js/models/Profile.js index 83b810c48..37d6fe2ca 100644 --- a/js/models/Profile.js +++ b/js/models/Profile.js @@ -12,7 +12,7 @@ function Profile(info, storage) { this.hash = info.hash; this.email = info.email; - this.extra = info.extra; + this.extra = info.extra || {}; this.walletInfos = info.walletInfos || {}; this.key = Profile.key(this.hash); @@ -69,7 +69,7 @@ Profile.prototype.getWallet = function(walletId, cb) { Profile.prototype.listWallets = function(opts, cb) { return _.sortBy(this.walletInfos, function(winfo) { - return winfo.lastOpenedTs || winfo.createdTs; + return -winfo.lastOpenedTs || -winfo.createdTs; }); }; @@ -141,4 +141,9 @@ Profile.prototype.store = function(opts, cb) { }); }; + +Profile.prototype.getName = function() { + return this.extra.nickname || this.email; +}; + module.exports = Profile; diff --git a/js/models/Storage.js b/js/models/Storage.js index 0c4a4daa6..8c57e3366 100644 --- a/js/models/Storage.js +++ b/js/models/Storage.js @@ -183,12 +183,13 @@ Storage.prototype.delete = function(key, cb) { }; Storage.prototype.deletePrefix = function(prefix, cb) { - storage.getFirst(prefix, function(err, v, k) { - if (err && !v) return cb(err); + var self = this; + this.getFirst(prefix, function(err, v, k) { + if (err || !v) return cb(err); - storage.delete(k, function(err) { + self.delete(k, function(err) { if (err) return cb(err); - storage.deletePrefix(prefix, cb); + self.deletePrefix(prefix, cb); }) }); }; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 2a97fe4bd..88cc0f7cb 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -241,6 +241,7 @@ Wallet.read = function(walletId, storage, network, blockchain, skipFields, cb) { var w, err; obj.id = walletId; try { + log.debug('## OPENING Wallet: ' + walletId); w = self.fromObj(obj, storage, network, blockchain, skipFields); } catch (e) { log.debug("ERROR: ", e.message); @@ -892,6 +893,7 @@ Wallet.prototype.netStart = function() { self.emit('connectionError'); }); + log.debug('Starting wallet networking'); net.start(startOpts, function() { self._setBlockchainListeners(); self.emit('ready', net.getPeer()); @@ -2536,10 +2538,8 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) */ Wallet.prototype.close = function(cb) { var self = this; - log.debug('## CLOSING Wallet'); + log.debug('## CLOSING Wallet: ' + this.id); this.lock.release(function() { - self.network.cleanUp(); - self.blockchain.destroy(); if (cb) return cb(); }); }; diff --git a/js/routes.js b/js/routes.js index 37eebafb6..9428638e2 100644 --- a/js/routes.js +++ b/js/routes.js @@ -20,7 +20,7 @@ angular }) .when('/join', { templateUrl: 'views/join.html', - validate: false + validate: true }) .when('/import', { templateUrl: 'views/import.html', diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 8bdc230c7..d2bd34750 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -154,13 +154,18 @@ angular.module('copayApp.services') }); }; + + root.unbindWallet = function($scope) { + var w =$rootScope.wallet; + w.removeAllListeners(); + }; + root.bindWallet = function(w, $scope) { root.setupRootVariables(); root.installWalletHandlers(w, $scope); root.updateAddressList(); notification.enableHtml5Mode(); // for chrome: if support, enable it w.netStart(); - }; // TODO movie this to wallet diff --git a/views/copayers.html b/views/copayers.html index 3edc3059c..1de46a3bc 100644 --- a/views/copayers.html +++ b/views/copayers.html @@ -1,11 +1,9 @@
-
-
- Copay -
-
-
+ +
+
+
Step 3 diff --git a/views/create.html b/views/create.html index 89f91210f..868e7db12 100644 --- a/views/create.html +++ b/views/create.html @@ -35,6 +35,22 @@

(*) The limits are imposed by the bitcoin network.

+ + + + Show + Hide + advanced options + +
+ + + +

+ +

+ +
-
-
- Copay -
-
-
-
-

Join a Wallet in Creation

-
-