Merge pull request #1076 from matiu/bug/02-open-wallet

Bug/02 open wallet
This commit is contained in:
Yemel Jardi 2014-08-13 12:33:46 -03:00
commit 815c98f7e8
15 changed files with 682 additions and 478 deletions

View file

@ -1178,7 +1178,7 @@ a.text-warning:hover {color: #FD7262;}
.wide-page {
background-color: #2C3E50;
margin: 10% 0;
margin: 5% 0;
padding: 50px;
}

View file

@ -17,7 +17,8 @@
<div class="off-canvas-wrap">
<div class="inner-wrap">
<nav class="tab-bar" ng-class="{'hide-tab-bar' : !$root.wallet || !$root.wallet.isReady()}">
<nav class="tab-bar" ng-class="{'hide-tab-bar' : !$root.wallet ||
!$root.wallet.isReady() || $root.wallet.isLocked}">
<section class="left-small">
<a class="left-off-canvas-toggle menu-icon" ><span></span></a>
</section>
@ -39,12 +40,15 @@
<div notifications="right top"></div>
<div
ng-class="{'sidebar' : $root.wallet && $root.wallet.isReady()}"
ng-class="{'sidebar' : $root.wallet && $root.wallet.isReady() &&
!$root.wallet.isLocked}"
ng-include="'views/includes/sidebar.html'"
role='navigation'
ng-if="$root.wallet && $root.wallet.isReady()"></div>
ng-if="$root.wallet && $root.wallet.isReady() &&
!$root.wallet.isLocked"></div>
<section ng-class="{'main' : $root.wallet && $root.wallet.isReady()}" ng-view></section>
<section ng-class="{'main' : $root.wallet && $root.wallet.isReady() &&
!$root.wallet.isLocked}" ng-view></section>
<a class="exit-off-canvas"></a>
@ -108,6 +112,7 @@
<script src="js/controllers/settings.js"></script>
<script src="js/controllers/uriPayment.js"></script>
<script src="js/controllers/version.js"></script>
<script src="js/controllers/warning.js"></script>
<!-- PLACEHOLDER: CORDOVA SRIPT -->
<script src="js/mobile.js"></script>

View file

@ -1,77 +1,77 @@
'use strict';
angular.module('copayApp.controllers').controller('SidebarController',
function($scope, $rootScope, $sce, $location, $http, notification, controllerUtils) {
angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $sce, $location, $http, notification, controllerUtils) {
$scope.menu = [{
'title': 'Receive',
'icon': 'fi-arrow-left',
'link': 'receive'
}, {
'title': 'Send',
'icon': 'fi-arrow-right',
'link': 'send'
}, {
'title': 'History',
'icon': 'fi-clipboard-pencil',
'link': 'history'
}, {
'title': 'More',
'icon': 'fi-download',
'link': 'backup'
}];
$scope.menu = [{
'title': 'Receive',
'icon': 'fi-arrow-left',
'link': 'receive'
}, {
'title': 'Send',
'icon': 'fi-arrow-right',
'link': 'send'
}, {
'title': 'History',
'icon': 'fi-clipboard-pencil',
'link': 'history'
}, {
'title': 'More',
'icon': 'fi-download',
'link': 'backup'
}];
$scope.signout = function() {
logout();
};
$scope.signout = function() {
logout();
};
// Ensures a graceful disconnect
window.onbeforeunload = logout;
// Ensures a graceful disconnect
window.onbeforeunload = function() {
controllerUtils.logout();
};
$scope.$on('$destroy', function() {
window.onbeforeunload = undefined;
$scope.$on('$destroy', function() {
window.onbeforeunload = undefined;
});
$scope.refresh = function() {
var w = $rootScope.wallet;
w.connectToAll();
if ($rootScope.addrInfos.length > 0) {
controllerUtils.updateBalance(function() {
$rootScope.$digest();
});
}
};
$scope.isActive = function(item) {
return item.link && item.link == $location.path().split('/')[1];
};
function logout() {
var w = $rootScope.wallet;
if (w) {
w.disconnect();
controllerUtils.logout();
}
}
// ng-repeat defined number of times instead of repeating over array?
$scope.getNumber = function(num) {
return new Array(num);
}
// Init socket handlers (with no wallet yet)
controllerUtils.setSocketHandlers();
if ($rootScope.wallet) {
$scope.$on('$idleStart', function(a) {
notification.warning('Session will be closed', 'Your session is about to expire due to inactivity');
});
$scope.refresh = function() {
var w = $rootScope.wallet;
w.connectToAll();
if ($rootScope.addrInfos.length > 0) {
controllerUtils.updateBalance(function() {
$rootScope.$digest();
});
}
};
$scope.isActive = function(item) {
return item.link && item.link == $location.path().split('/')[1];
};
function logout() {
var w = $rootScope.wallet;
if (w) {
w.disconnect();
controllerUtils.logout();
}
}
// ng-repeat defined number of times instead of repeating over array?
$scope.getNumber = function(num) {
return new Array(num);
}
// Init socket handlers (with no wallet yet)
controllerUtils.setSocketHandlers();
if ($rootScope.wallet) {
$scope.$on('$idleStart', function(a) {
notification.warning('Session will be closed', 'Your session is about to expire due to inactivity');
});
$scope.$on('$idleTimeout', function() {
$scope.signout();
notification.warning('Session closed', 'Session closed because a long time of inactivity');
});
}
});
$scope.$on('$idleTimeout', function() {
$scope.signout();
notification.warning('Session closed', 'Session closed because a long time of inactivity');
});
}
});

