diff --git a/src/js/controllers/tab-send.controller.js b/src/js/controllers/tab-send.controller.js index 431b81c80..12da93cb8 100644 --- a/src/js/controllers/tab-send.controller.js +++ b/src/js/controllers/tab-send.controller.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('tabSendController', function tabSendController( - bitcoinUriService + bitcoinUriService , externalLinkService , $scope , $log @@ -21,28 +21,26 @@ angular.module('copayApp.controllers').controller('tabSendController', function , clipboardService , incomingDataService , moonPayService + , ionicToast + , opencapService ) { + var clipboardHasAddress = false; var clipboardHasContent = false; var originalList; - var isBuyBitcoinAllowed = false; $scope.displayBalanceAsFiat = true; $scope.walletSelectorTitleForce = true; - moonPayService.getCountryByIpAddress().then(function onGetCountryByIpAddress(user) { - isBuyBitcoinAllowed = user.isAllowed; - }); - - $scope.addContact = function() { - $state.go('tabs.send.addressbook'); + $scope.addContact = function () { + $state.go('tabs.send.addressbook'); }; - $scope.pasteClipboard = function() { + $scope.pasteClipboard = function () { if ($scope.clipboardHasAddress || $scope.clipboardHasContent) { - clipboardService.readFromClipboard(function(text) { - $scope.$apply(function() { - $scope.formData.search = text; - $scope.findContact($scope.formData.search); + clipboardService.readFromClipboard(function (text) { + $scope.$apply(function () { + $scope.formData.search = text; + $scope.findContact($scope.formData.search); }); }); } else { @@ -53,12 +51,12 @@ angular.module('copayApp.controllers').controller('tabSendController', function } }; - $scope.$on("$ionicView.enter", function(event, data) { + $scope.$on("$ionicView.enter", function (event, data) { var stateParams = sendFlowService.state.getClone(); $scope.fromWallet = profileService.getWallet(stateParams.fromWalletId); - clipboardService.readFromClipboard(function(text) { + clipboardService.readFromClipboard(function (text) { if (text.length > 200) { text = text.substring(0, 200); } @@ -82,33 +80,78 @@ angular.module('copayApp.controllers').controller('tabSendController', function return; } updateHasFunds(); - updateContactsList(function() { + updateContactsList(function () { updateList(); }); }); - $scope.findContact = function(search) { + $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; - $timeout(function() { + $timeout(function () { $scope.$apply(); }); return; } + $scope.resolveOpencapAlias(search); + var params = sendFlowService.state.getClone(); params.data = search; sendFlowService.start(params, function onError() { - var result = lodash.filter(originalList, function(item) { + var result = lodash.filter(originalList, function (item) { var val = item.name; return lodash.startsWith(val.toLowerCase(), search.toLowerCase()); }); - + $scope.list = result; }); }; - var hasWallets = function() { + var hasWallets = function () { $scope.walletsWithFunds = profileService.getWallets({ onlyComplete: true, hasFunds: true @@ -127,11 +170,11 @@ angular.module('copayApp.controllers').controller('tabSendController', function $scope.hasWallets = lodash.isEmpty($scope.wallets) ? false : true; }; - var updateHasFunds = function() { + var updateHasFunds = function () { $scope.hasFunds = false; var index = 0; - lodash.each($scope.wallets, function(w) { - walletService.getStatus(w, {}, function(err, status) { + lodash.each($scope.wallets, function (w) { + walletService.getStatus(w, {}, function (err, status) { ++index; if (err && !status) { @@ -146,7 +189,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function if (index === $scope.wallets.length) { $scope.checkingBalance = false; - $timeout(function() { + $timeout(function () { $scope.$apply(); }); } @@ -154,26 +197,26 @@ angular.module('copayApp.controllers').controller('tabSendController', function }); }; - var updateContactsList = function(cb) { + var updateContactsList = function (cb) { var config = configService.getSync(); var defaults = configService.getDefaults(); - addressbookService.list(function(err, ab) { + addressbookService.list(function (err, ab) { if (err) $log.error(err); $scope.hasContacts = lodash.isEmpty(ab) ? false : true; if (!$scope.hasContacts) return cb(); var completeContacts = []; - lodash.each(ab, function(v, k) { + lodash.each(ab, function (v, k) { completeContacts.push({ name: lodash.isObject(v) ? v.name : v, address: k, email: lodash.isObject(v) ? v.email : null, recipientType: 'contact', coin: v.coin, - displayCoin: (v.coin == 'bch' - ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias) - : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase() + displayCoin: (v.coin == 'bch' + ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias) + : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase() }); }); originalList = completeContacts; @@ -181,19 +224,19 @@ angular.module('copayApp.controllers').controller('tabSendController', function }); }; - var updateList = function() { + var updateList = function () { $scope.list = lodash.clone(originalList); - $timeout(function() { + $timeout(function () { $ionicScrollDelegate.resize(); $scope.$apply(); }, 10); }; - $scope.searchInFocus = function() { + $scope.searchInFocus = function () { $scope.searchFocus = true; }; - $scope.searchBlurred = function() { + $scope.searchBlurred = function () { if ($scope.formData.search == null || $scope.formData.search.length === 0) { $scope.searchFocus = false; } @@ -210,7 +253,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function } $log.debug('Got toAddress:' + toAddress + ' | ' + item.name); - + var stateParams = sendFlowService.state.getClone(); stateParams.toAddress = toAddress; stateParams.coin = item.coin; @@ -218,7 +261,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function }); }; - $scope.startWalletToWalletTransfer = function() { + $scope.startWalletToWalletTransfer = function () { console.log('startWalletToWalletTransfer()'); var params = sendFlowService.state.getClone(); params.isWalletTransfer = true; @@ -226,22 +269,18 @@ angular.module('copayApp.controllers').controller('tabSendController', function } // This could probably be enhanced refactoring the routes abstract states - $scope.createWallet = function() { - $state.go('tabs.home').then(function() { + $scope.createWallet = function () { + $state.go('tabs.home').then(function () { $state.go('tabs.add.create-personal'); }); }; - $scope.buyBitcoin = function() { - if (isBuyBitcoinAllowed) { - moonPayService.start(); - } else { - var os = platformInfo.isAndroid ? 'android' : platformInfo.isIOS ? 'ios' : 'desktop'; - externalLinkService.open('https://purchase.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os); - } + $scope.buyBitcoin = function () { + var os = platformInfo.isAndroid ? 'android' : platformInfo.isIOS ? 'ios' : 'desktop'; + externalLinkService.open('https://purchase.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os); }; - $scope.$on("$ionicView.beforeEnter", function(event, data) { + $scope.$on("$ionicView.beforeEnter", function (event, data) { console.log(data); console.log('tab-send onBeforeEnter sendflow ', sendFlowService.state); $scope.isIOS = platformInfo.isIOS && platformInfo.isCordova; @@ -254,7 +293,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function originalList = []; hasWallets(); - configService.whenAvailable(function(_config) { + configService.whenAvailable(function (_config) { $scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat'; }); @@ -263,4 +302,4 @@ angular.module('copayApp.controllers').controller('tabSendController', function } }); -}); \ No newline at end of file +}); diff --git a/src/js/services/opencap.service.js b/src/js/services/opencap.service.js new file mode 100644 index 000000000..b5b5797bb --- /dev/null +++ b/src/js/services/opencap.service.js @@ -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; + }); +})(); diff --git a/www/templates/popoverOpencapSend.html b/www/templates/popoverOpencapSend.html new file mode 100644 index 000000000..9d6d2482a --- /dev/null +++ b/www/templates/popoverOpencapSend.html @@ -0,0 +1,21 @@ + +
+
+ Alias found! +
+
+
+ {{opencapAddresses.bch}} + +
+
+ {{opencapAddresses.btc}} + +
+ + DNSSEC not used + +
+
+ +
\ No newline at end of file diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 28be3f41b..fb37baa12 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -33,9 +33,9 @@

{{fromWallet.name}}

- + ng-focus="searchInFocus()" ng-blur="searchBlurred()" id="search-input">