From 43f24ee35e1b4e4d4a530305ee749bdcd9bb530e Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Mon, 14 Nov 2016 16:24:52 -0500 Subject: [PATCH 01/11] Abstract bitpayCardCredentials to appIdentity. Improve alert text on card removal. --- src/js/controllers/bitpayCardIntro.js | 6 +- src/js/controllers/preferencesBitpayCard.js | 4 +- src/js/services/appIdentityService.js | 34 +++++++++++ src/js/services/bitpayCardService.js | 66 ++++++--------------- src/js/services/storageService.js | 37 +++++++++--- 5 files changed, 87 insertions(+), 60 deletions(-) create mode 100644 src/js/services/appIdentityService.js diff --git a/src/js/controllers/bitpayCardIntro.js b/src/js/controllers/bitpayCardIntro.js index 63e5b7904..f2abecdf0 100644 --- a/src/js/controllers/bitpayCardIntro.js +++ b/src/js/controllers/bitpayCardIntro.js @@ -1,5 +1,5 @@ 'use strict'; -angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService) { +angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, appIdentityService) { var checkOtp = function(obj, cb) { if (obj.otp) { @@ -56,9 +56,9 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f }); }); } else { - bitpayCardService.getCredentials(function(err, credentials) { + appIdentityService.getIdentity(bitpayCardService.getEnvironment(), function(err, appIdentity) { if (err) popupService.showAlert(null, err); - else $log.info('BitPay Debit Card Credentials: Ok.'); + else $log.info('App identity: Ok.'); }); } }); diff --git a/src/js/controllers/preferencesBitpayCard.js b/src/js/controllers/preferencesBitpayCard.js index 3b7e0ac54..9813ad3a3 100644 --- a/src/js/controllers/preferencesBitpayCard.js +++ b/src/js/controllers/preferencesBitpayCard.js @@ -4,7 +4,9 @@ angular.module('copayApp.controllers').controller('preferencesBitpayCardControll function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog) { $scope.remove = function(card) { - var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card account from this device?'); + var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card account ({{lastFourDigits}}) from this device?', { + lastFourDigits: card.lastFourDigits + }); popupService.showConfirm(null, msg, null, null, function(res) { if (res) remove(card); }); diff --git a/src/js/services/appIdentityService.js b/src/js/services/appIdentityService.js new file mode 100644 index 000000000..6df3f136d --- /dev/null +++ b/src/js/services/appIdentityService.js @@ -0,0 +1,34 @@ +'use strict'; + +angular.module('copayApp.services').factory('appIdentityService', function($log, lodash, storageService, bitauthService) { + var root = {}; + + root.getIdentity = function(network, cb) { + var pubkey, sin, isNew; + storageService.getAppIdentity(network, function(err, data) { + if (err) return cb(err); + if (lodash.isString(data)) { + data = JSON.parse(data); + } + var appIdentity = data || {}; + if (lodash.isEmpty(appIdentity) || (appIdentity && !appIdentity.priv)) { + isNew = true; + appIdentity = bitauthService.generateSin(); + } + try { + pubkey = bitauthService.getPublicKeyFromPrivateKey(appIdentity.priv); + sin = bitauthService.getSinFromPublicKey(pubkey); + if (isNew) + storageService.setAppIdentity(network, JSON.stringify(appIdentity), function(err) {}); + } + catch (e) { + $log.error(e); + return cb(e); + }; + return cb(null, appIdentity); + }); + }; + + return root; + +}); \ No newline at end of file diff --git a/src/js/services/bitpayCardService.js b/src/js/services/bitpayCardService.js index bd671c02d..4ae042f6c 100644 --- a/src/js/services/bitpayCardService.js +++ b/src/js/services/bitpayCardService.js @@ -1,36 +1,10 @@ 'use strict'; -angular.module('copayApp.services').factory('bitpayCardService', function($http, $log, lodash, storageService, bitauthService, platformInfo, moment) { +angular.module('copayApp.services').factory('bitpayCardService', function($http, $log, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService) { var root = {}; var BITPAY_CARD_NETWORK = 'livenet'; var BITPAY_CARD_API_URL = BITPAY_CARD_NETWORK == 'livenet' ? 'https://bitpay.com' : 'https://test.bitpay.com'; - var _getCredentials = function(cb) { - var pubkey, sin, isNew; - storageService.getBitpayCardCredentials(BITPAY_CARD_NETWORK, function(err, data) { - if (err) return cb(err); - if (lodash.isString(data)) { - data = JSON.parse(data); - } - var credentials = data || {}; - if (lodash.isEmpty(credentials) || (credentials && !credentials.priv)) { - isNew = true; - credentials = bitauthService.generateSin(); - } - try { - pubkey = bitauthService.getPublicKeyFromPrivateKey(credentials.priv); - sin = bitauthService.getSinFromPublicKey(pubkey); - if (isNew) - storageService.setBitpayCardCredentials(BITPAY_CARD_NETWORK, JSON.stringify(credentials), function(err) {}); - } - catch (e) { - $log.error(e); - return cb(e); - }; - return cb(null, credentials); - }); - }; - var _setError = function(msg, e) { $log.error(msg); var error = e.data ? e.data.error : msg; @@ -47,25 +21,25 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, }; }; - var _post = function(endpoint, json, credentials) { + var _post = function(endpoint, json, appIdentity) { var dataToSign = BITPAY_CARD_API_URL + endpoint + JSON.stringify(json); - var signedData = bitauthService.sign(dataToSign, credentials.priv); + var signedData = bitauthService.sign(dataToSign, appIdentity.priv); return { method: 'POST', url: BITPAY_CARD_API_URL + endpoint, headers: { 'content-type': 'application/json', - 'x-identity': credentials.pub, + 'x-identity': appIdentity.pub, 'x-signature': signedData }, data: json }; }; - var _postAuth = function(endpoint, json, credentials) { - json['params'].signature = bitauthService.sign(JSON.stringify(json.params), credentials.priv); - json['params'].pubkey = credentials.pub; + var _postAuth = function(endpoint, json, appIdentity) { + json['params'].signature = bitauthService.sign(JSON.stringify(json.params), appIdentity.priv); + json['params'].pubkey = appIdentity.pub; json['params'] = JSON.stringify(json.params); var ret = { @@ -81,12 +55,12 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, return ret; }; - var _afterBitAuthSuccess = function(token, obj, credentials, cb) { + var _afterBitAuthSuccess = function(token, obj, appIdentity, cb) { var json = { method: 'getDebitCards' }; // Get Debit Cards - $http(_post('/api/v2/' + token, json, credentials)).then(function(data) { + $http(_post('/api/v2/' + token, json, appIdentity)).then(function(data) { if (data && data.data.error) return cb(data.data.error); $log.info('BitPay Get Debit Cards: SUCCESS'); return cb(data.data.error, {token: token, cards: data.data.data, email: obj.email}); @@ -129,12 +103,6 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, return BITPAY_CARD_NETWORK; }; - root.getCredentials = function(cb) { - _getCredentials(function(err, credentials) { - return cb(err, credentials); - }); - }; - root.bitAuthPair = function(obj, cb) { var deviceName = 'Unknow device'; if (platformInfo.isNW) { @@ -151,12 +119,12 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, code: obj.otp } }; - _getCredentials(function(err, credentials) { + appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { if (err) return cb(err); - $http(_postAuth('/api/v2/', json, credentials)).then(function(data) { + $http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) { if (data && data.data.error) return cb(data.data.error); $log.info('BitPay Card BitAuth Create Token: SUCCESS'); - _afterBitAuthSuccess(data.data.data, obj, credentials, cb); + _afterBitAuthSuccess(data.data.data, obj, appIdentity, cb); }, function(data) { return cb(_setError('BitPay Card Error Create Token: BitAuth', data)); }); @@ -170,14 +138,14 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, method: 'getInvoiceHistory', params: JSON.stringify(params) }; - _getCredentials(function(err, credentials) { + appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { if (err) return cb(err); root.getBitpayDebitCards(function(err, data) { if (err) return cb(err); var card = lodash.find(data, {id : cardId}); if (!card) return cb(_setError('Not card found')); // Get invoices - $http(_post('/api/v2/' + card.token, json, credentials)).then(function(data) { + $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { $log.info('BitPay Get Invoices: SUCCESS'); invoices = data.data.data || []; if (lodash.isEmpty(invoices)) $log.info('No invoices'); @@ -186,7 +154,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, params: JSON.stringify(params) }; // Get transactions list - $http(_post('/api/v2/' + card.token, json, credentials)).then(function(data) { + $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { $log.info('BitPay Get Transactions: SUCCESS'); transactions = data.data.data || {}; transactions['txs'] = _processTransactions(invoices, transactions.transactionList); @@ -207,13 +175,13 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, method: 'generateTopUpInvoice', params: JSON.stringify(params) }; - _getCredentials(function(err, credentials) { + appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { if (err) return cb(err); root.getBitpayDebitCards(function(err, data) { if (err) return cb(err); var card = lodash.find(data, {id : cardId}); if (!card) return cb(_setError('Not card found')); - $http(_post('/api/v2/' + card.token, json, credentials)).then(function(data) { + $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { $log.info('BitPay TopUp: SUCCESS'); if(data.data.error) { return cb(data.data.error); diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 17586f609..340b25eb5 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -95,7 +95,8 @@ angular.module('copayApp.services') // Upgraders are executed in numerical order per the '##_' object key prefix. // var _upgraders = { - '00_bitpayDebitCards' : _upgrade_bitpayDebitCards // 2016-11: Upgrade bitpayDebitCards-x to bitpayAccounts-x + '00_bitpayDebitCards' : _upgrade_bitpayDebitCards, // 2016-11: Upgrade bitpayDebitCards-x to bitpayAccounts-x + '01_bitpayCardCredentials' : _upgrade_bitpayCardCredentials // 2016-11: Upgrade bitpayCardCredentials-x to appIdentity-x }; function _upgrade_bitpayDebitCards(key, network, cb) { @@ -119,6 +120,28 @@ angular.module('copayApp.services') } }); }; + + function _upgrade_bitpayCardCredentials(key, network, cb) { + key += '-' + network; + storage.get(key, function(err, data) { + if (err) return cb(err); + if (data != null) { + // Needs upgrade + if (lodash.isString(data)) { + data = JSON.parse(data); + } + data = data || {}; + root.setAppIdentity(network, data, function(err) { + if (err) return cb(err); + storage.remove(key, function() { + cb(null, 'replaced with \'appIdentity\''); + }); + }); + } else { + cb(); + } + }); + }; // //////////////////////////////////////////////////////////////////////////// @@ -488,16 +511,16 @@ angular.module('copayApp.services') }); }; - root.setBitpayCardCredentials = function(network, data, cb) { - storage.set('bitpayCardCredentials-' + network, data, cb); + root.setAppIdentity = function(network, data, cb) { + storage.set('appIdentity-' + network, data, cb); }; - root.getBitpayCardCredentials = function(network, cb) { - storage.get('bitpayCardCredentials-' + network, cb); + root.getAppIdentity = function(network, cb) { + storage.get('appIdentity-' + network, cb); }; - root.removeBitpayCardCredentials = function(network, cb) { - storage.remove('bitpayCardCredentials-' + network, cb); + root.removeAppIdentity = function(network, cb) { + storage.remove('appIdentity-' + network, cb); }; root.removeAllWalletData = function(walletId, cb) { From 90f908709a44b65fc25d3c572454ded07d73aaa3 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Mon, 14 Nov 2016 16:58:54 -0500 Subject: [PATCH 02/11] Imporved card remove confirm message. --- src/js/controllers/preferencesBitpayCard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/preferencesBitpayCard.js b/src/js/controllers/preferencesBitpayCard.js index 9813ad3a3..a23e7b995 100644 --- a/src/js/controllers/preferencesBitpayCard.js +++ b/src/js/controllers/preferencesBitpayCard.js @@ -4,7 +4,7 @@ angular.module('copayApp.controllers').controller('preferencesBitpayCardControll function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog) { $scope.remove = function(card) { - var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card account ({{lastFourDigits}}) from this device?', { + var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card ({{lastFourDigits}}) from this device?', { lastFourDigits: card.lastFourDigits }); popupService.showConfirm(null, msg, null, null, function(res) { From 4a6499d528c42c96bc4170dcfea7aea1cd133825 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Fri, 23 Dec 2016 11:37:19 -0500 Subject: [PATCH 03/11] WIP - re-factor debit card to create a reusable bitpayService. --- src/js/controllers/bitpayCard.js | 4 +- src/js/controllers/bitpayCardIntro.js | 80 ++++------ src/js/services/bitpayCardService.js | 128 +++------------- src/js/services/bitpayService.js | 193 ++++++++++++++++++++++++ src/js/services/incomingData.js | 19 ++- src/js/services/storageService.js | 209 ++++++++++++++++++++++++-- 6 files changed, 464 insertions(+), 169 deletions(-) create mode 100644 src/js/services/bitpayService.js diff --git a/src/js/controllers/bitpayCard.js b/src/js/controllers/bitpayCard.js index 40187b4ef..0c8a85bd3 100644 --- a/src/js/controllers/bitpayCard.js +++ b/src/js/controllers/bitpayCard.js @@ -1,13 +1,13 @@ 'use strict'; -angular.module('copayApp.controllers').controller('bitpayCardController', function($scope, $timeout, $log, $state, lodash, bitpayCardService, moment, popupService, gettextCatalog, $ionicHistory) { +angular.module('copayApp.controllers').controller('bitpayCardController', function($scope, $timeout, $log, $state, lodash, bitpayCardService, moment, popupService, gettextCatalog, $ionicHistory, bitpayService) { var self = this; var runningBalance; $scope.dateRange = { value: 'last30Days' }; - $scope.network = bitpayCardService.getEnvironment(); + $scope.network = bitpayService.getEnvironment(); var updateHistoryFromCache = function(cb) { bitpayCardService.getBitpayDebitCardsHistory($scope.cardId, function(err, data) { diff --git a/src/js/controllers/bitpayCardIntro.js b/src/js/controllers/bitpayCardIntro.js index 49f29ffe8..59eb0a29b 100644 --- a/src/js/controllers/bitpayCardIntro.js +++ b/src/js/controllers/bitpayCardIntro.js @@ -1,64 +1,48 @@ 'use strict'; -angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, appIdentityService) { - - var checkOtp = function(obj, cb) { - if (obj.otp) { - var msg = gettextCatalog.getString('Enter Two Factor for BitPay Card'); - popupService.showPrompt(null, msg, null, function(res) { - cb(res); - }); - } else { - cb(); - } - }; +angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, appIdentityService, bitpayService) { $scope.$on("$ionicView.beforeEnter", function(event, data) { - if (data.stateParams && data.stateParams.secret) { - var obj = { + var pairData = { secret: data.stateParams.secret, email: data.stateParams.email, otp: data.stateParams.otp }; - checkOtp(obj, function(otp) { - obj.otp = otp; - bitpayCardService.bitAuthPair(obj, function(err, data) { - if (err) { - popupService.showAlert(gettextCatalog.getString('Error'), err); - return; - } - var title = gettextCatalog.getString('Add BitPay Card Account?'); - var msg = gettextCatalog.getString('Would you like to add this account ({{email}}) to your wallet?', { - email: obj.email - }); - var ok = gettextCatalog.getString('Add Account'); - var cancel = gettextCatalog.getString('Go back'); - popupService.showConfirm(title, msg, ok, cancel, function(res) { - if (res) { - // Set flag for nextStep - storageService.setNextStep('BitpayCard', 'true', function(err) {}); - // Save data - bitpayCardService.setBitpayDebitCards(data, function(err) { - if (err) return; - $ionicHistory.nextViewOptions({ - disableAnimate: true - }); - $state.go('tabs.home').then(function() { - if (data.cards[0]) { - $state.transitionTo('tabs.bitpayCard', { - id: data.cards[0].id - }); - } - }); - }); + var pairingReason = gettextCatalog.getString('add your BitPay Visa® card(s)'); + bitpayService.pair(pairData, pairingReason, function(err, paired, apiContext) { + if (err) { + popupService.showAlert(gettextCatalog.getString('Error'), err); + return; + } + if (paired) { + bitpayCardService.fetchBitpayDebitCards(apiContext, function(err, data) { + if (err) { + popupService.showAlert(gettextCatalog.getString('Error'), err); + return; } + // Set flag for nextStep + storageService.setNextStep('BitpayCard', 'true', function(err) {}); + // Save data + bitpayCardService.setBitpayDebitCards(data, function(err) { + if (err) return; + $ionicHistory.nextViewOptions({ + disableAnimate: true + }); + $state.go('tabs.home').then(function() { + if (data.cards[0]) { + $state.transitionTo('tabs.bitpayCard', { + id: data.cards[0].id + }); + } + }); + }); }); - }); + } }); } else { - appIdentityService.getIdentity(bitpayCardService.getEnvironment(), function(err, appIdentity) { + appIdentityService.getIdentity(bitpayService.getEnvironment(), function(err, appIdentity) { if (err) popupService.showAlert(null, err); - else $log.info('App identity: Ok.'); + else $log.info('App identity: OK'); }); } }); diff --git a/src/js/services/bitpayCardService.js b/src/js/services/bitpayCardService.js index bf57b805a..ce8e787c7 100644 --- a/src/js/services/bitpayCardService.js +++ b/src/js/services/bitpayCardService.js @@ -1,9 +1,7 @@ 'use strict'; -angular.module('copayApp.services').factory('bitpayCardService', function($http, $log, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService) { +angular.module('copayApp.services').factory('bitpayCardService', function($log, $rootScope, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService, bitpayService) { var root = {}; - var BITPAY_CARD_NETWORK = 'livenet'; - var BITPAY_CARD_API_URL = BITPAY_CARD_NETWORK == 'livenet' ? 'https://bitpay.com' : 'https://test.bitpay.com'; var _setError = function(msg, e) { $log.error(msg); @@ -11,64 +9,6 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, return error; }; - var _get = function(endpoint) { - return { - method: 'GET', - url: BITPAY_CARD_API_URL + endpoint, - headers: { - 'content-type': 'application/json' - } - }; - }; - - var _post = function(endpoint, json, appIdentity) { - var dataToSign = BITPAY_CARD_API_URL + endpoint + JSON.stringify(json); - var signedData = bitauthService.sign(dataToSign, appIdentity.priv); - - return { - method: 'POST', - url: BITPAY_CARD_API_URL + endpoint, - headers: { - 'content-type': 'application/json', - 'x-identity': appIdentity.pub, - 'x-signature': signedData - }, - data: json - }; - }; - - var _postAuth = function(endpoint, json, appIdentity) { - json['params'].signature = bitauthService.sign(JSON.stringify(json.params), appIdentity.priv); - json['params'].pubkey = appIdentity.pub; - json['params'] = JSON.stringify(json.params); - - var ret = { - method: 'POST', - url: BITPAY_CARD_API_URL + endpoint, - headers: { - 'content-type': 'application/json' - }, - data: json - }; - - $log.debug('post auth:' + JSON.stringify(ret)); - return ret; - }; - - var _afterBitAuthSuccess = function(token, obj, appIdentity, cb) { - var json = { - method: 'getDebitCards' - }; - // Get Debit Cards - $http(_post('/api/v2/' + token, json, appIdentity)).then(function(data) { - if (data && data.data.error) return cb(data.data.error); - $log.info('BitPay Get Debit Cards: SUCCESS'); - return cb(data.data.error, {token: token, cards: data.data.data, email: obj.email}); - }, function(data) { - return cb(_setError('BitPay Card Error: Get Debit Cards', data)); - }); - }; - var _processTransactions = function(invoices, history) { invoices = invoices || []; for (var i = 0; i < invoices.length; i++) { @@ -99,35 +39,17 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, return history; }; - root.getEnvironment = function() { - return BITPAY_CARD_NETWORK; - }; - - root.bitAuthPair = function(obj, cb) { - var deviceName = 'Unknown device'; - if (platformInfo.isNW) { - deviceName = require('os').platform(); - } else if (platformInfo.isCordova) { - deviceName = device.model; - } + root.fetchBitpayDebitCards = function(apiContext, cb) { var json = { - method: 'createToken', - params: { - secret: obj.secret, - version: 2, - deviceName: deviceName, - code: obj.otp - } + method: 'getDebitCards' }; - appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { - if (err) return cb(err); - $http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) { - if (data && data.data.error) return cb(data.data.error); - $log.info('BitPay Card BitAuth Create Token: SUCCESS'); - _afterBitAuthSuccess(data.data.data, obj, appIdentity, cb); - }, function(data) { - return cb(_setError('BitPay Card Error Create Token: BitAuth', data)); - }); + // Get Debit Cards + bitpayService.post('/api/v2/' + apiContext.token, json, function(data) { + if (data && data.data.error) return cb(data.data.error); + $log.info('BitPay Get Debit Cards: SUCCESS'); + return cb(data.data.error, {token: apiContext.token, cards: data.data.data, email: apiContext.pairData.email}); + }, function(data) { + return cb(_setError('BitPay Card Error: Get Debit Cards', data)); }); }; @@ -138,14 +60,14 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, method: 'getInvoiceHistory', params: JSON.stringify(params) }; - appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { + appIdentityService.getIdentity(bitpayService.getEnvironment(), function(err, appIdentity) { if (err) return cb(err); root.getBitpayDebitCards(function(err, data) { if (err) return cb(err); var card = lodash.find(data, {id : cardId}); if (!card) return cb(_setError('Card not found')); // Get invoices - $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { + bitpayService.post('/api/v2/' + card.token, json, function(data) { $log.info('BitPay Get Invoices: SUCCESS'); invoices = data.data.data || []; if (lodash.isEmpty(invoices)) $log.info('No invoices'); @@ -154,7 +76,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, params: JSON.stringify(params) }; // Get transactions list - $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { + bitpayService.post('/api/v2/' + card.token, json, function(data) { $log.info('BitPay Get Transactions: SUCCESS'); transactions = data.data.data || {}; transactions['txs'] = _processTransactions(invoices, transactions.transactionList); @@ -163,7 +85,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, return cb(_setError('BitPay Card Error: Get Transactions', data)); }); }, function(data) { - return cb(_setError('BitPay Card Error: Get Invoices', data)); + return cb(_setError('BitPay Card Error: Get Invoices', data)); }); }); }); @@ -175,13 +97,13 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, method: 'generateTopUpInvoice', params: JSON.stringify(params) }; - appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { + appIdentityService.getIdentity(bitpayService.getEnvironment(), function(err, appIdentity) { if (err) return cb(err); root.getBitpayDebitCards(function(err, data) { if (err) return cb(err); var card = lodash.find(data, {id : cardId}); if (!card) return cb(_setError('Card not found')); - $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { + bitpayService.post('/api/v2/' + card.token, json, function(data) { $log.info('BitPay TopUp: SUCCESS'); if(data.data.error) { return cb(data.data.error); @@ -196,7 +118,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, }; root.getInvoice = function(id, cb) { - $http(_get('/invoices/' + id)).then(function(data) { + bitpayService.get('/invoices/' + id, function(data) { $log.info('BitPay Get Invoice: SUCCESS'); return cb(data.data.error, data.data.data); }, function(data) { @@ -205,7 +127,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, }; root.getBitpayDebitCards = function(cb) { - storageService.getBitpayDebitCards(BITPAY_CARD_NETWORK, function(err, data) { + storageService.getBitpayDebitCards(bitpayService.getEnvironment(), function(err, data) { if (err) return cb(err); if (lodash.isString(data)) { data = JSON.parse(data); @@ -217,14 +139,14 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, root.setBitpayDebitCards = function(data, cb) { data = JSON.stringify(data); - storageService.setBitpayDebitCards(BITPAY_CARD_NETWORK, data, function(err) { + storageService.setBitpayDebitCards(bitpayService.getEnvironment(), data, function(err) { if (err) return cb(err); return cb(); }); }; root.getBitpayDebitCardsHistory = function(cardId, cb) { - storageService.getBitpayDebitCardsHistory(BITPAY_CARD_NETWORK, function(err, data) { + storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment(), function(err, data) { if (err) return cb(err); if (lodash.isString(data)) { data = JSON.parse(data); @@ -236,7 +158,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, }; root.setBitpayDebitCardsHistory = function(cardId, data, opts, cb) { - storageService.getBitpayDebitCardsHistory(BITPAY_CARD_NETWORK, function(err, oldData) { + storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment(), function(err, oldData) { if (lodash.isString(oldData)) { oldData = JSON.parse(oldData); } @@ -250,19 +172,19 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, } inv = JSON.stringify(inv); - storageService.setBitpayDebitCardsHistory(BITPAY_CARD_NETWORK, inv, function(err) { + storageService.setBitpayDebitCardsHistory(bitpayService.getEnvironment(), inv, function(err) { return cb(err); }); }); }; root.remove = function(card, cb) { - storageService.removeBitpayDebitCard(BITPAY_CARD_NETWORK, card, function(err) { + storageService.removeBitpayDebitCard(bitpayService.getEnvironment(), card, function(err) { if (err) { $log.error('Error removing BitPay debit card: ' + err); // Continue, try to remove/cleanup card history } - storageService.removeBitpayDebitCardHistory(BITPAY_CARD_NETWORK, card, function(err) { + storageService.removeBitpayDebitCardHistory(bitpayService.getEnvironment(), card, function(err) { if (err) { $log.error('Error removing BitPay debit card transaction history: ' + err); return cb(err); @@ -274,7 +196,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http, }; root.getRates = function(currency, cb) { - $http(_get('/rates/' + currency)).then(function(data) { + bitpayService.get('/rates/' + currency, function(data) { $log.info('BitPay Get Rates: SUCCESS'); return cb(data.data.error, data.data.data); }, function(data) { diff --git a/src/js/services/bitpayService.js b/src/js/services/bitpayService.js new file mode 100644 index 000000000..12c3177e6 --- /dev/null +++ b/src/js/services/bitpayService.js @@ -0,0 +1,193 @@ +'use strict'; + +angular.module('copayApp.services').factory('bitpayService', function($log, $http, platformInfo, appIdentityService, bitauthService, storageService, gettextCatalog, popupService) { + var root = {}; + + var NETWORK = 'livenet'; + var BITPAY_API_URL = NETWORK == 'livenet' ? 'https://bitpay.com' : 'https://test.bitpay.com'; + + root.getEnvironment = function() { + return NETWORK; + }; + + /* + * Pair this app with the bitpay server using the specified pairing data. + * An app identity will be created if one does not already exist. + * Pairing data is provided by an input URI provided by the bitpay server. + * + * pairData - data needed to complete the pairing process + * { + * secret: shared pairing secret + * email: email address associated with bitpay account + * otp: two-factor one-time use password + * } + * + * pairingReason - text string to be embedded into popup message. If `null` then the reason + * message is not shown to the UI. + * "To {{reason}} you must pair this app with your BitPay account ({{email}})." + * + * cb - callback after completion + * callback(err, paired, apiContext) + * + * err - something unexpected happened which prevented the pairing + * + * paired - boolean indicating whether the pairing was compledted by the user + * + * apiContext - the context needed for making future api calls + * { + * token: api token for use in future calls + * pairData: the input pair data + * appIdentity: the identity of this app + * } + */ + root.pair = function(pairData, pairingReason, cb) { + checkOtp(pairData, function(otp) { + pairData.otp = otp; + var deviceName = 'Unknown device'; + if (platformInfo.isNW) { + deviceName = require('os').platform(); + } else if (platformInfo.isCordova) { + deviceName = device.model; + } + var json = { + method: 'createToken', + params: { + secret: pairData.secret, + version: 2, + deviceName: deviceName, + code: pairData.otp + } + }; + appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { + if (err) return cb(err); + $http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) { + if (data && data.data.error) return cb(data.data.error); + $log.info('BitPay service BitAuth create token: SUCCESS'); + var title = gettextCatalog.getString('Add BitPay Account?'); + var msgDetail = 'Add this BitPay account ({{email}})?'; + if (pairingReason) { + msgDetail = 'To {{reason}} you must first add your BitPay account.

