diff --git a/bower.json b/bower.json index ba78d8346..d96cd64d8 100644 --- a/bower.json +++ b/bower.json @@ -22,6 +22,7 @@ "angular-moment": "~0.7.1", "socket.io-client": ">=1.0.0", "mousetrap": "1.4.6", - "zeroclipboard": "~2.1.6" + "zeroclipboard": "~2.1.6", + "ng-idle": "*" } } diff --git a/index.html b/index.html index 69fd2321f..2188f3e9a 100644 --- a/index.html +++ b/index.html @@ -62,6 +62,7 @@ + diff --git a/js/app.js b/js/app.js index cf5656438..058dfbba6 100644 --- a/js/app.js +++ b/js/app.js @@ -27,6 +27,7 @@ var copayApp = window.copayApp = angular.module('copayApp', [ 'angularMoment', 'mm.foundation', 'monospaced.qrcode', + 'ngIdle', 'copayApp.filters', 'copayApp.services', 'copayApp.controllers', @@ -40,6 +41,7 @@ copayApp.config(function($sceDelegateProvider) { ]); }); + angular.module('copayApp.filters', []); angular.module('copayApp.services', []); angular.module('copayApp.controllers', []); diff --git a/js/controllers/addresses.js b/js/controllers/addresses.js index 907bfc7ef..c81a10fc3 100644 --- a/js/controllers/addresses.js +++ b/js/controllers/addresses.js @@ -20,13 +20,6 @@ angular.module('copayApp.controllers').controller('AddressesController', var ModalInstanceCtrl = function ($scope, $modalInstance, address) { $scope.address = address; - $scope.openExternal = function(address) { - var url = 'bitcoin:' + address; - if (window.cordova) return window.open(url, '_blank'); - - window.location = url; - } - $scope.cancel = function () { $modalInstance.dismiss('cancel'); }; diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js index 79b8ad859..7606483b9 100644 --- a/js/controllers/sidebar.js +++ b/js/controllers/sidebar.js @@ -82,4 +82,15 @@ angular.module('copayApp.controllers').controller('SidebarController', // 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'); + }); + } + }); diff --git a/js/directives.js b/js/directives.js index 8601249b3..03681d02b 100644 --- a/js/directives.js +++ b/js/directives.js @@ -198,24 +198,31 @@ angular.module('copayApp.directives') } }; }) -// From https://gist.github.com/asafge/7430497 -.directive('ngReallyClick', [ - - function() { + .directive('openExternal', function() { return { restrict: 'A', link: function(scope, element, attrs) { element.bind('click', function() { - var message = attrs.ngReallyMessage; - if (message && confirm(message)) { - scope.$apply(attrs.ngReallyClick); - } + window.open('bitcoin:'+attrs.address, '_blank'); }); } } - } -]) - + }) + // From https://gist.github.com/asafge/7430497 + .directive('ngReallyClick', [function() { + return { + restrict: 'A', + link: function(scope, element, attrs) { + element.bind('click', function() { + var message = attrs.ngReallyMessage; + if (message && confirm(message)) { + scope.$apply(attrs.ngReallyClick); + } + }); + } + } + } + ]) .directive('match', function () { return { require: 'ngModel', @@ -265,5 +272,4 @@ angular.module('copayApp.directives') }); } }; - }) -; + }); diff --git a/js/models/core/HDParams.js b/js/models/core/HDParams.js index 9c84108b9..6f045e679 100644 --- a/js/models/core/HDParams.js +++ b/js/models/core/HDParams.js @@ -18,7 +18,7 @@ function HDParams(opts) { HDParams.init = function(totalCopayers) { preconditions.shouldBeNumber(totalCopayers); - var ret = [new HDParams()]; + var ret = [new HDParams({receiveIndex: 1})]; for (var i = 0 ; i < totalCopayers ; i++) { ret.push(new HDParams({copayerIndex: i})); } diff --git a/js/routes.js b/js/routes.js index 9db46fdc1..00ab8b185 100644 --- a/js/routes.js +++ b/js/routes.js @@ -65,17 +65,22 @@ angular //Setting HTML5 Location Mode angular .module('copayApp') - .config(function($locationProvider) { + .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) { + .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()) { diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 7bae6bdce..57a5faa13 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -185,13 +185,14 @@ angular.module('copayApp.services') root.updateAddressList = function() { var w = $rootScope.wallet; - if (w) + 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; @@ -212,6 +213,10 @@ angular.module('copayApp.services') $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; diff --git a/karma.conf.js b/karma.conf.js index 8bd9d8e1b..fd2c586fe 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -21,6 +21,7 @@ module.exports = function(config) { 'lib/angular/angular.min.js', 'lib/angular-mocks/angular-mocks.js', 'lib/moment/moment.js', + 'lib/ng-idle/angular-idle.min.js', 'lib/angular-moment/angular-moment.js', 'lib/qrcode-generator/js/qrcode.js', 'lib/angular-qrcode/qrcode.js', diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js index 4c043395e..945e584e9 100644 --- a/test/mocks/FakeWallet.js +++ b/test/mocks/FakeWallet.js @@ -46,6 +46,10 @@ FakeWallet.prototype.getAddressesInfo = function() { return ret; }; +FakeWallet.prototype.isReady = function() { + return true; +} + FakeWallet.prototype.getBalance = function(cb) { return cb(null, this.balance, this.balanceByAddr, this.safeBalance); diff --git a/test/test.PublicKeyRing.js b/test/test.PublicKeyRing.js index 088c1f819..41d874676 100644 --- a/test/test.PublicKeyRing.js +++ b/test/test.PublicKeyRing.js @@ -145,9 +145,8 @@ describe('PublicKeyRing model', function() { var k = createW(); var w = k.w; - var a = w.getAddresses(); - a.length.should.equal(0); + a.length.should.equal(1); [true, false].forEach(function(isChange){ for (var i = 0; i < 2; i++) { @@ -156,15 +155,22 @@ describe('PublicKeyRing model', function() { }); var as = w.getAddressesInfo(); - as.length.should.equal(4); + as.length.should.equal(5); // include pre-generated shared one for (var j in as) { var a = as[j]; a.address.isValid().should.equal(true); a.addressStr.should.equal(a.address.toString()); - a.isChange.should.equal([false, false, true, true][j]); + a.isChange.should.equal([false, false, false, true, true][j]); } }); + + it('should start with one shared address', function() { + var k = createW(); + var a = k.w.getAddresses(); + a.length.should.equal(1); + }); + it('should count generation indexes', function() { var k = createW(); var w = k.w; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 46cc15cd8..04b1626de 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -594,7 +594,7 @@ describe('Wallet model', function() { it('should get balance', function(done) { - var w = createW(); + var w = createW2(); var spy = sinon.spy(w.blockchain, 'getUnspent'); w.blockchain.fixUnspent([]); w.getBalance(function(err, balance, balanceByAddr, safeBalance) { diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index 0358d1b74..aa7f7e2c8 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -93,8 +93,12 @@ describe("Unit: controllerUtils", function() { controllerUtils.updateBalance(function() { expect($rootScope.totalBalanceBTC).to.be.equal(1.00000001); expect($rootScope.availableBalanceBTC).to.be.equal(0.90000002); + expect($rootScope.lockedBalanceBTC).to.be.equal(0.09999999); + expect($rootScope.totalBalance).to.be.equal(1000000.01); expect($rootScope.availableBalance).to.be.equal(900000.02); + expect($rootScope.lockedBalance).to.be.equal(99999.99); + expect($rootScope.addrInfos).not.to.equal(null); expect($rootScope.addrInfos[0].address).to.equal(Waddr); }); diff --git a/views/includes/sidebar-mobile.html b/views/includes/sidebar-mobile.html index 5b634fe1a..506fcc54a 100644 --- a/views/includes/sidebar-mobile.html +++ b/views/includes/sidebar-mobile.html @@ -24,16 +24,16 @@
- Available + Locked {{availableBalance || 0|noFractionNumber}} {{$root.unitName}} - + tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}} +  
diff --git a/views/includes/sidebar.html b/views/includes/sidebar.html index 8679c9f83..220358562 100644 --- a/views/includes/sidebar.html +++ b/views/includes/sidebar.html @@ -34,7 +34,7 @@ |noFractionNumber}} {{$root.unitName}}
- Available + Locked   @@ -42,10 +42,10 @@ class="has-tip" data-options="disable_for_touch:true" tooltip-popup-delay='500' - tooltip="{{availableBalanceBTC || 0 |noFractionNumber:8}} BTC" + tooltip="{{lockedBalanceBTC || 0 |noFractionNumber:8}} BTC" tooltip-trigger="mouseenter" - tooltip-placement="bottom">{{availableBalance || 0|noFractionNumber}} {{$root.unitName}} - + tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}} +  
diff --git a/views/modals/qr-address.html b/views/modals/qr-address.html index feb228ee2..031deecac 100644 --- a/views/modals/qr-address.html +++ b/views/modals/qr-address.html @@ -10,8 +10,8 @@ {{address.balance || 0|noFractionNumber}} {{$root.unitName}}
- -   Open in external aplication + +   Open in external application