Merge pull request #686 from maraoz/automatic/backup
Automatic backup after create/join
This commit is contained in:
commit
03993b5172
9 changed files with 150 additions and 79 deletions
|
|
@ -849,6 +849,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
|
||||||
<script src="js/services/controllerUtils.js"></script>
|
<script src="js/services/controllerUtils.js"></script>
|
||||||
<script src="js/services/passphrase.js"></script>
|
<script src="js/services/passphrase.js"></script>
|
||||||
<script src="js/services/notifications.js"></script>
|
<script src="js/services/notifications.js"></script>
|
||||||
|
<script src="js/services/backupService.js"></script>
|
||||||
|
|
||||||
<script src="js/controllers/header.js"></script>
|
<script src="js/controllers/header.js"></script>
|
||||||
<script src="js/controllers/footer.js"></script>
|
<script src="js/controllers/footer.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@ var copayApp = window.copayApp = angular.module('copayApp',[
|
||||||
'monospaced.qrcode',
|
'monospaced.qrcode',
|
||||||
'notifications',
|
'notifications',
|
||||||
'copayApp.filters',
|
'copayApp.filters',
|
||||||
|
'copayApp.services',
|
||||||
'copayApp.controllers',
|
'copayApp.controllers',
|
||||||
'copayApp.directives',
|
'copayApp.directives',
|
||||||
'copayApp.services',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
angular.module('copayApp.filters', []);
|
angular.module('copayApp.filters', []);
|
||||||
|
angular.module('copayApp.services', []);
|
||||||
angular.module('copayApp.controllers', []);
|
angular.module('copayApp.controllers', []);
|
||||||
angular.module('copayApp.directives', []);
|
angular.module('copayApp.directives', []);
|
||||||
angular.module('copayApp.services', []);
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,33 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('BackupController',
|
angular.module('copayApp.controllers').controller('BackupController',
|
||||||
function($scope, $rootScope, $location, $window, $timeout, $modal) {
|
function($scope, $rootScope, $location, $window, $timeout, $modal, backupService) {
|
||||||
$scope.title = 'Backup';
|
$scope.title = 'Backup';
|
||||||
|
|
||||||
var _getEncryptedWallet = function() {
|
|
||||||
var wallet = $rootScope.wallet.toEncryptedObj();
|
|
||||||
return wallet;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.download = function() {
|
$scope.download = function() {
|
||||||
var timestamp = +(new Date);
|
backupService.download($rootScope.wallet);
|
||||||
var walletName = ($rootScope.wallet.name ? $rootScope.wallet.name : '') + '-' + $rootScope.wallet.id ;
|
|
||||||
var filename = walletName + '-' + timestamp + '.json.aes';
|
|
||||||
var wallet = _getEncryptedWallet();
|
|
||||||
var blob = new Blob([wallet], {type: 'text/plain;charset=utf-8'});
|
|
||||||
// show a native save dialog if we are in the shell
|
|
||||||
// and pass the wallet to the shell to convert to node Buffer
|
|
||||||
if (window.cshell) {
|
|
||||||
return window.cshell.send('backup:download', {
|
|
||||||
name: walletName,
|
|
||||||
wallet: wallet
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// otherwise lean on the browser implementation
|
|
||||||
saveAs(blob, filename);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.openModal = function () {
|
$scope.openModal = function() {
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
templateUrl: 'backupModal.html',
|
templateUrl: 'backupModal.html',
|
||||||
controller: ModalInstanceCtrl,
|
controller: ModalInstanceCtrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
modalInstance.result.then(sendEmail);
|
modalInstance.result.then(function(email) {
|
||||||
};
|
backupService.sendEmail(email, $rootScope.wallet);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var sendEmail = function(email) {
|
});
|
||||||
var body = _getEncryptedWallet();
|
|
||||||
var subject = ($rootScope.wallet.name ? $rootScope.wallet.name + ' - ' : '') + $rootScope.wallet.id;
|
|
||||||
var href = 'mailto:' + email + '?'
|
|
||||||
+ 'subject=[Copay Backup] ' + subject + '&'
|
|
||||||
+ 'body=' + body;
|
|
||||||
|
|
||||||
if (window.cshell) {
|
var ModalInstanceCtrl = function($scope, $modalInstance) {
|
||||||
return window.cshell.send('backup:email', href);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newWin = $window.open(href, '_blank', 'scrollbars=yes,resizable=yes,width=10,height=10');
|
$scope.submit = function(form) {
|
||||||
|
|
||||||
if (newWin) {
|
|
||||||
$timeout(function() {
|
|
||||||
newWin.close();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
var ModalInstanceCtrl = function ($scope, $modalInstance) {
|
|
||||||
|
|
||||||
$scope.submit = function (form) {
|
|
||||||
$modalInstance.close($scope.email);
|
$modalInstance.close($scope.email);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.cancel = function () {
|
$scope.cancel = function() {
|
||||||
$modalInstance.dismiss('cancel');
|
$modalInstance.dismiss('cancel');
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ var valid_pairs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('SetupController',
|
angular.module('copayApp.controllers').controller('SetupController',
|
||||||
function($scope, $rootScope, $location, $timeout, walletFactory, controllerUtils, Passphrase) {
|
function($scope, $rootScope, $location, $timeout, walletFactory, controllerUtils, Passphrase, backupService) {
|
||||||
|
|
||||||
$rootScope.videoInfo = {};
|
$rootScope.videoInfo = {};
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
@ -84,6 +84,7 @@ angular.module('copayApp.controllers').controller('SetupController',
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
};
|
};
|
||||||
var w = walletFactory.create(opts);
|
var w = walletFactory.create(opts);
|
||||||
|
backupService.download(w);
|
||||||
controllerUtils.startNetwork(w, $scope);
|
controllerUtils.startNetwork(w, $scope);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('SigninController',
|
angular.module('copayApp.controllers').controller('SigninController',
|
||||||
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase) {
|
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, backupService) {
|
||||||
var cmp = function(o1, o2){
|
var cmp = function(o1, o2) {
|
||||||
var v1 = o1.show.toLowerCase(), v2 = o2.show.toLowerCase();
|
var v1 = o1.show.toLowerCase(),
|
||||||
return v1 > v2 ? 1 : ( v1 < v2 ) ? -1 : 0;
|
v2 = o2.show.toLowerCase();
|
||||||
|
return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0;
|
||||||
};
|
};
|
||||||
$rootScope.videoInfo = {};
|
$rootScope.videoInfo = {};
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
@ -14,23 +15,31 @@ angular.module('copayApp.controllers').controller('SigninController',
|
||||||
|
|
||||||
$scope.open = function(form) {
|
$scope.open = function(form) {
|
||||||
if (form && form.$invalid) {
|
if (form && form.$invalid) {
|
||||||
$rootScope.$flashMessage = { message: 'Please, enter required fields', type: 'error'};
|
$rootScope.$flashMessage = {
|
||||||
|
message: 'Please, enter required fields',
|
||||||
|
type: 'error'
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
var password = form.openPassword.$modelValue;
|
var password = form.openPassword.$modelValue;
|
||||||
|
|
||||||
Passphrase.getBase64Async(password, function(passphrase){
|
Passphrase.getBase64Async(password, function(passphrase) {
|
||||||
var w, errMsg;
|
var w, errMsg;
|
||||||
try{
|
try {
|
||||||
var w = walletFactory.open($scope.selectedWalletId, { passphrase: passphrase});
|
var w = walletFactory.open($scope.selectedWalletId, {
|
||||||
} catch (e){
|
passphrase: passphrase
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
errMsg = e.message;
|
errMsg = e.message;
|
||||||
};
|
};
|
||||||
if (!w) {
|
if (!w) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$rootScope.$flashMessage = { message: errMsg || 'Wrong password', type: 'error'};
|
$rootScope.$flashMessage = {
|
||||||
|
message: errMsg || 'Wrong password',
|
||||||
|
type: 'error'
|
||||||
|
};
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -40,31 +49,48 @@ angular.module('copayApp.controllers').controller('SigninController',
|
||||||
|
|
||||||
$scope.join = function(form) {
|
$scope.join = function(form) {
|
||||||
if (form && form.$invalid) {
|
if (form && form.$invalid) {
|
||||||
$rootScope.$flashMessage = { message: 'Please, enter required fields', type: 'error'};
|
$rootScope.$flashMessage = {
|
||||||
|
message: 'Please, enter required fields',
|
||||||
|
type: 'error'
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
walletFactory.network.on('badSecret', function() {
|
walletFactory.network.on('badSecret', function() {});
|
||||||
});
|
|
||||||
|
|
||||||
Passphrase.getBase64Async($scope.joinPassword, function(passphrase){
|
Passphrase.getBase64Async($scope.joinPassword, function(passphrase) {
|
||||||
walletFactory.joinCreateSession($scope.connectionId, $scope.nickname, passphrase, function(err,w) {
|
walletFactory.joinCreateSession($scope.connectionId, $scope.nickname, passphrase, function(err, w) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
if (err || !w) {
|
if (err || !w) {
|
||||||
if (err === 'joinError')
|
if (err === 'joinError')
|
||||||
$rootScope.$flashMessage = { message: 'Can not find peer'};
|
$rootScope.$flashMessage = {
|
||||||
|
message: 'Can\'t find peer'
|
||||||
|
};
|
||||||
else if (err === 'walletFull')
|
else if (err === 'walletFull')
|
||||||
$rootScope.$flashMessage = { message: 'The wallet is full', type: 'error'};
|
$rootScope.$flashMessage = {
|
||||||
|
message: 'The wallet is full',
|
||||||
|
type: 'error'
|
||||||
|
};
|
||||||
else if (err === 'badNetwork')
|
else if (err === 'badNetwork')
|
||||||
$rootScope.$flashMessage = { message: 'The wallet your are trying to join uses a different Bitcoin Network. Check your settings.', type: 'error'};
|
$rootScope.$flashMessage = {
|
||||||
else if (err === 'badSecret')
|
message: 'The wallet your are trying to join uses a different Bitcoin Network. Check your settings.',
|
||||||
$rootScope.$flashMessage = { message: 'Bad secret secret string', type: 'error'};
|
type: 'error'
|
||||||
else
|
};
|
||||||
$rootScope.$flashMessage = { message: 'Unknown error', type: 'error'};
|
else if (err === 'badSecret')
|
||||||
|
$rootScope.$flashMessage = {
|
||||||
|
message: 'Bad secret secret string',
|
||||||
|
type: 'error'
|
||||||
|
};
|
||||||
|
else
|
||||||
|
$rootScope.$flashMessage = {
|
||||||
|
message: 'Unknown error',
|
||||||
|
type: 'error'
|
||||||
|
};
|
||||||
controllerUtils.onErrorDigest();
|
controllerUtils.onErrorDigest();
|
||||||
} else {
|
} else {
|
||||||
controllerUtils.startNetwork(w, $scope);
|
backupService.download(w);
|
||||||
|
controllerUtils.startNetwork(w, $scope);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
47
js/services/backupService.js
Normal file
47
js/services/backupService.js
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var BackupService = function() {};
|
||||||
|
|
||||||
|
BackupService.prototype.getName = function(wallet) {
|
||||||
|
return (wallet.name ? (wallet.name+'-') : '') + wallet.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
BackupService.prototype.download = function(wallet) {
|
||||||
|
var ew = wallet.toEncryptedObj();
|
||||||
|
var timestamp = +(new Date());
|
||||||
|
var walletName = this.getName(wallet);
|
||||||
|
var filename = walletName + '-' + timestamp + '-keybackup.json.aes';
|
||||||
|
var blob = new Blob([ew], {
|
||||||
|
type: 'text/plain;charset=utf-8'
|
||||||
|
});
|
||||||
|
// show a native save dialog if we are in the shell
|
||||||
|
// and pass the wallet to the shell to convert to node Buffer
|
||||||
|
if (window.cshell) {
|
||||||
|
return window.cshell.send('backup:download', {
|
||||||
|
name: walletName,
|
||||||
|
wallet: ew
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// otherwise lean on the browser implementation
|
||||||
|
saveAs(blob, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
BackupService.prototype.sendEmail = function(email, wallet) {
|
||||||
|
var ew = wallet.toEncryptedObj();
|
||||||
|
var body = ew;
|
||||||
|
var subject = this.getName(wallet);
|
||||||
|
var href = 'mailto:' + email + '?' + 'subject=[Copay Backup] ' + subject + '&' + 'body=' + body;
|
||||||
|
|
||||||
|
if (window.cshell) {
|
||||||
|
return window.cshell.send('backup:email', href);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newWin = window.open(href, '_blank', 'scrollbars=yes,resizable=yes,width=10,height=10');
|
||||||
|
if (newWin) {
|
||||||
|
setTimeout(function() {
|
||||||
|
newWin.close();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
angular.module('copayApp.services').value('backupService', new BackupService());
|
||||||
|
|
@ -3,6 +3,7 @@ var FakeWallet = function(){
|
||||||
this.balance=10000;
|
this.balance=10000;
|
||||||
this.safeBalance=1000;
|
this.safeBalance=1000;
|
||||||
this.balanceByAddr={'1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC': 1000};
|
this.balanceByAddr={'1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC': 1000};
|
||||||
|
this.name = 'myTESTwullet';
|
||||||
};
|
};
|
||||||
|
|
||||||
FakeWallet.prototype.set = function(balance, safeBalance, balanceByAddr){
|
FakeWallet.prototype.set = function(balance, safeBalance, balanceByAddr){
|
||||||
|
|
@ -28,6 +29,11 @@ FakeWallet.prototype.getBalance=function(cb){
|
||||||
return cb(null, this.balance, this.balanceByAddr, this.safeBalance);
|
return cb(null, this.balance, this.balanceByAddr, this.safeBalance);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
FakeWallet.prototype.toEncryptedObj = function() {
|
||||||
|
return 'SUPERENCRYPTEDSICRITSTUFF';
|
||||||
|
};
|
||||||
|
|
||||||
// This mock is meant for karma, module.exports is not necesary.
|
// This mock is meant for karma, module.exports is not necesary.
|
||||||
try {
|
try {
|
||||||
module.exports = require('soop')(FakeWallet);
|
module.exports = require('soop')(FakeWallet);
|
||||||
|
|
|
||||||
|
|
@ -87,3 +87,24 @@ describe("Unit: controllerUtils", function() {
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("Unit: Backup Service", function() {
|
||||||
|
var sinon = require('../sinon');
|
||||||
|
beforeEach(angular.mock.module('copayApp.services'));
|
||||||
|
it('should contain a backup service', inject(function(backupService) {
|
||||||
|
expect(backupService).not.to.equal(null);
|
||||||
|
}));
|
||||||
|
it('should backup in file', inject(function(backupService) {
|
||||||
|
var mock = sinon.mock(window);
|
||||||
|
var expectation = mock.expects('saveAs');
|
||||||
|
backupService.download(new FakeWallet());
|
||||||
|
expectation.once();
|
||||||
|
}));
|
||||||
|
it('should backup by email', inject(function(backupService) {
|
||||||
|
var mock = sinon.mock(window);
|
||||||
|
var expectation = mock.expects('open');
|
||||||
|
backupService.sendEmail('fake@test.com', new FakeWallet());
|
||||||
|
expectation.once();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,11 @@ var createBundle = function(opts) {
|
||||||
expose: '../js/models/core/Passphrase'
|
expose: '../js/models/core/Passphrase'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (opts.dontminify) {
|
||||||
|
b.require('sinon', {
|
||||||
|
expose: '../sinon'
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!opts.dontminify) {
|
if (!opts.dontminify) {
|
||||||
b.transform({
|
b.transform({
|
||||||
global: true
|
global: true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue