Merge pull request #799 from maraoz/feature/uri-handler

bitcoin uri handling
This commit is contained in:
Ryan X. Charles 2014-07-03 12:16:14 -07:00
commit ea86f45d09
12 changed files with 171 additions and 17 deletions

View file

@ -883,6 +883,21 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
</script>
<!-- URI PAYMENT -->
<script type="text/ng-template" id="uri_payment.html">
<h3 class="text-center">
Preparing payment...
</h3>
<div data-ng-init="" data-ng-controller="UriPaymentController">
<p>Protocol: {{protocol}}</p>
<p>To: {{address}}</p>
<p>Amount: {{amount}}</p>
<p>Message:</p>
<div class="alert-box secondary radius">{{message}}</div>
</div>
</script>
<!-- NOT FOUND -->
<script type="text/ng-template" id="404.html">
<h2 class="text-center">404</h2>
@ -925,6 +940,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="js/services/notifications.js"></script>
<script src="js/services/backupService.js"></script>
<script src="js/services/isMobile.js"></script>
<script src="js/services/uriHandler.js"></script>
<script src="js/controllers/header.js"></script>
<script src="js/controllers/footer.js"></script>
@ -936,6 +952,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="js/controllers/setup.js"></script>
<script src="js/controllers/import.js"></script>
<script src="js/controllers/settings.js"></script>
<script src="js/controllers/uriPayment.js"></script>
<script src="js/init.js"></script>
</body>

View file

@ -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);
}
});

View file

@ -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;
};
});

View file

@ -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');

View file

@ -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);
});

View file

@ -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);

View file

@ -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 {

View file

@ -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);
});

12
js/services/uriHandler.js Normal file
View file

@ -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());

View file

@ -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');
});
});

View file

@ -300,7 +300,7 @@ describe("Unit: Controllers", function() {
'<form name="form">' +
'<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.0001" max="10000000" enough-amount required>' +
'</form>'
);
);
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');
});
});
});

View file

@ -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();
}));
});