diff --git a/css/main.css b/css/main.css
index 150401828..62045d634 100644
--- a/css/main.css
+++ b/css/main.css
@@ -1178,7 +1178,7 @@ a.text-warning:hover {color: #FD7262;}
.wide-page {
background-color: #2C3E50;
- margin: 10% 0;
+ margin: 5% 0;
padding: 50px;
}
diff --git a/index.html b/index.html
index 1df6aa092..cb2e4c7b1 100644
--- a/index.html
+++ b/index.html
@@ -17,7 +17,8 @@
-
-
+
@@ -108,6 +112,7 @@
+
diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js
index f90b13f01..f0e907cf9 100644
--- a/js/controllers/sidebar.js
+++ b/js/controllers/sidebar.js
@@ -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');
+ });
+ }
+});
diff --git a/js/controllers/warning.js b/js/controllers/warning.js
new file mode 100644
index 000000000..75f19ff56
--- /dev/null
+++ b/js/controllers/warning.js
@@ -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);
+ }
+ };
+});
diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js
index 55649c296..015b46ad9 100644
--- a/js/models/core/Wallet.js
+++ b/js/models/core/Wallet.js
@@ -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();
};
diff --git a/js/models/storage/LocalEncrypted.js b/js/models/storage/LocalEncrypted.js
index df9c9c703..0ea61f217 100644
--- a/js/models/storage/LocalEncrypted.js
+++ b/js/models/storage/LocalEncrypted.js
@@ -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) {
diff --git a/js/routes.js b/js/routes.js
index 712bbda17..f0d95f93f 100644
--- a/js/routes.js
+++ b/js/routes.js
@@ -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):/);
+});
diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js
index f70cdb87c..646923bff 100644
--- a/js/services/controllerUtils.js
+++ b/js/services/controllerUtils.js
@@ -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;
+});
diff --git a/test/mocks/FakeStorage.js b/test/mocks/FakeStorage.js
index c7d86f3c1..3a4b426cc 100644
--- a/test/mocks/FakeStorage.js
+++ b/test/mocks/FakeStorage.js
@@ -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];
};
diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js
index bdca60a5c..48b4af4a6 100644
--- a/test/mocks/FakeWallet.js
+++ b/test/mocks/FakeWallet.js
@@ -4,6 +4,7 @@ var FakeWallet = function() {
this.safeBalance = 1000;
this.totalCopayers = 2;
this.requiredCopayers = 2;
+ this.isLocked = false;
this.balanceByAddr = {
'1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC': 1000
};
diff --git a/test/test.Wallet.js b/test/test.Wallet.js
index fad38aff6..4139da669 100644
--- a/test/test.Wallet.js
+++ b/test/test.Wallet.js
@@ -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() {
diff --git a/test/test.storage.LocalEncrypted.js b/test/test.storage.LocalEncrypted.js
index 2212ab41f..8ad081f06 100644
--- a/test/test.storage.LocalEncrypted.js
+++ b/test/test.storage.LocalEncrypted.js
@@ -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({
diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js
index 0cbb52c8e..818eac500 100644
--- a/test/unit/controllers/controllersSpec.js
+++ b/test/unit/controllers/controllersSpec.js
@@ -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);
+ });
+ });
+
});
diff --git a/util/build.js b/util/build.js
index 4447e1dd1..11a7e17ff 100755
--- a/util/build.js
+++ b/util/build.js
@@ -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);
diff --git a/views/warning.html b/views/warning.html
new file mode 100644
index 000000000..7597b1c14
--- /dev/null
+++ b/views/warning.html
@@ -0,0 +1,26 @@
+
+
+

+
+
+
Warning!
+
+ This wallet appears to be currently open.
+
+ Opening the wallet in multiple browser tabs could lead to unexpected results
+
+
+
+
+
+