diff --git a/cordova/build.sh b/cordova/build.sh
index 6b5f294ba..1bf496ce1 100755
--- a/cordova/build.sh
+++ b/cordova/build.sh
@@ -129,6 +129,9 @@ if [ ! -d $PROJECT ]; then
cordova plugin add org.apache.cordova.file
checkOK
+ cordova plugin add https://github.com/EddyVerbruggen/cordova-plugin-touch-id && cordova prepare
+ checkOK
+
fi
if $DBGJS
diff --git a/public/views/preferences.html b/public/views/preferences.html
index 0922aabbc..1221f7e11 100644
--- a/public/views/preferences.html
+++ b/public/views/preferences.html
@@ -6,7 +6,7 @@
-
+
{{index.alias}} [{{index.walletName}}] settings
@@ -40,13 +40,17 @@
Request Password for Spending Funds
+ -
+ Request Touch ID for Spending Funds
+
+
-
Hardware wallet
{{preferences.externalSource}}
-
+
-
diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js
index 4f3019b1a..3ef895e8a 100644
--- a/src/js/controllers/index.js
+++ b/src/js/controllers/index.js
@@ -1,6 +1,6 @@
'use strict';
-angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettext, amMoment, nodeWebkit, addonManager, feeService, isChromeApp, bwsError, txFormatService, uxLanguage, $state, glideraService, isMobile) {
+angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, nodeWebkit, addonManager, feeService, isChromeApp, bwsError, txFormatService, uxLanguage, $state, glideraService, isMobile) {
var self = this;
self.isCordova = isCordova;
self.isChromeApp = isChromeApp;
@@ -1152,6 +1152,20 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.setTab(tab, reset);
});
+ $rootScope.$on('Local/RequestTouchid', function(event, cb) {
+ window.plugins.touchid.verifyFingerprint(
+ gettextCatalog.getString('Scan your fingerprint please'),
+ function(msg) {
+ // OK
+ return cb();
+ },
+ function(msg) {
+ // ERROR
+ return cb(gettext('Invalid Touch ID'));
+ }
+ );
+ });
+
$rootScope.$on('Local/ShowAlert', function(event, msg, cb) {
self.showErrorPopup(msg, cb);
});
diff --git a/src/js/controllers/preferences.js b/src/js/controllers/preferences.js
index ea9abe04f..d5c29fadf 100644
--- a/src/js/controllers/preferences.js
+++ b/src/js/controllers/preferences.js
@@ -2,24 +2,34 @@
angular.module('copayApp.controllers').controller('preferencesController',
function($scope, $rootScope, $filter, $timeout, $modal, $log, lodash, configService, profileService, uxLanguage) {
- var config = configService.getSync();
- this.unitName = config.wallet.settings.unitName;
- this.bwsurl = config.bws.url;
- this.currentLanguageName = uxLanguage.getCurrentLanguageName();
- this.selectedAlternative = {
- name: config.wallet.settings.alternativeName,
- isoCode: config.wallet.settings.alternativeIsoCode
- };
- $scope.spendUnconfirmed = config.wallet.spendUnconfirmed;
- $scope.glideraEnabled = config.glidera.enabled;
- $scope.glideraTestnet = config.glidera.testnet;
- var fc = profileService.focusedClient;
- if (fc) {
- $scope.encrypt = fc.hasPrivKeyEncrypted();
- this.externalSource = fc.getPrivKeyExternalSourceName() == 'ledger' ? "Ledger" : null;
- // TODO externalAccount
- //this.externalIndex = fc.getExternalIndex();
- }
+
+ this.init = function() {
+ var config = configService.getSync();
+ this.unitName = config.wallet.settings.unitName;
+ this.bwsurl = config.bws.url;
+ this.currentLanguageName = uxLanguage.getCurrentLanguageName();
+ this.selectedAlternative = {
+ name: config.wallet.settings.alternativeName,
+ isoCode: config.wallet.settings.alternativeIsoCode
+ };
+ $scope.spendUnconfirmed = config.wallet.spendUnconfirmed;
+ $scope.glideraEnabled = config.glidera.enabled;
+ $scope.glideraTestnet = config.glidera.testnet;
+ var fc = profileService.focusedClient;
+ if (fc) {
+ $scope.encrypt = fc.hasPrivKeyEncrypted();
+ this.externalSource = fc.getPrivKeyExternalSourceName() == 'ledger' ? "Ledger" : null;
+ // TODO externalAccount
+ //this.externalIndex = fc.getExternalIndex();
+ }
+
+ if (window.touchidAvailable) {
+ var walletId = fc.credentials.walletId;
+ this.touchidAvailable = true;
+ config.touchIdFor = config.touchIdFor || {};
+ $scope.touchid = config.touchIdFor[walletId];
+ }
+ };
var unwatchSpendUnconfirmed = $scope.$watch('spendUnconfirmed', function(newVal, oldVal) {
if (newVal == oldVal) return;
@@ -94,10 +104,43 @@ angular.module('copayApp.controllers').controller('preferencesController',
});
});
+ var unwatchRequestTouchid = $scope.$watch('touchid', function(newVal, oldVal) {
+ if (newVal == oldVal || $scope.touchidError) {
+ $scope.touchidError = false;
+ return;
+ }
+ var walletId = profileService.focusedClient.credentials.walletId;
+
+ var opts = {
+ touchIdFor: {}
+ };
+ opts.touchIdFor[walletId] = newVal;
+
+ $rootScope.$emit('Local/RequestTouchid', function(err) {
+ if (err) {
+ $log.debug(err);
+ $timeout(function() {
+ $scope.touchidError = true;
+ $scope.touchid = oldVal;
+ }, 100);
+ }
+ else {
+ configService.set(opts, function(err) {
+ if (err) {
+ $log.debug(err);
+ $scope.touchidError = true;
+ $scope.touchid = oldVal;
+ }
+ });
+ }
+ });
+ });
+
$scope.$on('$destroy', function() {
unwatch();
unwatchSpendUnconfirmed();
unwatchGlideraEnabled();
unwatchGlideraTestnet();
+ unwatchRequestTouchid();
});
});
diff --git a/src/js/controllers/walletHome.js b/src/js/controllers/walletHome.js
index 3e7d253ac..34025618c 100644
--- a/src/js/controllers/walletHome.js
+++ b/src/js/controllers/walletHome.js
@@ -5,17 +5,19 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
var self = this;
$rootScope.hideMenuBar = false;
$rootScope.wpInputFocused = false;
- $scope.currentSpendUnconfirmed = configService.getSync().wallet.spendUnconfirmed;
+ var config = configService.getSync();
+ var configWallet = config.wallet;
+ $scope.currentSpendUnconfirmed = configWallet.spendUnconfirmed;
// INIT
- var config = configService.getSync().wallet.settings;
- this.unitToSatoshi = config.unitToSatoshi;
+ var walletSettings = configWallet.settings;
+ this.unitToSatoshi = walletSettings.unitToSatoshi;
this.satToUnit = 1 / this.unitToSatoshi;
- this.unitName = config.unitName;
- this.alternativeIsoCode = config.alternativeIsoCode;
- this.alternativeName = config.alternativeName;
+ this.unitName = walletSettings.unitName;
+ this.alternativeIsoCode = walletSettings.alternativeIsoCode;
+ this.alternativeName = walletSettings.alternativeName;
this.alternativeAmount = 0;
- this.unitDecimals = config.unitDecimals;
+ this.unitDecimals = walletSettings.unitDecimals;
this.isCordova = isCordova;
this.addresses = [];
this.isMobile = isMobile.any();
@@ -85,6 +87,16 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
$rootScope.hideMenuBar = false;
});
+ var requestTouchid = function(cb) {
+ var fc = profileService.focusedClient;
+ config.touchIdFor = config.touchIdFor || {};
+ if (window.touchidAvailable && config.touchIdFor[fc.credentials.walletId]) {
+ $rootScope.$emit('Local/RequestTouchid', cb);
+ } else {
+ return cb();
+ }
+ };
+
rateService.whenAvailable(function() {
self.isRateAvailable = true;
$rootScope.$digest();
@@ -249,7 +261,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
};
$scope.sign = function(txp) {
- var fc = profileService.focusedClient;
+ var fc = profileService.focusedClient;
if (!fc.canSign() && !fc.isPrivKeyExternal())
return;
@@ -264,45 +276,56 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
});
return;
};
+
self._setOngoingForSigning();
-
$scope.loading = true;
- $scope.error = null;
+ $scope.error = null;
$timeout(function() {
- profileService.signTxProposal(txp, function(err, txpsi) {
- self.setOngoingProcess();
+ requestTouchid(function(err) {
if (err) {
- $scope.$emit('UpdateTx');
+ self.setOngoingProcess();
$scope.loading = false;
- $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not accept payment'));
+ profileService.lockFC();
+ $scope.error = err;
$scope.$digest();
- } else {
- //if txp has required signatures then broadcast it
- var txpHasRequiredSignatures = txpsi.status == 'accepted';
- if (txpHasRequiredSignatures) {
- self.setOngoingProcess(gettext('Broadcasting transaction'));
- $scope.loading = true;
- fc.broadcastTxProposal(txpsi, function(err, txpsb, memo) {
- self.setOngoingProcess();
- $scope.loading = false;
- if (err) {
- $scope.$emit('UpdateTx');
- $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment'));
- $scope.$digest();
- } else {
- $log.debug('Transaction signed and broadcasted')
- if (memo)
- $log.info(memo);
-
- refreshUntilItChanges = true;
- $modalInstance.close(txpsb);
- }
- });
- } else {
- $scope.loading = false;
- $modalInstance.close(txpsi);
- }
+ return;
}
+
+ profileService.signTxProposal(txp, function(err, txpsi) {
+ self.setOngoingProcess();
+ if (err) {
+ $scope.$emit('UpdateTx');
+ $scope.loading = false;
+ $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not accept payment'));
+ $scope.$digest();
+ } else {
+ //if txp has required signatures then broadcast it
+ var txpHasRequiredSignatures = txpsi.status == 'accepted';
+ if (txpHasRequiredSignatures) {
+ self.setOngoingProcess(gettext('Broadcasting transaction'));
+ $scope.loading = true;
+ fc.broadcastTxProposal(txpsi, function(err, txpsb, memo) {
+ self.setOngoingProcess();
+ $scope.loading = false;
+ if (err) {
+ $scope.$emit('UpdateTx');
+ $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment'));
+ $scope.$digest();
+ } else {
+ $log.debug('Transaction signed and broadcasted')
+ if (memo)
+ $log.info(memo);
+
+ refreshUntilItChanges = true;
+ $modalInstance.close(txpsb);
+ }
+ });
+ } else {
+ $scope.loading = false;
+ $modalInstance.close(txpsi);
+ }
+ }
+ });
});
}, 100);
};
@@ -787,44 +810,56 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
} else {
feeService.getCurrentFeeValue(self.currentSendFeeLevel, cb);
}
- };
+ };
- getFee(function(err, feePerKb) {
- if (err) $log.debug(err);
- fc.sendTxProposal({
- toAddress: address,
- amount: amount,
- message: comment,
- payProUrl: paypro ? paypro.url : null,
- feePerKb: feePerKb,
- excludeUnconfirmedUtxos: $scope.currentSpendUnconfirmed ? false : true
- }, function(err, txp) {
- if (err) {
- self.setOngoingProcess();
- profileService.lockFC();
- return self.setSendError(err);
- }
+ requestTouchid(function(err) {
+ if (err) {
+ profileService.lockFC();
+ self.setOngoingProcess();
+ self.error = err;
+ $timeout(function() {
+ $scope.$digest();
+ }, 1);
+ return;
+ }
- if (!fc.canSign() && !fc.isPrivKeyExternal()) {
- $log.info('No signing proposal: No private key')
- self.setOngoingProcess();
- self.resetForm();
- txStatus.notify(txp, function() {
- return $scope.$emit('Local/TxProposalAction');
- });
- return;
- }
-
- self.signAndBroadcast(txp, function(err) {
- self.setOngoingProcess();
- self.resetForm();
+ getFee(function(err, feePerKb) {
+ if (err) $log.debug(err);
+ fc.sendTxProposal({
+ toAddress: address,
+ amount: amount,
+ message: comment,
+ payProUrl: paypro ? paypro.url : null,
+ feePerKb: feePerKb,
+ excludeUnconfirmedUtxos: $scope.currentSpendUnconfirmed ? false : true
+ }, function(err, txp) {
if (err) {
- self.error = err.message ? err.message : gettext('The payment was created but could not be completed. Please try again from home screen');
- $scope.$emit('Local/TxProposalAction');
- $timeout(function() {
- $scope.$digest();
- }, 1);
+ self.setOngoingProcess();
+ profileService.lockFC();
+ return self.setSendError(err);
}
+
+ if (!fc.canSign() && !fc.isPrivKeyExternal()) {
+ $log.info('No signing proposal: No private key')
+ self.setOngoingProcess();
+ self.resetForm();
+ txStatus.notify(txp, function() {
+ return $scope.$emit('Local/TxProposalAction');
+ });
+ return;
+ }
+
+ self.signAndBroadcast(txp, function(err) {
+ self.setOngoingProcess();
+ self.resetForm();
+ if (err) {
+ self.error = err.message ? err.message : gettext('The payment was created but could not be completed. Please try again from home screen');
+ $scope.$emit('Local/TxProposalAction');
+ $timeout(function() {
+ $scope.$digest();
+ }, 1);
+ }
+ });
});
});
});
@@ -1122,7 +1157,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
var fc = profileService.focusedClient;
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.btx = btx;
- $scope.settings = config;
+ $scope.settings = walletSettings;
$scope.color = fc.backgroundColor;
$scope.copayerId = fc.credentials.copayerId;
$scope.isShared = fc.credentials.n > 1;
diff --git a/src/js/init.js b/src/js/init.js
index b5c5d8066..75e2d5766 100644
--- a/src/js/init.js
+++ b/src/js/init.js
@@ -57,8 +57,6 @@ angular.element(document).ready(function() {
window.location = '#/preferences';
}, false);
-
-
setTimeout(function() {
navigator.splashscreen.hide();
}, 2000);
@@ -67,6 +65,11 @@ angular.element(document).ready(function() {
window.plugins.webintent.onNewIntent(handleBitcoinURI);
window.handleOpenURL = handleBitcoinURI;
+ window.plugins.touchid.isAvailable(
+ function(msg) { window.touchidAvailable = true; }, // success handler: TouchID available
+ function(msg) { window.touchidAvailable = false; } // error handler: no TouchID available
+ );
+
startAngular();
}, false);
} else {