{{email}}'; + } + var msg = gettextCatalog.getString(msgDetail, { + reason: pairingReason, + email: pairData.email + }); + var ok = gettextCatalog.getString('Add Account'); + var cancel = gettextCatalog.getString('Go back'); + popupService.showConfirm(title, msg, ok, cancel, function(res) { + if (res) { + var acctData = { + token: data.data.data, + email: pairData.email + }; + setBitpayAccount(acctData, function(err) { + if (err) return cb(err); + return cb(null, true, { + token: acctData.token, + pairData: pairData, + appIdentity: appIdentity + }); + }); + } else { + $log.info('User cancelled BitPay pairing process'); + return cb(null, false); + } + }); + }, function(data) { + return cb(_setError('BitPay service BitAuth create token: ERROR ', data)); + }); + }); + }); + }; + + root.get = function(endpoint, successCallback, errorCallback) { + $http(_get(endpoint)).then(function(data) { + successCallback(data); + }, function(data) { + errorCallback(data); + }); + }; + + root.post = function(endpoint, json, successCallback, errorCallback) { + appIdentityService.getIdentity(NETWORK, function(err, appIdentity) { + if (err) return errorCallback(err); + $http(_post(endpoint, json, appIdentity)).then(function(data) { + successCallback(data); + }, function(data) { + errorCallback(data); + }); + }); + }; + + var checkOtp = function(pairData, cb) { + if (pairData.otp) { + var msg = gettextCatalog.getString('Enter Two Factor for your BitPay account'); + popupService.showPrompt(null, msg, null, function(res) { + cb(res); + }); + } else { + cb(); + } + }; + + var setBitpayAccount = function(accountData, cb) { + var data = JSON.stringify(accountData); + storageService.setBitpayAccount(root.getEnvironment(), data, function(err) { + if (err) return cb(err); + return cb(); + }); + }; + + var _get = function(endpoint) { + return { + method: 'GET', + url: BITPAY_API_URL + endpoint, + headers: { + 'content-type': 'application/json' + } + }; + }; + + var _post = function(endpoint, json, appIdentity) { + var dataToSign = BITPAY_API_URL + endpoint + JSON.stringify(json); + var signedData = bitauthService.sign(dataToSign, appIdentity.priv); + + return { + method: 'POST', + url: BITPAY_API_URL + endpoint, + headers: { + 'content-type': 'application/json', + 'x-identity': appIdentity.pub, + 'x-signature': signedData + }, + data: json + }; + }; + + var _postAuth = function(endpoint, json, appIdentity) { + json['params'].signature = bitauthService.sign(JSON.stringify(json.params), appIdentity.priv); + json['params'].pubkey = appIdentity.pub; + json['params'] = JSON.stringify(json.params); + + var ret = { + method: 'POST', + url: BITPAY_API_URL + endpoint, + headers: { + 'content-type': 'application/json' + }, + data: json + }; + + $log.debug('post auth:' + JSON.stringify(ret)); + return ret; + }; + + var _setError = function(msg, e) { + $log.error(msg); + var error = (e && e.data && e.data.error) ? e.data.error : msg; + return error; + }; + + return root; + +}); diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 7600dcb79..1d40a3032 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -130,20 +130,27 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat url: data }); - // BitPayCard Authentication + // BitPay Authentication } else if (data && data.indexOf($window.appConfig.name + '://') === 0) { var secret = getParameterByName('secret', data); var email = getParameterByName('email', data); var otp = getParameterByName('otp', data); + var reason = getParameterByName('r', data); + $state.go('tabs.home', {}, { 'reload': true, 'notify': $state.current.name == 'tabs.home' ? false : true }).then(function() { - $state.transitionTo('tabs.bitpayCardIntro', { - secret: secret, - email: email, - otp: otp - }); + switch (reason) { + default: + case '0': /* For BitPay card binding */ + $state.transitionTo('tabs.bitpayCardIntro', { + secret: secret, + email: email, + otp: otp + }); + break; + } }); return true; diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index bd1d41fd8..3643dc724 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -78,6 +78,14 @@ angular.module('copayApp.services') // // UPGRADING STORAGE // + // TODO: prior to upgrading storage a backup of all storage should be captured and made available + // for export for recovery/debugging. + // + // Upgraders are executed in numerical order per the '##_' object key prefix. Each upgrader will run. + // Each upgrader should detect storage configuraton and fail-safe; no upgrader should damage the ability + // of another to function properly (in order). Each upgrader should upgrade storage incrementally; storage + // upgrade is not complete until all upgraders have run. + // // 1. Write a function to upgrade the desired storage key(s). The function should have the protocol: // // _upgrade_x(key, network, cb), where: @@ -91,12 +99,15 @@ angular.module('copayApp.services') // when a storage key is involved in multiple upgraders as well as predicte the order in which upgrades // occur the `_upgrader` object key should be prefixed with '##_' (e.g., '01_') to create a unique and // sortable name. This format is interpreted by the _upgrade() function. - // - // Upgraders are executed in numerical order per the '##_' object key prefix. + // + // 3. Any dependency functions called by upgraders should be copied/factored out and remain unchanged as + // long as the upgrader remains in effect. By convention the dependency function is prefixed by '##_' to + // match the upgrader key. // var _upgraders = { '00_bitpayDebitCards' : _upgrade_bitpayDebitCards, // 2016-11: Upgrade bitpayDebitCards-x to bitpayAccounts-x - '01_bitpayCardCredentials' : _upgrade_bitpayCardCredentials // 2016-11: Upgrade bitpayCardCredentials-x to appIdentity-x + '01_bitpayCardCredentials' : _upgrade_bitpayCardCredentials, // 2016-11: Upgrade bitpayCardCredentials-x to appIdentity-x + '02_bitpayAccounts' : _upgrade_bitpayAccounts // 2016-12: Reorganize the object }; function _upgrade_bitpayDebitCards(key, network, cb) { @@ -109,7 +120,7 @@ angular.module('copayApp.services') data = JSON.parse(data); } data = data || {}; - root.setBitpayDebitCards(network, data, function(err) { + _00_setBitpayDebitCards(network, data, function(err) { if (err) return cb(err); storage.remove(key, function() { cb(null, 'replaced with \'bitpayAccounts\''); @@ -131,7 +142,7 @@ angular.module('copayApp.services') data = JSON.parse(data); } data = data || {}; - root.setAppIdentity(network, data, function(err) { + _01_setAppIdentity(network, data, function(err) { if (err) return cb(err); storage.remove(key, function() { cb(null, 'replaced with \'appIdentity\''); @@ -142,6 +153,105 @@ angular.module('copayApp.services') } }); }; + + function _upgrade_bitpayAccounts(key, network, cb) { + key += '-' + network; + storage.get(key, function(err, data) { + if (err) return cb(err); + if (lodash.isString(data)) { + data = JSON.parse(data); + } + data = data || {}; + var upgraded = ''; + Object.keys(data).forEach(function(key) { + // Keys are account emails + if (!data[key]['bitpayApi-' + network].token) { + // Needs upgrade + upgraded += ' ' + key; + var acctData = { + token: data[key]['bitpayDebitCards-' + network].token, + email: key + }; + _02_setBitpayAccount(network, data, function(err) { + if (err) return cb(err); + + _02_setBitpayDebitCards(network, data[key]['bitpayDebitCards-' + network], function(err) { + if (err) return cb(err); + }); + }); + } + }); + if (upgraded.length > 0) { + cb(null, 'upgraded \'bitpayAccounts\':' + upgraded); + } else { + cb(); + } + }); + }; + // + //////////////////////////////////////////////////////////////////////////// + // + // UPGRADER DEPENDENCIES + // These functions remain as long as the upgrader remains in effect. + // + var _00_setBitpayDebitCards = function(network, data, cb) { + if (lodash.isString(data)) { + data = JSON.parse(data); + } + data = data || {}; + if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set'); + storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + if (err) return cb(err); + if (lodash.isString(bitpayAccounts)) { + bitpayAccounts = JSON.parse(bitpayAccounts); + } + bitpayAccounts = bitpayAccounts || {}; + bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; + bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data; + storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + }); + }; + + var _01_setAppIdentity = function(network, data, cb) { + storage.set('appIdentity-' + network, data, cb); + }; + + var _02_setBitpayAccount = function(network, data, cb) { + if (lodash.isString(data)) { + data = JSON.parse(data); + } + data = data || {}; + if (lodash.isEmpty(data) || !data.email) return cb('No account to set'); + storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + if (err) return cb(err); + if (lodash.isString(bitpayAccounts)) { + bitpayAccounts = JSON.parse(bitpayAccounts); + } + bitpayAccounts = bitpayAccounts || {}; + bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; + bitpayAccounts[data.email]['bitpayApi-' + network] = bitpayAccounts[data.email]['bitpayApi-' + network] || {}; + bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token; + storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + }); + }; + + var _02_setBitpayDebitCards = function(network, data, cb) { + if (lodash.isString(data)) { + data = JSON.parse(data); + } + data = data || {}; + if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set'); + storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + if (err) return cb(err); + if (lodash.isString(bitpayAccounts)) { + bitpayAccounts = JSON.parse(bitpayAccounts); + } + bitpayAccounts = bitpayAccounts || {}; + bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; + bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards; + storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + }); + }; // //////////////////////////////////////////////////////////////////////////// @@ -467,6 +577,16 @@ angular.module('copayApp.services') }); }; + // data: { + // cards: [ + // eid: card id + // id: card id + // lastFourDigits: card number + // token: card token + // ] + // email: account email + // token: account token + // } root.setBitpayDebitCards = function(network, data, cb) { if (lodash.isString(data)) { data = JSON.parse(data); @@ -480,11 +600,19 @@ angular.module('copayApp.services') } bitpayAccounts = bitpayAccounts || {}; bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; - bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data; + bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards; storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); }); }; + // cb(err, cards) + // cards: [ + // eid: card id + // id: card id + // lastFourDigits: card number + // token: card token + // email: account email + // ] root.getBitpayDebitCards = function(network, cb) { storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { if (lodash.isString(bitpayAccounts)) { @@ -494,7 +622,7 @@ angular.module('copayApp.services') var cards = []; Object.keys(bitpayAccounts).forEach(function(email) { // For the UI, add the account email to the card object. - var acctCards = bitpayAccounts[email]['bitpayDebitCards-' + network].cards; + var acctCards = bitpayAccounts[email]['bitpayDebitCards-' + network] || []; for (var i = 0; i < acctCards.length; i++) { acctCards[i].email = email; } @@ -504,6 +632,12 @@ angular.module('copayApp.services') }); }; + // card: { + // eid: card id + // id: card id + // lastFourDigits: card number + // token: card token + // } root.removeBitpayDebitCard = function(network, card, cb) { if (lodash.isString(card)) { card = JSON.parse(card); @@ -516,12 +650,14 @@ angular.module('copayApp.services') bitpayAccounts = JSON.parse(bitpayAccounts); } bitpayAccounts = bitpayAccounts || {}; - Object.keys(bitpayAccounts).forEach(function(userId) { - var data = bitpayAccounts[userId]['bitpayDebitCards-' + network]; - var newCards = lodash.reject(data.cards, { + Object.keys(bitpayAccounts).forEach(function(email) { + var data = bitpayAccounts[email]['bitpayDebitCards-' + network]; + var newCards = lodash.reject(data, { 'eid': card.eid }); + data = {}; data.cards = newCards; + data.email = email; root.setBitpayDebitCards(network, data, function(err) { if (err) cb(err); // If there are no more cards in storage then re-enable the next step entry. @@ -538,6 +674,59 @@ angular.module('copayApp.services') }); }; + // data: { + // email: account email + // token: account token + // } + root.setBitpayAccount = function(network, data, cb) { + if (lodash.isString(data)) { + data = JSON.parse(data); + } + data = data || {}; + if (lodash.isEmpty(data) || !data.email) return cb('No account to set'); + storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + if (err) return cb(err); + if (lodash.isString(bitpayAccounts)) { + bitpayAccounts = JSON.parse(bitpayAccounts); + } + bitpayAccounts = bitpayAccounts || {}; + bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; + bitpayAccounts[data.email]['bitpayApi-' + network] = bitpayAccounts[data.email]['bitpayApi-' + network] || {}; + bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token; + storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + }); + }; + + // cb(err, accounts) + // accounts: { + // email_1: { + // bitpayApi-: { + // token: account token + // } + // bitpayDebitCards-: { + // + // } + // } + // ... + // email_n: { + // bitpayApi-: { + // token: account token + // } + // bitpayDebitCards-: { + // + // } + // } + // } + root.getBitpayAccounts = function(network, cb) { + storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + if (err) return cb(err); + if (lodash.isString(bitpayAccounts)) { + bitpayAccounts = JSON.parse(bitpayAccounts); + } + cb(err, bitpayAccounts); + }); + }; + root.setAppIdentity = function(network, data, cb) { storage.set('appIdentity-' + network, data, cb); }; From 58d9c91ca3fe850c3b077bfec53fbee45ba68ac2 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Fri, 23 Dec 2016 11:53:56 -0500 Subject: [PATCH 04/11] Fixes upgrade of bitpayCardCredentials to appIdentity. --- src/js/services/storageService.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 3643dc724..fa6544501 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -138,10 +138,6 @@ angular.module('copayApp.services') if (err) return cb(err); if (data != null) { // Needs upgrade - if (lodash.isString(data)) { - data = JSON.parse(data); - } - data = data || {}; _01_setAppIdentity(network, data, function(err) { if (err) return cb(err); storage.remove(key, function() { From 3bac0fe707902951d27f74a598238bc49f1d6856 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Fri, 23 Dec 2016 12:22:53 -0500 Subject: [PATCH 05/11] Fix upgrade of bitpay account storage. --- src/js/services/storageService.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index fa6544501..bb23f641d 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -161,14 +161,14 @@ angular.module('copayApp.services') var upgraded = ''; Object.keys(data).forEach(function(key) { // Keys are account emails - if (!data[key]['bitpayApi-' + network].token) { + if (!data[key]['bitpayApi-' + network]) { // Needs upgrade upgraded += ' ' + key; var acctData = { token: data[key]['bitpayDebitCards-' + network].token, email: key }; - _02_setBitpayAccount(network, data, function(err) { + _02_setBitpayAccount(network, acctData, function(err) { if (err) return cb(err); _02_setBitpayDebitCards(network, data[key]['bitpayDebitCards-' + network], function(err) { @@ -236,7 +236,7 @@ angular.module('copayApp.services') data = JSON.parse(data); } data = data || {}; - if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set'); + if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set'); storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { if (err) return cb(err); if (lodash.isString(bitpayAccounts)) { @@ -588,7 +588,7 @@ angular.module('copayApp.services') data = JSON.parse(data); } data = data || {}; - if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set'); + if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set'); storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { if (err) return cb(err); if (lodash.isString(bitpayAccounts)) { From 28f9fbc0b639ffcc743c0d1e3c23b6e7d606074c Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Thu, 5 Jan 2017 16:29:17 -0500 Subject: [PATCH 06/11] Use an environment object instead of string. --- src/js/controllers/bitpayCard.js | 2 +- src/js/services/appIdentityService.js | 3 --- src/js/services/bitpayCardService.js | 18 +++++++++--------- src/js/services/bitpayService.js | 8 +++++--- src/js/services/storageService.js | 8 +++++++- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/js/controllers/bitpayCard.js b/src/js/controllers/bitpayCard.js index 0c8a85bd3..f50218d9f 100644 --- a/src/js/controllers/bitpayCard.js +++ b/src/js/controllers/bitpayCard.js @@ -7,7 +7,7 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi $scope.dateRange = { value: 'last30Days' }; - $scope.network = bitpayService.getEnvironment(); + $scope.network = bitpayService.getEnvironment().network; var updateHistoryFromCache = function(cb) { bitpayCardService.getBitpayDebitCardsHistory($scope.cardId, function(err, data) { diff --git a/src/js/services/appIdentityService.js b/src/js/services/appIdentityService.js index 6df3f136d..3f6debbbe 100644 --- a/src/js/services/appIdentityService.js +++ b/src/js/services/appIdentityService.js @@ -7,9 +7,6 @@ angular.module('copayApp.services').factory('appIdentityService', function($log, var pubkey, sin, isNew; storageService.getAppIdentity(network, function(err, data) { if (err) return cb(err); - if (lodash.isString(data)) { - data = JSON.parse(data); - } var appIdentity = data || {}; if (lodash.isEmpty(appIdentity) || (appIdentity && !appIdentity.priv)) { isNew = true; diff --git a/src/js/services/bitpayCardService.js b/src/js/services/bitpayCardService.js index ce8e787c7..d26b35c9b 100644 --- a/src/js/services/bitpayCardService.js +++ b/src/js/services/bitpayCardService.js @@ -60,7 +60,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, method: 'getInvoiceHistory', params: JSON.stringify(params) }; - appIdentityService.getIdentity(bitpayService.getEnvironment(), function(err, appIdentity) { + appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) { if (err) return cb(err); root.getBitpayDebitCards(function(err, data) { if (err) return cb(err); @@ -97,7 +97,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, method: 'generateTopUpInvoice', params: JSON.stringify(params) }; - appIdentityService.getIdentity(bitpayService.getEnvironment(), function(err, appIdentity) { + appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) { if (err) return cb(err); root.getBitpayDebitCards(function(err, data) { if (err) return cb(err); @@ -127,7 +127,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, }; root.getBitpayDebitCards = function(cb) { - storageService.getBitpayDebitCards(bitpayService.getEnvironment(), function(err, data) { + storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, function(err, data) { if (err) return cb(err); if (lodash.isString(data)) { data = JSON.parse(data); @@ -139,14 +139,14 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, root.setBitpayDebitCards = function(data, cb) { data = JSON.stringify(data); - storageService.setBitpayDebitCards(bitpayService.getEnvironment(), data, function(err) { + storageService.setBitpayDebitCards(bitpayService.getEnvironment().network, data, function(err) { if (err) return cb(err); return cb(); }); }; root.getBitpayDebitCardsHistory = function(cardId, cb) { - storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment(), function(err, data) { + storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment().network, function(err, data) { if (err) return cb(err); if (lodash.isString(data)) { data = JSON.parse(data); @@ -158,7 +158,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, }; root.setBitpayDebitCardsHistory = function(cardId, data, opts, cb) { - storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment(), function(err, oldData) { + storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment().network, function(err, oldData) { if (lodash.isString(oldData)) { oldData = JSON.parse(oldData); } @@ -172,19 +172,19 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, } inv = JSON.stringify(inv); - storageService.setBitpayDebitCardsHistory(bitpayService.getEnvironment(), inv, function(err) { + storageService.setBitpayDebitCardsHistory(bitpayService.getEnvironment().network, inv, function(err) { return cb(err); }); }); }; root.remove = function(card, cb) { - storageService.removeBitpayDebitCard(bitpayService.getEnvironment(), card, function(err) { + storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, card, function(err) { if (err) { $log.error('Error removing BitPay debit card: ' + err); // Continue, try to remove/cleanup card history } - storageService.removeBitpayDebitCardHistory(bitpayService.getEnvironment(), card, function(err) { + storageService.removeBitpayDebitCardHistory(bitpayService.getEnvironment().network, card, function(err) { if (err) { $log.error('Error removing BitPay debit card transaction history: ' + err); return cb(err); diff --git a/src/js/services/bitpayService.js b/src/js/services/bitpayService.js index 12c3177e6..c0c3a5233 100644 --- a/src/js/services/bitpayService.js +++ b/src/js/services/bitpayService.js @@ -7,7 +7,9 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt var BITPAY_API_URL = NETWORK == 'livenet' ? 'https://bitpay.com' : 'https://test.bitpay.com'; root.getEnvironment = function() { - return NETWORK; + return { + network: NETWORK + }; }; /* @@ -58,7 +60,7 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt code: pairData.otp } }; - appIdentityService.getIdentity(root.getEnvironment(), function(err, appIdentity) { + appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) { if (err) return cb(err); $http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) { if (data && data.data.error) return cb(data.data.error); @@ -132,7 +134,7 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt var setBitpayAccount = function(accountData, cb) { var data = JSON.stringify(accountData); - storageService.setBitpayAccount(root.getEnvironment(), data, function(err) { + storageService.setBitpayAccount(root.getEnvironment().network, data, function(err) { if (err) return cb(err); return cb(); }); diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index bb23f641d..70d766bf7 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -728,7 +728,13 @@ angular.module('copayApp.services') }; root.getAppIdentity = function(network, cb) { - storage.get('appIdentity-' + network, cb); + storage.get('appIdentity-' + network, function(err, data) { + if (err) return cb(err); + if (lodash.isString(data)) { + data = JSON.parse(data); + } + cb(err, data); + }); }; root.removeAppIdentity = function(network, cb) { From 21875a53b59dae45007727e129b1e4de46f851af Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Thu, 5 Jan 2017 17:23:26 -0500 Subject: [PATCH 07/11] Write to a new storage key on storage upgrade. --- src/js/services/storageService.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 70d766bf7..1ec64acd9 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -107,7 +107,7 @@ angular.module('copayApp.services') var _upgraders = { '00_bitpayDebitCards' : _upgrade_bitpayDebitCards, // 2016-11: Upgrade bitpayDebitCards-x to bitpayAccounts-x '01_bitpayCardCredentials' : _upgrade_bitpayCardCredentials, // 2016-11: Upgrade bitpayCardCredentials-x to appIdentity-x - '02_bitpayAccounts' : _upgrade_bitpayAccounts // 2016-12: Reorganize the object + '02_bitpayAccounts' : _upgrade_bitpayAccounts // 2016-12: Upgrade tpayAccounts-x to bitpayAccounts-v2-x }; function _upgrade_bitpayDebitCards(key, network, cb) { @@ -227,7 +227,7 @@ angular.module('copayApp.services') bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; bitpayAccounts[data.email]['bitpayApi-' + network] = bitpayAccounts[data.email]['bitpayApi-' + network] || {}; bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token; - storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb); }); }; @@ -237,7 +237,7 @@ angular.module('copayApp.services') } data = data || {}; if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set'); - storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { if (err) return cb(err); if (lodash.isString(bitpayAccounts)) { bitpayAccounts = JSON.parse(bitpayAccounts); @@ -245,7 +245,7 @@ angular.module('copayApp.services') bitpayAccounts = bitpayAccounts || {}; bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards; - storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb); }); }; // @@ -589,7 +589,7 @@ angular.module('copayApp.services') } data = data || {}; if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set'); - storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { if (err) return cb(err); if (lodash.isString(bitpayAccounts)) { bitpayAccounts = JSON.parse(bitpayAccounts); @@ -597,7 +597,7 @@ angular.module('copayApp.services') bitpayAccounts = bitpayAccounts || {}; bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards; - storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb); }); }; @@ -610,7 +610,7 @@ angular.module('copayApp.services') // email: account email // ] root.getBitpayDebitCards = function(network, cb) { - storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { if (lodash.isString(bitpayAccounts)) { bitpayAccounts = JSON.parse(bitpayAccounts); } @@ -640,7 +640,7 @@ angular.module('copayApp.services') } card = card || {}; if (lodash.isEmpty(card) || !card.eid) return cb('No card to remove'); - storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { if (err) cb(err); if (lodash.isString(bitpayAccounts)) { bitpayAccounts = JSON.parse(bitpayAccounts); @@ -680,7 +680,7 @@ angular.module('copayApp.services') } data = data || {}; if (lodash.isEmpty(data) || !data.email) return cb('No account to set'); - storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { if (err) return cb(err); if (lodash.isString(bitpayAccounts)) { bitpayAccounts = JSON.parse(bitpayAccounts); @@ -689,7 +689,7 @@ angular.module('copayApp.services') bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; bitpayAccounts[data.email]['bitpayApi-' + network] = bitpayAccounts[data.email]['bitpayApi-' + network] || {}; bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token; - storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); + storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb); }); }; @@ -714,7 +714,7 @@ angular.module('copayApp.services') // } // } root.getBitpayAccounts = function(network, cb) { - storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { + storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { if (err) return cb(err); if (lodash.isString(bitpayAccounts)) { bitpayAccounts = JSON.parse(bitpayAccounts); From 099a20c3a32e57bde0d63cf605ad19c572073b0f Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Thu, 5 Jan 2017 17:44:23 -0500 Subject: [PATCH 08/11] Fix missed network parameter. --- src/js/services/bitpayService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/bitpayService.js b/src/js/services/bitpayService.js index c0c3a5233..d2dc831dc 100644 --- a/src/js/services/bitpayService.js +++ b/src/js/services/bitpayService.js @@ -111,7 +111,7 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt }; root.post = function(endpoint, json, successCallback, errorCallback) { - appIdentityService.getIdentity(NETWORK, function(err, appIdentity) { + appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) { if (err) return errorCallback(err); $http(_post(endpoint, json, appIdentity)).then(function(data) { successCallback(data); From 3bf8923f10666153ed5ee19565ed95b2c7589ab0 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Thu, 5 Jan 2017 17:57:25 -0500 Subject: [PATCH 09/11] Remove obsolete key on storage upgrade. Improve log output. --- src/js/services/storageService.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 1ec64acd9..d982b7846 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -177,11 +177,14 @@ angular.module('copayApp.services') }); } }); - if (upgraded.length > 0) { - cb(null, 'upgraded \'bitpayAccounts\':' + upgraded); - } else { - cb(); - } + // Remove obsolete key. + storage.remove('bitpayAccounts-' + network, function() { + if (upgraded.length > 0) { + cb(null, 'upgraded to \'bitpayAccounts-v2-' + network + '\':' + upgraded); + } else { + cb(); + } + }); }); }; // @@ -277,11 +280,11 @@ angular.module('copayApp.services') var storagekey = key.split('_')[1]; _upgraders[key](storagekey, network, function(err, msg) { if (err) { - _handleUpgradeError(storagekey, err); + _handleUpgradeError(storagekey + '-' + network, err); errorCount++; errorMessage = errorCount + ' storage upgrade failures'; } - if (msg) _handleUpgradeSuccess(storagekey, msg); + if (msg) _handleUpgradeSuccess(storagekey + '-' + network, msg); }); }); }); From c57effda96b4d5a640f1808625a173cfc066c748 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Thu, 5 Jan 2017 18:16:24 -0500 Subject: [PATCH 10/11] Fixes hiding of bitpay cards entry in settings when no cards are found. --- src/js/controllers/tab-settings.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/tab-settings.js b/src/js/controllers/tab-settings.js index d90570d8b..b5573155e 100644 --- a/src/js/controllers/tab-settings.js +++ b/src/js/controllers/tab-settings.js @@ -28,11 +28,9 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct $scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp; if ($scope.bitpayCardEnabled) { - bitpayCardService.getBitpayDebitCards(function(err, data) { + bitpayCardService.getBitpayDebitCards(function(err, cards) { if (err) $log.error(err); - if (!lodash.isEmpty(data)) { - $scope.bitpayCards = true; - } + $scope.bitpayCards = cards && cards.length > 0; }); } From 94539871ba60031b919e3ede140e707a131645e3 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Thu, 5 Jan 2017 18:19:23 -0500 Subject: [PATCH 11/11] Remove confusing comment. --- src/js/services/storageService.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index d982b7846..aafc4b759 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -78,9 +78,6 @@ angular.module('copayApp.services') // // UPGRADING STORAGE // - // TODO: prior to upgrading storage a backup of all storage should be captured and made available - // for export for recovery/debugging. - // // Upgraders are executed in numerical order per the '##_' object key prefix. Each upgrader will run. // Each upgrader should detect storage configuraton and fail-safe; no upgrader should damage the ability // of another to function properly (in order). Each upgrader should upgrade storage incrementally; storage