28
js/controllers/warning.js Normal file
View file

@ -0,0 +1,28 @@
'use strict';
angular.module('copayApp.controllers').controller('WarningController', function($scope, $rootScope, $location, controllerUtils) {
$scope.checkLock = function() {
if (!$rootScope.tmp || !$rootScope.tmp.getLock()) {
controllerUtils.redirIfLogged();
}
};
$scope.signout = function() {
controllerUtils.logout();
};
$scope.ignoreLock = function() {
var w = $rootScope.tmp;
delete $rootScope['tmp'];
if (!w) {
$location.path('/');
} else {
w.ignoreLock = 1;
$scope.loading = true;
controllerUtils.startNetwork(w, $scope);
}
};
});

View file

@ -44,6 +44,7 @@ function Wallet(opts) {
this.id = opts.id || Wallet.getRandomId();
this.name = opts.name;
this.ignoreLock = opts.ignoreLock;
this.verbose = opts.verbose;
this.publicKeyRing.walletId = this.id;
this.txProposals.walletId = this.id;
@ -92,6 +93,27 @@ Wallet.prototype.connectToAll = function() {
}
};
Wallet.prototype.getLock = function() {
return this.storage.getLock(this.id);
};
Wallet.prototype.setLock = function() {
return this.storage.setLock(this.id);
};
Wallet.prototype.unlock = function() {
this.storage.removeLock(this.id);
};
Wallet.prototype.checkAndLock = function() {
if (this.getLock()) {
return true;
}
this.setLock();
return false;
};
Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
this.log('RECV INDEXES:', data);
var inIndexes = HDParams.fromList(data.indexes);
@ -112,7 +134,7 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
try {
hasChanged = this.publicKeyRing.merge(inPKR, true);
} catch (e) {
this.log('## WALLET ERROR', e); //TODO
this.log('## WALLET ERROR', e);
this.emit('connectionError', e.message);
return;
}
@ -306,9 +328,10 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
if (data.type !== 'walletId' && this.id !== data.walletId) {
this.emit('badMessage', senderId);
this.log('badMessage FROM:', senderId); //TODO
this.log('badMessage FROM:', senderId);
return;
}
switch (data.type) {
// This handler is repeaded on WalletFactory (#join). TODO
case 'walletId':
@ -409,6 +432,12 @@ Wallet.prototype._lockIncomming = function() {
Wallet.prototype.netStart = function(callback) {
var self = this;
var net = this.network;
if (this.checkAndLock() && !this.ignoreLock) {
this.emit('locked');
return;
}
net.removeAllListeners();
net.on('connect', self._handleConnect.bind(self));
net.on('disconnect', self._handleDisconnect.bind(self));
@ -722,7 +751,6 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
} else {
self.log('Sent failed. Checking is the TX was sent already');
self._checkSentTx(ntxid, function(txid) {
console.log('[Wallet.js.730:txid:]', txid); //TODO
if (txid)
self.store();
@ -993,6 +1021,7 @@ Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) {
Wallet.prototype.disconnect = function() {
this.log('## DISCONNECTING');
this.unlock();
this.network.disconnect();
};

View file

@ -14,8 +14,8 @@ function Storage(opts) {
if (opts.localStorage) {
this.localStorage = opts.localStorage;
} else if (localStorage) {
this.localStorage = localStorage;
}
this.localStorage = localStorage;
}
}
var pps = {};
@ -180,6 +180,18 @@ Storage.prototype.getLastOpened = function() {
return this.getGlobal('lastOpened');
}
Storage.prototype.setLock = function(walletId) {
this.setGlobal(this._key(walletId, 'Lock'), true);
}
Storage.prototype.getLock = function(walletId) {
return this.getGlobal(this._key(walletId, 'Lock'));
}
Storage.prototype.removeLock = function(walletId) {
this.removeGlobal(this._key(walletId, 'Lock'));
}
//obj contains keys to be set
Storage.prototype.setFromObj = function(walletId, obj) {
for (var k in obj) {

View file

@ -2,93 +2,113 @@
//Setting up route
angular
.module('copayApp')
.config(function($routeProvider) {
.module('copayApp')
.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
validate: false
})
.when('/open', {
templateUrl: 'views/open.html',
validate: false
})
.when('/join', {
templateUrl: 'views/join.html',
validate: false
})
.when('/import', {
templateUrl: 'views/import.html',
validate: false
})
.when('/setup', {
templateUrl: 'views/setup.html',
validate: false
})
.when('/copayers', {
templateUrl: 'views/copayers.html',
validate: true
})
.when('/receive', {
templateUrl: 'views/addresses.html',
validate: true
})
.when('/history', {
templateUrl: 'views/transactions.html',
validate: true
})
.when('/send', {
templateUrl: 'views/send.html',
validate: true
})
.when('/backup', {
templateUrl: 'views/backup.html',
validate: true
})
.when('/settings', {
templateUrl: 'views/settings.html',
validate: false
})
.when('/unsupported', {
templateUrl: 'views/unsupported.html'
})
.when('/uri-payment/:data', {
templateUrl: 'views/uri-payment.html'
})
.otherwise({
templateUrl: 'views/errors/404.html',
title: 'Error'
});
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
validate: false
})
.when('/open', {
templateUrl: 'views/open.html',
validate: false
})
.when('/join', {
templateUrl: 'views/join.html',
validate: false
})
.when('/import', {
templateUrl: 'views/import.html',
validate: false
})
.when('/setup', {
templateUrl: 'views/setup.html',
validate: false
})
.when('/copayers', {
templateUrl: 'views/copayers.html',
validate: true
})
.when('/receive', {
templateUrl: 'views/addresses.html',
validate: true
})
.when('/history', {
templateUrl: 'views/transactions.html',
validate: true
})
.when('/send', {
templateUrl: 'views/send.html',
validate: true
})
.when('/backup', {
templateUrl: 'views/backup.html',
validate: true
})
.when('/settings', {
templateUrl: 'views/settings.html',
validate: false
})
.when('/unsupported', {
templateUrl: 'views/unsupported.html'
})
.when('/uri-payment/:data', {
templateUrl: 'views/uri-payment.html'
})
.when('/warning', {
templateUrl: 'views/warning.html',
validate: true
})
.otherwise({
templateUrl: 'views/errors/404.html',
title: 'Error'
});
});
//Setting HTML5 Location Mode
angular
.module('copayApp')
.config(function($locationProvider, $idleProvider) {
$locationProvider
.html5Mode(false)
.hashPrefix('!');
// IDLE timeout
$idleProvider.idleDuration(15 * 60); // in seconds
$idleProvider.warningDuration(10); // in seconds
})
.run(function($rootScope, $location, $idle) {
$idle.watch();
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (!util.supports.data) {
$location.path('unsupported');
} else {
if ((!$rootScope.wallet || !$rootScope.wallet.id) && next.validate) {
$idle.unwatch();
$location.path('/');
}
if ($rootScope.wallet && !$rootScope.wallet.isReady()) {
$location.path('/copayers');
.module('copayApp')
.config(function($locationProvider, $idleProvider) {
$locationProvider
.html5Mode(false)
.hashPrefix('!');
// IDLE timeout
$idleProvider.idleDuration(15 * 60); // in seconds
$idleProvider.warningDuration(10); // in seconds
})
.run(function($rootScope, $location, $idle) {
$idle.watch();
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (!util.supports.data) {
$location.path('unsupported');
} else {
// Locked?
if ($rootScope.showLockWarning) {
if ($rootScope.tmp) {
if ($location.path() !== '/warning') {
$location.path('/warning');
}
else {
delete $rootScope['showLockWarning'];
}
}
return;
}
});
})
.config(function($compileProvider) {
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|tel|chrome-extension|resource):/);
if ((!$rootScope.wallet || !$rootScope.wallet.id) && next.validate) {
$idle.unwatch();
$location.path('/');
}
// In creation?
if ($rootScope.wallet && !$rootScope.wallet.isReady()) {
$location.path('/copayers');
}
}
});
})
.config(function($compileProvider) {
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|tel|chrome-extension|resource):/);
});

