diff --git a/index.html b/index.html index 9b11021f0..cb2e4c7b1 100644 --- a/index.html +++ b/index.html @@ -112,6 +112,7 @@ + diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js index f07c1987a..f0e907cf9 100644 --- a/js/controllers/sidebar.js +++ b/js/controllers/sidebar.js @@ -1,90 +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.checkIfWarning = function() { - if ($rootScope.wallet && !$rootScope.wallet.isLocked) { - controllerUtils.redirIfLogged(); - } - }; - - $scope.ignoreLocked = function() { - if ($rootScope.wallet) { - $rootScope.wallet.isLocked = false; - controllerUtils.redirIfLogged(); - } - }; - - }); + $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 b3ef6fb56..97b2e247a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -43,8 +43,8 @@ function Wallet(opts) { this.id = opts.id || Wallet.getRandomId(); this.name = opts.name; - this.isLocked = false; + this.ignoreLock = opts.ignoreLock; this.verbose = opts.verbose; this.publicKeyRing.walletId = this.id; this.txProposals.walletId = this.id; @@ -93,25 +93,25 @@ Wallet.prototype.connectToAll = function() { } }; -Wallet.prototype.getIsOpen = function() { - return this.storage.getIsOpen(this.id); +Wallet.prototype.getLock = function() { + return this.storage.getLock(this.id); }; -Wallet.prototype.setIsOpen = function() { - return this.storage.setIsOpen(this.id); +Wallet.prototype.setLock = function() { + return this.storage.setLock(this.id); }; -Wallet.prototype.closeIfOpen = function() { - this.storage.removeIsOpen(this.id); +Wallet.prototype.unlock = function() { + this.storage.removeLock(this.id); }; -Wallet.prototype._checkLocked = function() { - if (this.getIsOpen()) { - this.isLocked = true; - } - else { - this.setIsOpen(); - } +Wallet.prototype.checkAndLock = function() { + if (this.getLock()) { + return true; + } + + this.setLock(); + return false; }; Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { @@ -432,6 +432,12 @@ Wallet.prototype.netStart = function(callback) { var self = this; var net = this.network; + if (this.checkAndLock() && !this.ignoreLock) { + +console.log('[Wallet.js.436] LOCKED' ); //TODO + this.emit('locked'); + return; + } net.removeAllListeners(); net.on('connect', self._handleConnect.bind(self)); @@ -464,7 +470,6 @@ Wallet.prototype.netStart = function(callback) { self.scheduleConnect(); self.emit('txProposalsUpdated'); }, 10); - self._checkLocked(); }); }; @@ -1018,9 +1023,7 @@ Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) { Wallet.prototype.disconnect = function() { this.log('## DISCONNECTING'); - if (!this.isLocked) { - this.closeIfOpen(); - } + this.unlock(); this.network.disconnect(); }; diff --git a/js/models/storage/LocalEncrypted.js b/js/models/storage/LocalEncrypted.js index 2dc2e9377..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,16 +180,16 @@ Storage.prototype.getLastOpened = function() { return this.getGlobal('lastOpened'); } -Storage.prototype.setIsOpen = function(walletId) { - this.setGlobal(this._key(walletId, 'isOpen'), true); +Storage.prototype.setLock = function(walletId) { + this.setGlobal(this._key(walletId, 'Lock'), true); } -Storage.prototype.getIsOpen = function(walletId) { - return this.getGlobal(this._key(walletId, 'isOpen')); +Storage.prototype.getLock = function(walletId) { + return this.getGlobal(this._key(walletId, 'Lock')); } -Storage.prototype.removeIsOpen = function(walletId) { - this.localStorage.removeItem(this._key(walletId, 'isOpen')); +Storage.prototype.removeLock = function(walletId) { + this.removeGlobal(this._key(walletId, 'Lock')); } //obj contains keys to be set diff --git a/js/routes.js b/js/routes.js index b17bf1304..f0d95f93f 100644 --- a/js/routes.js +++ b/js/routes.js @@ -2,100 +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' - }) - .when('/warning', { - templateUrl: 'views/warning.html', - validate: true - }) - .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'); - } - if ($rootScope.wallet && $rootScope.wallet.isLocked) { - $location.path('/warning'); +.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 cf6130b9e..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('receive'); - } - }; - - 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 b6c94d2fd..3a4b426cc 100644 --- a/test/mocks/FakeStorage.js +++ b/test/mocks/FakeStorage.js @@ -27,16 +27,16 @@ FakeStorage.prototype.getLastOpened = function() { return this.storage['lastOpened']; }; -FakeStorage.prototype.setIsOpen = function(id) { - this.storage[id + '::isOpen'] = true; +FakeStorage.prototype.setLock = function(id) { + this.storage[id + '::lock'] = true; } -FakeStorage.prototype.getIsOpen = function(id) { - return this.storage[id + '::isOpen']; +FakeStorage.prototype.getLock = function(id) { + return this.storage[id + '::lock']; } -FakeStorage.prototype.removeIsOpen = function(id) { - delete this[id + '::isOpen']; +FakeStorage.prototype.removeLock = function(id) { + delete this.storage[id + '::lock']; } FakeStorage.prototype.removeGlobal = function(id) { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index accd28cb9..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; }; @@ -1024,11 +1025,34 @@ describe('Wallet model', function() { }); it('should check if wallet is already opened', function() { - var w = createW(); - w._checkLocked(); - w.isLocked.should.equal(false); - w._checkLocked(); - w.isLocked.should.equal(true); + 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); }); }); diff --git a/test/test.storage.LocalEncrypted.js b/test/test.storage.LocalEncrypted.js index 41885f298..8ad081f06 100644 --- a/test/test.storage.LocalEncrypted.js +++ b/test/test.storage.LocalEncrypted.js @@ -160,16 +160,16 @@ describe('Storage/LocalEncrypted model', function() { }); }); - describe('#WalletIsOpened', function() { + describe('#WalletLock', function() { it('should get/set/remove opened', function() { var s = new LocalEncrypted({ localStorage: localMock, password: 'password' }); - s.setIsOpen('walletId'); - s.getIsOpen('walletId').should.equal(true); - s.removeIsOpen('walletId'); - should.not.exist(s.getIsOpen('walletId')); + s.setLock('walletId'); + s.getLock('walletId').should.equal(true); + s.removeLock('walletId'); + should.not.exist(s.getLock('walletId')); }); }); diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index faead7aeb..fa0b2462f 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -225,15 +225,15 @@ describe("Unit: Controllers", function() { beforeEach(inject(function($controller, $injector) { $httpBackend = $injector.get('$httpBackend'); $httpBackend.when('GET', GH) - .respond([{ - name: "v100.1.6", - zipball_url: "https://api.github.com/repos/bitpay/copay/zipball/v0.0.6", - tarball_url: "https://api.github.com/repos/bitpay/copay/tarball/v0.0.6", - commit: { - sha: "ead7352bf2eca705de58d8b2f46650691f2bc2c7", - url: "https://api.github.com/repos/bitpay/copay/commits/ead7352bf2eca705de58d8b2f46650691f2bc2c7" - } - }]); + .respond([{ + name: "v100.1.6", + zipball_url: "https://api.github.com/repos/bitpay/copay/zipball/v0.0.6", + tarball_url: "https://api.github.com/repos/bitpay/copay/tarball/v0.0.6", + commit: { + sha: "ead7352bf2eca705de58d8b2f46650691f2bc2c7", + url: "https://api.github.com/repos/bitpay/copay/commits/ead7352bf2eca705de58d8b2f46650691f2bc2c7" + } + }]); })); var rootScope; @@ -303,11 +303,6 @@ describe("Unit: Controllers", function() { expect(array.length).equal(n); }); - it('should ignore if wallet is locked', function() { - scope.ignoreLocked(); - expect(rootScope.wallet.isLocked).equal(false); - }); - }); describe('Send Controller', function() { @@ -428,4 +423,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 index 1872629a5..7597b1c14 100644 --- a/views/warning.html +++ b/views/warning.html @@ -1,26 +1,26 @@ -
+
Copay

Warning!

- This wallet appear to be open on an other tab at your browser. Are you sure - you want to proceed? (*) + This wallet appears to be currently open. +
+ Opening the wallet in multiple browser tabs could lead to unexpected results

- (*) Opening the wallet in multiple tabs could lead to unexpected results