diff --git a/index.html b/index.html index 68c07a745..75dd06d39 100644 --- a/index.html +++ b/index.html @@ -883,6 +883,21 @@ on supported browsers please check http://www.w + + + + @@ -936,6 +952,7 @@ on supported browsers please check http://www.w + diff --git a/js/controllers/header.js b/js/controllers/header.js index a638f7a2c..841e9ac9b 100644 --- a/js/controllers/header.js +++ b/js/controllers/header.js @@ -71,7 +71,9 @@ angular.module('copayApp.controllers').controller('HeaderController', $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); + notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? + 'You have a pending transaction proposal' : + 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); } }); diff --git a/js/controllers/send.js b/js/controllers/send.js index 5d94e90fd..0ab3d3858 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -23,6 +23,13 @@ angular.module('copayApp.controllers').controller('SendController', return flag; }; + if ($rootScope.pendingPayment) { + var pp = $rootScope.pendingPayment; + $scope.address = pp.address; + var amount = pp.amount / config.unitToSatoshi * 100000000; + $scope.amount = amount; + } + // Detect protocol $scope.isHttp = ($window.location.protocol.indexOf('http') === 0); @@ -61,6 +68,7 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loading = false; }); } + $rootScope.pendingPayment = null; }); // reset fields @@ -265,7 +273,7 @@ angular.module('copayApp.controllers').controller('SendController', }; $scope.topAmount = function(form) { - $scope.amount = $scope.getAvailableAmount(); + $scope.amount = $scope.getAvailableAmount(); form.amount.$pristine = false; }; }); diff --git a/js/controllers/signin.js b/js/controllers/signin.js index b2e6ef0c8..6503d46ab 100644 --- a/js/controllers/signin.js +++ b/js/controllers/signin.js @@ -13,6 +13,10 @@ angular.module('copayApp.controllers').controller('SigninController', $scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null; $scope.openPassword = ''; + if ($rootScope.pendingPayment) { + notification.info('Login Required', 'Please open wallet to complete payment'); + } + $scope.open = function(form) { if (form && form.$invalid) { notification.error('Error', 'Please enter the required fields'); diff --git a/js/controllers/uriPayment.js b/js/controllers/uriPayment.js new file mode 100644 index 000000000..9cbc383c6 --- /dev/null +++ b/js/controllers/uriPayment.js @@ -0,0 +1,17 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams, $timeout, $location) { + var data = decodeURIComponent($routeParams.data); + $rootScope.pendingPayment = copay.Structure.parseBitcoinURI($routeParams.data); + + $scope.protocol = $rootScope.pendingPayment.protocol; + $scope.address = $rootScope.pendingPayment.address; + $scope.amount = $rootScope.pendingPayment.amount; + $scope.message = $rootScope.pendingPayment.message; + + $timeout(function() { + $location.path('signin'); + }, 1000); + + +}); diff --git a/js/models/core/Structure.js b/js/models/core/Structure.js index 88543f966..aafee4c0f 100644 --- a/js/models/core/Structure.js +++ b/js/models/core/Structure.js @@ -50,4 +50,23 @@ Structure.MAX_NON_HARDENED = MAX_NON_HARDENED; Structure.SHARED_INDEX = SHARED_INDEX; Structure.ID_INDEX = ID_INDEX; +Structure.parseBitcoinURI = function(uri) { + var ret = {}; + var data = decodeURIComponent(uri); + var splitDots = data.split(':'); + ret.protocol = splitDots[0]; + data = splitDots[1]; + var splitQuestion = data.split('?'); + ret.address = splitQuestion[0]; + var search = splitQuestion[1]; + data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}', + function(key, value) { + return key === "" ? value : decodeURIComponent(value); + }); + ret.amount = parseFloat(data.amount); + ret.message = data.message; + + return ret; +}; + module.exports = require('soop')(Structure); diff --git a/js/routes.js b/js/routes.js index a4218112c..e4fe14cff 100644 --- a/js/routes.js +++ b/js/routes.js @@ -26,10 +26,6 @@ angular templateUrl: 'addresses.html', validate: true }) - .when('/join/:id', { - templateUrl: 'join.html', - validate: true - }) .when('/transactions', { templateUrl: 'transactions.html', validate: true @@ -49,6 +45,9 @@ angular .when('/unsupported', { templateUrl: 'unsupported.html' }) + .when('/uri_payment/:data', { + templateUrl: 'uri_payment.html' + }) .otherwise({ templateUrl: '404.html' }); @@ -64,7 +63,6 @@ angular }) .run(function($rootScope, $location) { $rootScope.$on('$routeChangeStart', function(event, next, current) { - if (!util.supports.data) { $location.path('unsupported'); } else { diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 342b33cdf..aa386b618 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -2,7 +2,7 @@ var bitcore = require('bitcore'); angular.module('copayApp.services') - .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video) { + .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) { var root = {}; root.getVideoMutedStatus = function(copayer) { @@ -64,6 +64,7 @@ angular.module('copayApp.services') }; root.setupRootVariables = function() { + uriHandler.register(); $rootScope.unitName = config.unitName; $rootScope.txAlertCount = 0; $rootScope.insightError = 0; @@ -102,7 +103,11 @@ angular.module('copayApp.services') }); w.on('ready', function(myPeerID) { $rootScope.wallet = w; - $location.path('addresses'); + if ($rootScope.pendingPayment) { + $location.path('send'); + } else { + $location.path('addresses'); + } if (!config.disableVideo) video.setOwnPeer(myPeerID, w, handlePeerVideo); }); diff --git a/js/services/uriHandler.js b/js/services/uriHandler.js new file mode 100644 index 000000000..ef50e1b5a --- /dev/null +++ b/js/services/uriHandler.js @@ -0,0 +1,12 @@ +'use strict'; + +var UriHandler = function() {}; + +UriHandler.prototype.register = function() { + var base = window.location.origin + '/'; + var url = base + '#/uri_payment/%s'; + navigator.registerProtocolHandler('bitcoin', + url, 'Copay'); +}; + +angular.module('copayApp.services').value('uriHandler', new UriHandler()); diff --git a/test/test.Structure.js b/test/test.Structure.js index 04b224668..3541c0f9c 100644 --- a/test/test.Structure.js +++ b/test/test.Structure.js @@ -39,13 +39,34 @@ describe('Structure model', function() { }); [ - ['m/45\'/0/0/0', {index: 0, isChange: false}], - ['m/45\'/0/0/1', {index: 1, isChange: false}], - ['m/45\'/0/0/2', {index: 2, isChange: false}], - ['m/45\'/0/1/0', {index: 0, isChange: true}], - ['m/45\'/0/1/1', {index: 1, isChange: true}], - ['m/45\'/0/1/2', {index: 2, isChange: true}], - ['m/45\'/0/0/900', {index: 900, isChange: false}], + ['m/45\'/0/0/0', { + index: 0, + isChange: false + }], + ['m/45\'/0/0/1', { + index: 1, + isChange: false + }], + ['m/45\'/0/0/2', { + index: 2, + isChange: false + }], + ['m/45\'/0/1/0', { + index: 0, + isChange: true + }], + ['m/45\'/0/1/1', { + index: 1, + isChange: true + }], + ['m/45\'/0/1/2', { + index: 2, + isChange: true + }], + ['m/45\'/0/0/900', { + index: 900, + isChange: false + }], ].forEach(function(datum) { var path = datum[0]; var result = datum[1]; @@ -55,5 +76,13 @@ describe('Structure model', function() { i.isChange.should.equal(result.isChange); }); }); + it('should get the correct result for bitcoin uri', function() { + var uri = 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=0.1&message=a%20bitcoin%20donation'; + var result = Structure.parseBitcoinURI(uri); + result.address.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8'); + result.amount.should.equal(0.1); + result.message.should.equal('a bitcoin donation'); + result.protocol.should.equal('bitcoin'); + }); }); diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 8525dc782..046ea0704 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -300,7 +300,7 @@ describe("Unit: Controllers", function() { '
' + '' + '
' - ); + ); scope.model = { amount: null }; @@ -368,4 +368,30 @@ describe("Unit: Controllers", function() { }); }); + describe('UriPayment Controller', function() { + var what; + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope.$new(); + var routeParams = { + data: 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=0.1&message=a%20bitcoin%20donation' + }; + what = $controller('UriPaymentController', { + $scope: scope, + $routeParams: routeParams + }); + })); + + it('should exist', function() { + should.exist(what); + }); + + it('should parse url correctly', function() { + should.exist(what); + scope.protocol.should.equal('bitcoin'); + scope.address.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8'); + scope.amount.should.equal(0.1); + scope.message.should.equal('a bitcoin donation'); + }); + }); + }); diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index badab5154..0358d1b74 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -181,3 +181,20 @@ describe("Unit: isMobile Service", function() { isMobile.any().should.equal(true); })); }); +describe("Unit: video service", function() { + beforeEach(angular.mock.module('copayApp.services')); + it('should contain a video service', inject(function(video) { + should.exist(video); + })); +}); +describe("Unit: uriHandler service", function() { + beforeEach(angular.mock.module('copayApp.services')); + it('should contain a uriHandler service', inject(function(uriHandler) { + should.exist(uriHandler); + })); + it('should register', inject(function(uriHandler) { + (function() { + uriHandler.register(); + }).should.not.throw(); + })); +});