diff --git a/css/src/main.css b/css/src/main.css index 71ccd1ed8..bddec8dd0 100644 --- a/css/src/main.css +++ b/css/src/main.css @@ -1244,6 +1244,21 @@ label.postfix, span.postfix { height: 80px; } +.need-backup { + background: #C0392A; + -moz-box-shadow: 1px 1px 0px 0px #A02F23; + box-shadow: 1px 1px 0px 0px #A02F23; + position: absolute; + top: 22px; + left: 0px; + width: 14px; + height: 14px; + border-radius: 100%; + font-size: 9px; + padding-top: 2px; + color: #fff; +} + a:hover .photo-container { background: #34495E; color: #fff; diff --git a/js/controllers/import.js b/js/controllers/import.js index 895bc5790..6ee3bf798 100644 --- a/js/controllers/import.js +++ b/js/controllers/import.js @@ -18,71 +18,72 @@ angular.module('copayApp.controllers').controller('ImportController', $scope.$digest(); } - $scope.getFile = function() { - // If we use onloadend, we need to check the readyState. - reader.onloadend = function(evt) { - if (evt.target.readyState == FileReader.DONE) { // DONE == 2 - var encryptedObj = evt.target.result; + + $scope.getFile = function() { + // If we use onloadend, we need to check the readyState. + reader.onloadend = function(evt) { + if (evt.target.readyState == FileReader.DONE) { // DONE == 2 + var encryptedObj = evt.target.result; + updateStatus('Importing wallet - Procesing backup...'); + identityService.importWallet(encryptedObj, $scope.password, {}, function(err) { + if (err) { + $scope.loading = false; + $scope.error = 'Could not read wallet. Please check your password'; + } + }); + } + } + }; + + $scope.import = function(form) { + $scope.loading = true; + + if (form.$invalid) { + $scope.loading = false; + $scope.error = 'There is an error in the form'; + return; + } + + var backupFile = $scope.file; + var backupText = form.backupText.$modelValue; + var backupOldWallet = form.backupOldWallet.$modelValue; + var password = form.password.$modelValue; + + if (backupOldWallet) { + backupText = backupOldWallet.value; + } + + if (!backupFile && !backupText) { + $scope.loading = false; + $scope.error = 'Please, select your backup file'; + return; + } + + $scope.importOpts = {}; + + var skipFields = []; + + if ($scope.skipPublicKeyRing) + skipFields.push('publicKeyRing'); + + if ($scope.skipTxProposals) + skipFields.push('txProposals'); + + if (skipFields) + $scope.importOpts.skipFields = skipFields; + + + if (backupFile) { + reader.readAsBinaryString(backupFile); + } else { updateStatus('Importing wallet - Procesing backup...'); - identityService.importWallet(encryptedObj, $scope.password, {}, function(err){ + identityService.importWallet(encryptedObj, $scope.password, $scope.importOpts, function(err) { if (err) { $scope.loading = false; $scope.error = 'Could not read wallet. Please check your password'; } + copay.Compatibility.deleteOldWallet(backupOldWallet); }); } }; - }; - - $scope.import = function(form) { - $scope.loading = true; - - if (form.$invalid) { - $scope.loading = false; - $scope.error = 'There is an error in the form'; - return; - } - - var backupFile = $scope.file; - var backupText = form.backupText.$modelValue; - var backupOldWallet = form.backupOldWallet.$modelValue; - var password = form.password.$modelValue; - - if (backupOldWallet) { - backupText = backupOldWallet.value; - } - - if (!backupFile && !backupText) { - $scope.loading = false; - $scope.error = 'Please, select your backup file'; - return; - } - - $scope.importOpts = {}; - - var skipFields = []; - - if ($scope.skipPublicKeyRing) - skipFields.push('publicKeyRing'); - - if ($scope.skipTxProposals) - skipFields.push('txProposals'); - - if (skipFields) - $scope.importOpts.skipFields = skipFields; - - - if (backupFile) { - reader.readAsBinaryString(backupFile); - } else { - updateStatus('Importing wallet - Procesing backup...'); - identityService.importWallet(encryptedObj, $scope.password, $scope.importOpts, function(err){ - if (err) { - $scope.loading = false; - $scope.error = 'Could not read wallet. Please check your password'; - } - copay.Compatibility.deleteOldWallet(backupOldWallet); - }); - } - }; -}); + }); diff --git a/js/controllers/profile.js b/js/controllers/profile.js index 95c7003fb..b13a2565d 100644 --- a/js/controllers/profile.js +++ b/js/controllers/profile.js @@ -14,7 +14,7 @@ angular.module('copayApp.controllers').controller('ProfileController', function( $scope.backupProfilePlainText = backupService.profileEncrypted($rootScope.iden); $scope.hideViewProfileBackup = true; }; - + $scope.deleteWallet = function(w) { if (!w) return; identityService.deleteWallet(w, function(err) { @@ -28,8 +28,8 @@ angular.module('copayApp.controllers').controller('ProfileController', function( $scope.init = function() { if ($rootScope.quotaPerItem) { - $scope.perItem = $filter('noFractionNumber')($rootScope.quotaPerItem/1000,1); - $scope.nrWallets =parseInt($rootScope.quotaItems) - 1; + $scope.perItem = $filter('noFractionNumber')($rootScope.quotaPerItem / 1000, 1); + $scope.nrWallets = parseInt($rootScope.quotaItems) - 1; } }; @@ -37,13 +37,13 @@ angular.module('copayApp.controllers').controller('ProfileController', function( if (!$rootScope.iden) return; var wallets = $rootScope.iden.listWallets(); - var max =$rootScope.quotaPerItem; + var max = $rootScope.quotaPerItem; _.each(wallets, function(w) { var bits = w.sizes().total; - w.kb = $filter('noFractionNumber')(bits/1000, 1); + w.kb = $filter('noFractionNumber')(bits / 1000, 1); if (max) { - w.usage = $filter('noFractionNumber')(bits/max * 100, 0); + w.usage = $filter('noFractionNumber')(bits / max * 100, 0); } }); @@ -73,15 +73,15 @@ angular.module('copayApp.controllers').controller('ProfileController', function( }); }; - $scope.deleteProfile = function () { - identityService.deleteProfile(function (err, res) { + $scope.deleteProfile = function() { + identityService.deleteProfile(function(err, res) { if (err) { log.warn(err); notification.error('Error', 'Could not delete profile'); return; } - $location.path('/'); - setTimeout(function () { + $location.path('/'); + setTimeout(function() { notification.error('Success', 'Profile successfully deleted'); }, 1); }); diff --git a/js/models/Identity.js b/js/models/Identity.js index 8062b3ba9..53984fbdf 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -62,6 +62,8 @@ function Identity(opts) { this.walletIds = opts.walletIds || {}; this.wallets = opts.wallets || {}; this.focusedTimestamps = opts.focusedTimestamps || {}; + this.backupNeeded = opts.backupNeeded || false; + }; @@ -91,7 +93,9 @@ Identity.prototype.getName = function() { * @return {undefined} */ Identity.create = function(opts, cb) { - opts = _.extend({}, opts); + opts = _.extend({ + backupNeeded: true + }, opts); var iden = new Identity(opts); iden.store(_.extend(opts, { @@ -265,21 +269,36 @@ Identity.prototype.toObj = function() { return _.extend({ walletIds: _.isEmpty(this.wallets) ? this.walletsIds : _.keys(this.wallets), }, - _.pick(this, 'version', 'fullName', 'password', 'email', 'focusedTimestamps')); + _.pick(this, 'version', 'fullName', 'password', 'email', 'backupNeeded', 'focusedTimestamps')); }; Identity.prototype.exportEncryptedWithWalletInfo = function(opts) { var crypto = opts.cryptoUtil || cryptoUtil; + return crypto.encrypt(this.password, this.exportWithWalletInfo(opts)); }; +Identity.prototype.setBackupNeeded = function() { + this.backupNeeded = true; + this.store({ + noWallets: true + }, function() {}); +} + +Identity.prototype.setBackupDone = function() { + this.backupNeeded = false; + this.store({ + noWallets: true + }, function() {}); +} + Identity.prototype.exportWithWalletInfo = function(opts) { return _.extend({ wallets: _.map(this.wallets, function(wallet) { return wallet.toObj(); }) }, - _.pick(this, 'version', 'fullName', 'password', 'email') + _.pick(this, 'version', 'fullName', 'password', 'email', 'backupNeeded') ); }; @@ -288,15 +307,15 @@ Identity.prototype.exportWithWalletInfo = function(opts) { * @param {Function} cb */ Identity.prototype.store = function(opts, cb) { - log.debug('Storing profile'); - var self = this; opts = opts || {}; var storeFunction = opts.failIfExists ? self.storage.createItem : self.storage.setItem; storeFunction.call(self.storage, this.getId(), this.toObj(), function(err) { - if (err) return cb(err); + if (err) { + return cb(err); + } if (opts.noWallets) return cb(); @@ -323,9 +342,9 @@ Identity.prototype.remove = function(opts, cb) { if (err) return cb(err); cb(); }); - }, function (err) { + }, function(err) { if (err) return cb(err); - + self.storage.removeItem(self.getId(), function(err) { if (err) return cb(err); self.emitAndKeepAlive('closed'); @@ -552,13 +571,16 @@ Identity.prototype.createWallet = function(opts, cb) { var self = this; + var w = new walletClass(opts); self.bindWallet(w); self.updateFocusedTimestamp(w.getId()); self.storeWallet(w, function(err) { if (err) return cb(err); + + self.backupNeeded = true; self.store({ - noWallets: true + noWallets: true, }, function(err) { return cb(err, w); }); diff --git a/js/services/backupService.js b/js/services/backupService.js index 775b25be3..1fb190371 100644 --- a/js/services/backupService.js +++ b/js/services/backupService.js @@ -38,6 +38,7 @@ BackupService.prototype.profileEncrypted = function(iden) { BackupService.prototype.profileDownload = function(iden) { var ew = this.profileEncrypted(iden); + iden.setBackupDone(); var name = iden.fullName; var filename = name + '-profile.json'; this._download(ew, name, filename) diff --git a/js/services/identityService.js b/js/services/identityService.js index 8a97fd046..53cf4331f 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -48,6 +48,7 @@ angular.module('copayApp.services') passphraseConfig: config.passphraseConfig, failIfExists: true, }, function(err, iden) { + if (err) return cb(err); preconditions.checkState(iden); root.bind(iden); @@ -68,19 +69,19 @@ angular.module('copayApp.services') }; root.setServerStatus = function(headers) { - if (!headers) + if (!headers) return; - if (headers['X-Email-Needs-Validation']) - $rootScope.needsEmailConfirmation = true; + if (headers['X-Email-Needs-Validation']) + $rootScope.needsEmailConfirmation = true; else - $rootScope.needsEmailConfirmation = null; + $rootScope.needsEmailConfirmation = null; - if (headers['X-Quota-Per-Item']) - $rootScope.quotaPerItem = parseInt(headers['X-Quota-Per-Item']); + if (headers['X-Quota-Per-Item']) + $rootScope.quotaPerItem = parseInt(headers['X-Quota-Per-Item']); - if (headers['X-Quota-Items-Limit']) - $rootScope.quotaItems = parseInt(headers['X-Quota-Items-Limit']); + if (headers['X-Quota-Items-Limit']) + $rootScope.quotaItems = parseInt(headers['X-Quota-Items-Limit']); }; root.open = function(email, password, cb) { @@ -102,7 +103,7 @@ angular.module('copayApp.services') }); }; - root.deleteProfile = function (cb) { + root.deleteProfile = function(cb) { $rootScope.iden.remove(null, cb); }; diff --git a/test/Identity.js b/test/Identity.js index f46c72f0e..f39d3db42 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -111,7 +111,14 @@ describe('Identity model', function() { params: params }; }; - + var orig; + beforeEach(function() { + orig = Identity.prototype.store; + sinon.stub(Identity.prototype, 'store').yields(null); + }); + afterEach(function() { + Identity.prototype.store = orig; + }); describe('new Identity()', function() { it('returns an identity', function() { var iden = new Identity(getDefaultParams()); @@ -124,7 +131,6 @@ describe('Identity model', function() { it('should create and store identity', function() { var args = createIdentity(); args.blockchain.on = sinon.stub(); - sinon.stub(Identity.prototype, 'store').yields(null); Identity.create(args.params, function(err, iden) { should.not.exist(err); should.exist(iden); @@ -240,7 +246,6 @@ describe('Identity model', function() { args = createIdentity(); args.params.noWallets = true; var old = Identity.prototype.createWallet; - sinon.stub(Identity.prototype, 'store').yields(null); Identity.create(args.params, function(err, res) { iden = res; }); @@ -297,7 +302,6 @@ describe('Identity model', function() { args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}'); var backup = Wallet.fromUntrustedObj; args.params.noWallets = true; - sinon.stub(Identity.prototype, 'store').yields(null); sinon.stub().returns(args.wallet); var opts = { @@ -390,8 +394,6 @@ describe('Identity model', function() { beforeEach(function() { args = createIdentity(); args.params.Async = net = sinon.stub(); - - sinon.stub(Identity.prototype, 'store').yields(null); net.cleanUp = sinon.spy(); net.on = sinon.stub(); net.start = sinon.spy(); diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index 3abe1efaf..f468f5917 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -76,6 +76,7 @@ describe("Angular services", function() { a[Waddr] = 200; w.getBalance = sinon.stub().yields(null, 100000001, a, 90000002, 5); + //retuns values in DEFAULT UNIT(bits) balanceService.update(w, function() { var b = w.balanceInfo; @@ -90,7 +91,7 @@ describe("Angular services", function() { expect(b.balanceByAddr[Waddr]).to.equal(2); expect(b.safeUnspentCount).to.equal(5); expect(b.topAmount).to.equal(899800.02); - },false); + }, false); })); }); diff --git a/views/includes/head.html b/views/includes/head.html index 87bf9307f..68736f757 100644 --- a/views/includes/head.html +++ b/views/includes/head.html @@ -16,10 +16,13 @@
-

Backup Profile

+

Profile [ Needs Backup ]

It's important to backup your profile so that you can recover it in case of disaster. The backup will include all your profile's wallets