Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenCAP sending feature added #409

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
47 changes: 46 additions & 1 deletion src/js/controllers/tab-send.controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

angular.module('copayApp.controllers').controller('tabSendController', function(bitcoinUriService, externalLinkService, $scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, platformInfo, sendFlowService, gettextCatalog, configService, $ionicPopup, $ionicNavBarDelegate, clipboardService, incomingDataService) {
angular.module('copayApp.controllers').controller('tabSendController', function(bitcoinUriService, externalLinkService, $scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, platformInfo, sendFlowService, gettextCatalog, configService, $ionicPopup, $ionicNavBarDelegate, clipboardService, incomingDataService, ionicToast, opencapService) {
var clipboardHasAddress = false;
var clipboardHasContent = false;
var originalList;
Expand Down Expand Up @@ -61,6 +61,49 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
});
});

$scope.closePopover = function() {
$scope.popover.hide();
};

$scope.$on('$destroy', function() {
$scope.popover.remove();
});

$scope.confirmOpenapAddress = function(address, coin){
$scope.popover.remove();
var params = sendFlowService.state.getClone();
params.data = address;
params.coin = coin;
sendFlowService.start(params, function onError() {
return
});
}

$scope.resolveOpencapAlias = function(alias){
opencapService.getAddress(alias)
.then(function(result) {
if (typeof $scope.fromWallet !== 'undefined'){
if ($scope.fromWallet.coin === 'bch'){
result.addresses = {bch: result.addresses.bch};
}
if ($scope.fromWallet.coin === 'btc'){
result.addresses = {btc: result.addresses.btc};
}
}

$scope.opencapAddresses = result.addresses;
$scope.opencapDnssec = result.dnssec;
$ionicPopover.fromTemplateUrl('templates/popoverOpencapSend.html', {scope: $scope})
.then(function onThen(popover) {
$scope.popover = popover;
popover.show(angular.element(document.querySelector('#search-input')))
});
})
.catch(function(status) {
// do nothing because they may have been typing
});
}

$scope.findContact = function(search) {
if (!search || search.length < 1) {
$scope.list = originalList;
Expand All @@ -70,6 +113,8 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
return;
}

$scope.resolveOpencapAlias(search);

var params = sendFlowService.state.getClone();
params.data = search;
sendFlowService.start(params, function onError() {
Expand Down
132 changes: 132 additions & 0 deletions src/js/services/opencap.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
'use strict';

(function() {
angular.module('bitcoincom.services').factory('opencapService', function($q, $http) {
function getAddress(alias) {
let aliasData = validateAlias(alias);
if (aliasData.username === '' || aliasData.domain === '') {
return $q(function onQ(resolve, reject) {
return reject('Invalid OpenCAP alias');
});
}

let deferred = $q.defer();
$http
.get(`https://dns.google.com/resolve?name=_opencap._tcp.${aliasData.domain}&type=SRV`)
.then(function onThen(response) {
deferred.resolve(
parseSRV(response.data)
.then(function onThen(data){
return getAddresses(alias, data.host, data.dnssec)
})
.catch(function onCatch(error) {
return $q(function onQ(resolve, reject) {
return reject(error);
});
})
);
})
.catch(function onCatch(response) {
deferred.reject('Couldn\'t find srv record for the provided domain');
});
return deferred.promise;
}

function parseSRV(respData) {
return $q(function onQ(resolve, reject) {
let dnssec = respData.AD;

if (typeof respData.Answer === 'undefined') {
return reject('Error contacting google dns server, no srv data');
}
if (respData.Answer.length < 1) {
return reject('Error contacting google dns server, not enough srv data');
}

let record = respData.Answer[0].data.split(' ');
if (record.length != 4) {
return reject('Error contacting google dns server, improper srv data');
}

if (record[3].slice(-1) == '.') {
record[3] = record[3].substring(0, record[3].length - 1);
}

return resolve({ host: record[3], dnssec });
});
};

function getAddresses(alias, host, dnssec) {
let deferred = $q.defer();
$http
.get(`https://${host}/v1/addresses?alias=${alias}`)
.then(function onThen(response) {
deferred.resolve(parseAddresses(response.data, dnssec).then());
})
.catch(function onCatch(response) {
deferred.reject('Address not found for the specified alias');
});
return deferred.promise;
};

function parseAddresses(respData, dnssec) {
let addresses = {}
return $q(function onQ(resolve, reject) {
for (let i = 0; i < respData.length; i++) {
if (respData[i].address_type === 'undefined') {
continue;
}
if (respData[i].address === 'undefined') {
continue;
}
// Take the last BCH address we hit, shouldn't matter which one
if (respData[i].address_type == 200 || respData[i].address_type == 201 || respData[i].address_type == 202) {
addresses.bch = respData[i].address;
}
// Take the last BTC address we hit, shouldn't matter which one
if (respData[i].address_type == 100 || respData[i].address_type == 101) {
addresses.btc = respData[i].address;
}
}

if (addresses.btc === 'undefined' && addresses.btc === 'undefined'){
return reject('Error contacting opencap server, no response');
}

return resolve({addresses, dnssec});
});
};

function validateUsername(username) {
return /^[a-z0-9._-]{1,25}$/.test(username);
}

function validateDomain(username) {
return /^[a-z0-9.\-]+\.[a-z]{2,4}$/.test(username);
}

function validateAlias(alias) {
let splitAlias = alias.split('$');
if (splitAlias.length != 2) {
return { username: '', domain: '' };
}
let username = splitAlias[0];
let domain = splitAlias[1];

if (!validateUsername(username)) {
return { username: '', domain: '' };
}
if (!validateDomain(domain)) {
return { username: '', domain: '' };
}

return { username, domain };
}

var service = {
getAddress,
};

return service;
});
})();
21 changes: 21 additions & 0 deletions www/templates/popoverOpencapSend.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<ion-popover-view style="height: 0px">
<div class="card" style="text-align: center">
<div class="item item-heading">
<span translate>Alias found!</span>
</div>
<div class="list">
<div class="item" ng-if="opencapAddresses.bch">
<span>{{opencapAddresses.bch}}</span>
<button ng-click="confirmOpenapAddress(opencapAddresses.bch, 'bch')" class="button button-standard button-primary button-outline" translate>Send BCH</button>
</div>
<div class="item" ng-if="opencapAddresses.btc">
<span>{{opencapAddresses.btc}}</span>
<button ng-click="confirmOpenapAddress(opencapAddresses.btc, 'btc')" class="button button-standard button-primary button-outline" translate>Send BTC</button>
</div>
<span class="item" ng-if="!opencapDnssec" style="color: red; " translate>
DNSSEC not used
</span>
</div>
</div>
</div>
</ion-popover-view>
4 changes: 2 additions & 2 deletions www/views/tab-send.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ <h2>{{fromWallet.name}}</h2>
<div class="send-wrapper item">
<div class="row">
<div class="input" ng-class="{'focus': searchFocus}">
<input type="text" class="search-input" placeholder="{{'Search or enter bitcoin address' | translate}}"
<input type="text" class="search-input" placeholder="{{'Search or enter bitcoin address or alias' | translate}}"
ng-model="formData.search" ng-change="findContact(formData.search)" ng-model-onblur
ng-focus="searchInFocus()" ng-blur="searchBlurred()">
ng-focus="searchInFocus()" ng-blur="searchBlurred()" id="search-input">
</div>
</div>
<div class="buttons">
Expand Down