View file

@ -2,371 +2,381 @@
var bitcore = require('bitcore');
angular.module('copayApp.services')
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) {
var root = {};
root.getVideoMutedStatus = function(copayer) {
if (!$rootScope.videoInfo) return;
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) {
var root = {};
root.getVideoMutedStatus = function(copayer) {
if (!$rootScope.videoInfo) return;
var vi = $rootScope.videoInfo[copayer]
if (!vi) {
return;
var vi = $rootScope.videoInfo[copayer]
if (!vi) {
return;
}
return vi.muted;
};
root.redirIfLogged = function() {
if ($rootScope.wallet) {
$rootScope.wallet.path('receive');
}
};
root.logout = function() {
if ($rootScope.wallet)
$rootScope.wallet.disconnect();
Socket.removeAllListeners();
$rootScope.wallet = $rootScope.tmp = null;
delete $rootScope['wallet'];
video.close();
// Clear rootScope
for (var i in $rootScope) {
if (i.charAt(0) != '$') {
delete $rootScope[i];
}
return vi.muted;
};
root.redirIfLogged = function() {
var w = $rootScope.wallet;
if (w) {
$location.path('addresses');
}
};
root.logout = function() {
Socket.removeAllListeners();
$rootScope.wallet = null;
delete $rootScope['wallet'];
video.close();
// Clear rootScope
for (var i in $rootScope) {
if (i.charAt(0) != '$') {
delete $rootScope[i];
}
}
$location.path('/');
};
root.onError = function(scope) {
if (scope) scope.loading = false;
root.logout();
}
root.onErrorDigest = function(scope, msg) {
root.onError(scope);
if (msg) {
notification.error('Error', msg);
}
$rootScope.$digest();
};
$location.path('/');
};
root.installStartupHandlers = function(wallet, $scope) {
wallet.on('connectionError', function() {
var message = "Looks like you are already connected to this wallet, please logout and try importing it again.";
notification.error('PeerJS Error', message);
root.onErrorDigest($scope);
});
wallet.on('serverError', function(m) {
var message = m || 'The PeerJS server is not responding, please try again';
$location.path('receive');
root.onErrorDigest($scope, message);
});
wallet.on('ready', function() {
$scope.loading = false;
});
};
root.onError = function(scope) {
if (scope) scope.loading = false;
root.logout();
}
root.setupRootVariables = function() {
uriHandler.register();
$rootScope.unitName = config.unitName;
$rootScope.txAlertCount = 0;
$rootScope.insightError = 0;
$rootScope.isCollapsed = true;
$rootScope.$watch('txAlertCount', function(txAlertCount) {
if (txAlertCount && txAlertCount > 0) {
notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount);
}
});
$rootScope.$watch('receivedFund', function(receivedFund) {
if (receivedFund) {
var currentAddr;
for (var i = 0; i < $rootScope.addrInfos.length; i++) {
var addrinfo = $rootScope.addrInfos[i];
if (addrinfo.address.toString() == receivedFund[1] && !addrinfo.isChange) {
currentAddr = addrinfo.address.toString();
break;
}
}
if (currentAddr) {
//var beep = new Audio('sound/transaction.mp3');
notification.funds('Received fund', currentAddr, receivedFund);
//beep.play();
}
}
});
};
root.startNetwork = function(w, $scope) {
Socket.removeAllListeners();
root.setupRootVariables();
root.installStartupHandlers(w, $scope);
root.setSocketHandlers();
var handlePeerVideo = function(err, peerID, url) {
if (err) {
delete $rootScope.videoInfo[peerID];
return;
}
$rootScope.videoInfo[peerID] = {
url: encodeURI(url),
muted: peerID === w.network.peerId
};
$rootScope.$digest();
};
notification.enableHtml5Mode(); // for chrome: if support, enable it
w.on('badMessage', function(peerId) {
notification.error('Error', 'Received wrong message from peer ' + peerId);
});
w.on('ready', function(myPeerID) {
$rootScope.wallet = w;
if ($rootScope.pendingPayment) {
$location.path('send');
} else {
$location.path('receive');
}
if (!config.disableVideo)
video.setOwnPeer(myPeerID, w, handlePeerVideo);
});
w.on('publicKeyRingUpdated', function(dontDigest) {
root.setSocketHandlers();
if (!dontDigest) {
$rootScope.$digest();
}
});
w.on('txProposalsUpdated', function(dontDigest) {
root.updateTxs();
// give sometime to the tx to propagate.
$timeout(function() {
root.updateBalance(function() {
if (!dontDigest) {
$rootScope.$digest();
}
});
}, 3000);
});
w.on('txProposalEvent', function(e) {
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
switch (e.type) {
case 'signed':
notification.info('Transaction Update', 'A transaction was signed by ' + user);
break;
case 'rejected':
notification.info('Transaction Update', 'A transaction was rejected by ' + user);
break;
case 'corrupt':
notification.error('Transaction Error', 'Received corrupt transaction from '+user);
break;
}
});
w.on('addressBookUpdated', function(dontDigest) {
if (!dontDigest) {
$rootScope.$digest();
}
});
w.on('connectionError', function(msg) {
root.onErrorDigest(null, msg);
});
w.on('connect', function(peerID) {
if (peerID && !config.disableVideo) {
video.callPeer(peerID, handlePeerVideo);
}
$rootScope.$digest();
});
w.on('disconnect', function(peerID) {
$rootScope.$digest();
});
w.on('close', root.onErrorDigest);
w.netStart();
};
root.updateAddressList = function() {
var w = $rootScope.wallet;
if (w && w.isReady())
$rootScope.addrInfos = w.getAddressesInfo();
};
root.updateBalance = function(cb) {
var w = $rootScope.wallet;
if (!w) return root.onErrorDigest();
if (!w.isReady()) return;
$rootScope.balanceByAddr = {};
$rootScope.updatingBalance = true;
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) {
if (err) {
console.error('Error: ' + err.message); //TODO
root._setCommError();
return null;
} else {
root._clearCommError();
}
var satToUnit = 1 / config.unitToSatoshi;
var COIN = bitcore.util.COIN;
$rootScope.totalBalance = balanceSat * satToUnit;
$rootScope.totalBalanceBTC = (balanceSat / COIN);
$rootScope.availableBalance = safeBalanceSat * satToUnit;
$rootScope.availableBalanceBTC = (safeBalanceSat / COIN);
$rootScope.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
$rootScope.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
var balanceByAddr = {};
for (var ii in balanceByAddrSat) {
balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit;
}
$rootScope.balanceByAddr = balanceByAddr;
root.updateAddressList();
$rootScope.updatingBalance = false;
return cb ? cb() : null;
});
};
root.updateTxs = function(opts) {
var w = $rootScope.wallet;
if (!w) return;
opts = opts || $rootScope.txsOpts || {};
var satToUnit = 1 / config.unitToSatoshi;
var myCopayerId = w.getMyCopayerId();
var pendingForUs = 0;
var inT = w.getTxProposals().sort(function(t1, t2) {
return t2.createdTs - t1.createdTs
});
var txs = [];
inT.forEach(function(i, index) {
if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) {
return txs.push(null);
}
if (myCopayerId != i.creator && !i.finallyRejected && !i.sentTs && !i.rejectedByUs && !i.signedByUs) {
pendingForUs++;
}
if (!i.finallyRejected && !i.sentTs) {
i.isPending = 1;
}
if (!!opts.pending == !!i.isPending) {
var tx = i.builder.build();
var outs = [];
tx.outs.forEach(function(o) {
var addr = bitcore.Address.fromScriptPubKey(o.getScript(), config.networkName)[0].toString();
if (!w.addressIsOwn(addr, {
excludeMain: true
})) {
outs.push({
address: addr,
value: bitcore.util.valueToBigInt(o.getValue()) * satToUnit,
});
}
});
// extra fields
i.outs = outs;
i.fee = i.builder.feeSat * satToUnit;
i.missingSignatures = tx.countInputMissingSignatures(0);
i.actionList = getActionList(i.peerActions);
txs.push(i);
}
});
$rootScope.txs = txs;
$rootScope.txsOpts = opts;
if ($rootScope.pendingTxCount < pendingForUs) {
$rootScope.txAlertCount = pendingForUs;
}
$rootScope.pendingTxCount = pendingForUs;
};
function getActionList(actions) {
var peers = Object.keys(actions).map(function(i) {
return {cId: i, actions: actions[i] }
});
return peers.sort(function(a, b) {
return !!b.actions.create - !!a.actions.create;
});
root.onErrorDigest = function(scope, msg) {
root.onError(scope);
if (msg) {
notification.error('Error', msg);
}
$rootScope.$digest();
};
$rootScope.$watch('insightError', function(status) {
if (status) {
if (status === -1) {
notification.success('Networking restored', 'Connection to Insight re-established');
} else if (!isNaN(status)) {
notification.error('Networking problem', 'Connection to Insight lost, reconnecting (attempt number ' + status + ')');
root.installStartupHandlers = function(wallet, $scope) {
wallet.on('connectionError', function() {
var message = "Looks like you are already connected to this wallet, please logout and try importing it again.";
notification.error('PeerJS Error', message);
root.onErrorDigest($scope);
});
wallet.on('serverError', function(m) {
var message = m || 'The PeerJS server is not responding, please try again';
$location.path('receive');
root.onErrorDigest($scope, message);
});
wallet.on('ready', function() {
$scope.loading = false;
});
};
root.setupRootVariables = function() {
uriHandler.register();
$rootScope.unitName = config.unitName;
$rootScope.showLockWarning = false;
$rootScope.txAlertCount = 0;
$rootScope.insightError = 0;
$rootScope.isCollapsed = true;
$rootScope.$watch('txAlertCount', function(txAlertCount) {
if (txAlertCount && txAlertCount > 0) {
notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount);
}
});
$rootScope.$watch('receivedFund', function(receivedFund) {
if (receivedFund) {
var currentAddr;
for (var i = 0; i < $rootScope.addrInfos.length; i++) {
var addrinfo = $rootScope.addrInfos[i];
if (addrinfo.address.toString() == receivedFund[1] && !addrinfo.isChange) {
currentAddr = addrinfo.address.toString();
break;
}
}
if (currentAddr) {
//var beep = new Audio('sound/transaction.mp3');
notification.funds('Received fund', currentAddr, receivedFund);
//beep.play();
}
}
});
root._setCommError = function(e) {
if ($rootScope.insightError < 0)
$rootScope.insightError = 0;
$rootScope.insightError++;
};
root.startNetwork = function(w, $scope) {
Socket.removeAllListeners();
root.setupRootVariables();
root.installStartupHandlers(w, $scope);
root.setSocketHandlers();
var handlePeerVideo = function(err, peerID, url) {
if (err) {
delete $rootScope.videoInfo[peerID];
return;
}
$rootScope.videoInfo[peerID] = {
url: encodeURI(url),
muted: peerID === w.network.peerId
};
$rootScope.$digest();
};
notification.enableHtml5Mode(); // for chrome: if support, enable it
w.on('locked', function() {
$rootScope.tmp = w;
$rootScope.showLockWarning=true;
$location.path('/warning');
$rootScope.$digest();
});
root._clearCommError = function(e) {
if ($rootScope.insightError > 0)
$rootScope.insightError = -1;
else
$rootScope.insightError = 0;
};
root.setSocketHandlers = function() {
root.updateAddressList();
if (!Socket.sysEventsSet) {
Socket.sysOn('error', root._setCommError);
Socket.sysOn('reconnect_error', root._setCommError);
Socket.sysOn('reconnect_failed', root._setCommError);
Socket.sysOn('connect', root._clearCommError);
Socket.sysOn('reconnect', root._clearCommError);
Socket.sysEventsSet = true;
w.on('badMessage', function(peerId) {
notification.error('Error', 'Received wrong message from peer ' + peerId);
});
w.on('ready', function(myPeerID) {
$rootScope.wallet = w;
if ($rootScope.pendingPayment) {
$location.path('send');
} else {
$location.path('receive');
}
if (!$rootScope.wallet) return;
if (!config.disableVideo)
video.setOwnPeer(myPeerID, w, handlePeerVideo);
});
var currentAddrs = Socket.getListeners();
var allAddrs = $rootScope.addrInfos;
var newAddrs = [];
for (var i in allAddrs) {
var a = allAddrs[i];
if (!currentAddrs[a.addressStr])
newAddrs.push(a);
w.on('publicKeyRingUpdated', function(dontDigest) {
root.setSocketHandlers();
if (!dontDigest) {
$rootScope.$digest();
}
for (var i = 0; i < newAddrs.length; i++) {
Socket.emit('subscribe', newAddrs[i].addressStr);
}
newAddrs.forEach(function(a) {
Socket.on(a.addressStr, function(txid) {
if (!a.isChange)
notification.funds('Funds received!', a.addressStr);
root.updateBalance(function() {
});
w.on('txProposalsUpdated', function(dontDigest) {
root.updateTxs();
// give sometime to the tx to propagate.
$timeout(function() {
root.updateBalance(function() {
if (!dontDigest) {
$rootScope.$digest();
});
}
});
}, 3000);
});
w.on('txProposalEvent', function(e) {
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
switch (e.type) {
case 'signed':
notification.info('Transaction Update', 'A transaction was signed by ' + user);
break;
case 'rejected':
notification.info('Transaction Update', 'A transaction was rejected by ' + user);
break;
case 'corrupt':
notification.error('Transaction Error', 'Received corrupt transaction from '+user);
break;
}
});
w.on('addressBookUpdated', function(dontDigest) {
if (!dontDigest) {
$rootScope.$digest();
}
});
w.on('connectionError', function(msg) {
root.onErrorDigest(null, msg);
});
w.on('connect', function(peerID) {
if (peerID && !config.disableVideo) {
video.callPeer(peerID, handlePeerVideo);
}
$rootScope.$digest();
});
w.on('disconnect', function(peerID) {
$rootScope.$digest();
});
w.on('close', root.onErrorDigest);
w.netStart();
};
root.updateAddressList = function() {
var w = $rootScope.wallet;
if (w && w.isReady())
$rootScope.addrInfos = w.getAddressesInfo();
};
root.updateBalance = function(cb) {
var w = $rootScope.wallet;
if (!w) return root.onErrorDigest();
if (!w.isReady()) return;
$rootScope.balanceByAddr = {};
$rootScope.updatingBalance = true;
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) {
if (err) {
console.error('Error: ' + err.message); //TODO
root._setCommError();
return null;
} else {
root._clearCommError();
}
var satToUnit = 1 / config.unitToSatoshi;
var COIN = bitcore.util.COIN;
$rootScope.totalBalance = balanceSat * satToUnit;
$rootScope.totalBalanceBTC = (balanceSat / COIN);
$rootScope.availableBalance = safeBalanceSat * satToUnit;
$rootScope.availableBalanceBTC = (safeBalanceSat / COIN);
$rootScope.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
$rootScope.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
var balanceByAddr = {};
for (var ii in balanceByAddrSat) {
balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit;
}
$rootScope.balanceByAddr = balanceByAddr;
root.updateAddressList();
$rootScope.updatingBalance = false;
return cb ? cb() : null;
});
};
root.updateTxs = function(opts) {
var w = $rootScope.wallet;
if (!w) return;
opts = opts || $rootScope.txsOpts || {};
var satToUnit = 1 / config.unitToSatoshi;
var myCopayerId = w.getMyCopayerId();
var pendingForUs = 0;
var inT = w.getTxProposals().sort(function(t1, t2) {
return t2.createdTs - t1.createdTs
});
var txs = [];
inT.forEach(function(i, index) {
if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) {
return txs.push(null);
}
if (myCopayerId != i.creator && !i.finallyRejected && !i.sentTs && !i.rejectedByUs && !i.signedByUs) {
pendingForUs++;
}
if (!i.finallyRejected && !i.sentTs) {
i.isPending = 1;
}
if (!!opts.pending == !!i.isPending) {
var tx = i.builder.build();
var outs = [];
tx.outs.forEach(function(o) {
var addr = bitcore.Address.fromScriptPubKey(o.getScript(), config.networkName)[0].toString();
if (!w.addressIsOwn(addr, {
excludeMain: true
})) {
outs.push({
address: addr,
value: bitcore.util.valueToBigInt(o.getValue()) * satToUnit,
});
}
});
// extra fields
i.outs = outs;
i.fee = i.builder.feeSat * satToUnit;
i.missingSignatures = tx.countInputMissingSignatures(0);
i.actionList = getActionList(i.peerActions);
txs.push(i);
}
});
$rootScope.txs = txs;
$rootScope.txsOpts = opts;
if ($rootScope.pendingTxCount < pendingForUs) {
$rootScope.txAlertCount = pendingForUs;
}
$rootScope.pendingTxCount = pendingForUs;
};
function getActionList(actions) {
var peers = Object.keys(actions).map(function(i) {
return {cId: i, actions: actions[i] }
});
return peers.sort(function(a, b) {
return !!b.actions.create - !!a.actions.create;
});
}
$rootScope.$watch('insightError', function(status) {
if (status) {
if (status === -1) {
notification.success('Networking restored', 'Connection to Insight re-established');
} else if (!isNaN(status)) {
notification.error('Networking problem', 'Connection to Insight lost, reconnecting (attempt number ' + status + ')');
}
}
});
root._setCommError = function(e) {
if ($rootScope.insightError < 0)
$rootScope.insightError = 0;
$rootScope.insightError++;
};
root._clearCommError = function(e) {
if ($rootScope.insightError > 0)
$rootScope.insightError = -1;
else
$rootScope.insightError = 0;
};
root.setSocketHandlers = function() {
root.updateAddressList();
if (!Socket.sysEventsSet) {
Socket.sysOn('error', root._setCommError);
Socket.sysOn('reconnect_error', root._setCommError);
Socket.sysOn('reconnect_failed', root._setCommError);
Socket.sysOn('connect', root._clearCommError);
Socket.sysOn('reconnect', root._clearCommError);
Socket.sysEventsSet = true;
}
if (!$rootScope.wallet) return;
var currentAddrs = Socket.getListeners();
var allAddrs = $rootScope.addrInfos;
var newAddrs = [];
for (var i in allAddrs) {
var a = allAddrs[i];
if (!currentAddrs[a.addressStr])
newAddrs.push(a);
}
for (var i = 0; i < newAddrs.length; i++) {
Socket.emit('subscribe', newAddrs[i].addressStr);
}
newAddrs.forEach(function(a) {
Socket.on(a.addressStr, function(txid) {
if (!a.isChange)
notification.funds('Funds received!', a.addressStr);
root.updateBalance(function() {
$rootScope.$digest();
});
});
});
if (!$rootScope.wallet.spendUnconfirmed && !Socket.isListeningBlocks()) {
Socket.emit('subscribe', 'inv');
Socket.on('block', function(block) {
root.updateBalance(function() {
$rootScope.$digest();
});
if (!$rootScope.wallet.spendUnconfirmed && !Socket.isListeningBlocks()) {
Socket.emit('subscribe', 'inv');
Socket.on('block', function(block) {
root.updateBalance(function() {
$rootScope.$digest();
});
}
};
return root;
});
});
}
};
return root;
});

View file

@ -27,6 +27,18 @@ FakeStorage.prototype.getLastOpened = function() {
return this.storage['lastOpened'];
};
FakeStorage.prototype.setLock = function(id) {
this.storage[id + '::lock'] = true;
}
FakeStorage.prototype.getLock = function(id) {
return this.storage[id + '::lock'];
}
FakeStorage.prototype.removeLock = function(id) {
delete this.storage[id + '::lock'];
}
FakeStorage.prototype.removeGlobal = function(id) {
delete this.storage[id];
};

View file

@ -4,6 +4,7 @@ var FakeWallet = function() {
this.safeBalance = 1000;
this.totalCopayers = 2;
this.requiredCopayers = 2;
this.isLocked = false;
this.balanceByAddr = {
'1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC': 1000
};

View file

@ -181,6 +181,7 @@ describe('Wallet model', function() {
cachedW2obj.opts.reconnectDelay = 100;
}
var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain);
w.unlock();
return w;
};
@ -1022,6 +1023,37 @@ describe('Wallet model', function() {
w.netStart();
w.network.start.getCall(0).args[0].privkey.length.should.equal(64);
});
it('should check if wallet is already opened', function() {
var w = cachedCreateW2();
should.not.exist(w.getLock());
w.checkAndLock().should.equal(false);
w.getLock().should.equal(true);
});
it('should check if wallet is already opened', function() {
var w = cachedCreateW2();
should.not.exist(w.getLock());
w.checkAndLock().should.equal(false);
w.getLock().should.equal(true);
});
it('should not start if locked', function() {
var w = cachedCreateW2();
w.netStart();
w.emit = sinon.spy();
w.netStart();
w.emit.getCall(0).args[0].should.equal('locked');
});
it('should accept ignoreLocked', function() {
var w = cachedCreateW2();
w.netStart();
w.network.start = sinon.spy();
w.ignoreLock=1;
w.netStart();
w.network.start.getCall(0).args[0].privkey.length.should.equal(64);
});
});
describe('#forceNetwork in config', function() {

View file

@ -160,6 +160,19 @@ describe('Storage/LocalEncrypted model', function() {
});
});
describe('#WalletLock', function() {
it('should get/set/remove opened', function() {
var s = new LocalEncrypted({
localStorage: localMock,
password: 'password'
});
s.setLock('walletId');
s.getLock('walletId').should.equal(true);
s.removeLock('walletId');
should.not.exist(s.getLock('walletId'));
});
});
describe('#getWallets', function() {
it('should retreive wallets from storage', function() {
var s = new LocalEncrypted({

View file

@ -288,8 +288,10 @@ describe("Unit: Controllers", function() {
describe("Unit: Sidebar Controller", function() {
var rootScope;
beforeEach(inject(function($controller, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new();
rootScope = $rootScope;
rootScope.wallet = new FakeWallet(config);
headerCtrl = $controller('SidebarController', {
$scope: scope,
});
@ -437,4 +439,18 @@ describe("Unit: Controllers", function() {
});
});
describe('Warning Controller', function() {
var what;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
what = $controller('WarningController', {
$scope: scope,
});
}));
it('should exist', function() {
should.exist(what);
});
});
});

View file

@ -97,14 +97,14 @@ var createBundle = function(opts) {
expose: '../js/models/core/HDPath'
});
if (opts.dontminify) {
if (opts.debug) {
//include dev dependencies
b.require('sinon');
b.require('blanket');
b.require('soop');
}
if (!opts.dontminify) {
if (!opts.debug) {
b.transform({
global: true
}, 'uglifyify');
@ -120,7 +120,7 @@ if (require.main === module) {
var program = require('commander');
program
.version('0.0.1')
.option('-d, --dontminify', 'Development. Don\'t minify the code.')
.option('-d, --debug', 'Development. Don\'t minify the codem and include debug packages.')
.option('-o, --stdout', 'Specify output as stdout')
.parse(process.argv);

26
views/warning.html Normal file
View file

@ -0,0 +1,26 @@
<div class="wide-page" ng-controller="WarningController"
ng-init="checkLock()">
<div class="text-center">
<img src="img/logo-negative-beta.svg" alt="Copay">
<div class="text-white" ng-include="'views/includes/version.html'"></div>
</div>
<h1 class="text-center text-warning">Warning!</h1>
<h3 class="text-center text-white">
This wallet appears to be currently open.
<br>
Opening the wallet in multiple browser tabs could lead to unexpected results
</h3>
<div class="text-center m30v large-12 columns">
<div class="row">
<div class="large-12 columns medium-12 small-12 text-center">
<a href class="button sucess" ng-click="signout()">Go back</a>
<br>
<br>
<a href ng-click="ignoreLock()">Continue anyways</a>
</div>
</div>
</div>
<div class="text-center text-gray small cb">
</div>
</div>