diff --git a/app-template/bitpay/appConfig.json b/app-template/bitpay/appConfig.json index 664fe156f..3282d4feb 100644 --- a/app-template/bitpay/appConfig.json +++ b/app-template/bitpay/appConfig.json @@ -21,8 +21,8 @@ "windowsAppId": "2d1002d7-ee34-4f60-bd29-0c871ba0c195", "pushSenderId": "1036948132229", "description": "Secure Bitcoin Wallet", - "version": "1.3.2", - "androidVersion": "132000", + "version": "1.3.4", + "androidVersion": "134000", "_extraCSS": null, "_enabledExtensions": { "coinbase": true, diff --git a/src/js/controllers/advancedSettings.js b/src/js/controllers/advancedSettings.js index 9bfd90a58..0664a0804 100644 --- a/src/js/controllers/advancedSettings.js +++ b/src/js/controllers/advancedSettings.js @@ -3,27 +3,18 @@ angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $rootScope, $log, $window, lodash, configService, uxLanguage, platformInfo, pushNotificationsService, profileService, feeService, storageService, $ionicHistory, $timeout, $ionicScrollDelegate) { var updateConfig = function() { - var config = configService.getSync(); $scope.spendUnconfirmed = { value: config.wallet.spendUnconfirmed }; - $scope.bitpayCardEnabled = { - value: config.bitpayCard.enabled - }; - $scope.amazonEnabled = { - value: config.amazon.enabled - }; - $scope.glideraEnabled = { - value: config.glidera.enabled - }; - $scope.coinbaseEnabled = { - value: config.coinbaseV2 - }; $scope.recentTransactionsEnabled = { value: config.recentTransactions.enabled }; + + $scope.hideNextSteps = { + value: config.hideNextSteps.enabled + }; }; $scope.spendUnconfirmedChange = function() { @@ -37,42 +28,11 @@ angular.module('copayApp.controllers').controller('advancedSettingsController', }); }; - $scope.bitpayCardChange = function() { + $scope.nextStepsChange = function() { var opts = { - bitpayCard: { - enabled: $scope.bitpayCardEnabled.value - } - }; - configService.set(opts, function(err) { - if (err) $log.debug(err); - }); - }; - - $scope.amazonChange = function() { - var opts = { - amazon: { - enabled: $scope.amazonEnabled.value - } - }; - configService.set(opts, function(err) { - if (err) $log.debug(err); - }); - }; - - $scope.glideraChange = function() { - var opts = { - glidera: { - enabled: $scope.glideraEnabled.value - } - }; - configService.set(opts, function(err) { - if (err) $log.debug(err); - }); - }; - - $scope.coinbaseChange = function() { - var opts = { - coinbaseV2: $scope.coinbaseEnabled.value + hideNextSteps: { + enabled: $scope.hideNextSteps.value + }, }; configService.set(opts, function(err) { if (err) $log.debug(err); diff --git a/src/js/controllers/bitpayCard.js b/src/js/controllers/bitpayCard.js index f50218d9f..05a823a3e 100644 --- a/src/js/controllers/bitpayCard.js +++ b/src/js/controllers/bitpayCard.js @@ -9,16 +9,6 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi }; $scope.network = bitpayService.getEnvironment().network; - var updateHistoryFromCache = function(cb) { - bitpayCardService.getBitpayDebitCardsHistory($scope.cardId, function(err, data) { - if (err ||  lodash.isEmpty(data)) return cb(); - $scope.historyCached = true; - self.bitpayCardTransactionHistory = data.transactions; - self.bitpayCardCurrentBalance = data.balance; - return cb(); - }); - }; - var setDateRange = function(preset) { var startDate, endDate; preset = preset ||  'last30Days'; @@ -45,13 +35,19 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi }; var setGetStarted = function(history, cb) { - if (lodash.isEmpty(history.transactionList)) { - var dateRange = setDateRange('all'); - bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) { - if (lodash.isEmpty(history.transactionList)) return cb(true); - return cb(false); - }); - } else return cb(false); + + // Is the card new? + if (!lodash.isEmpty(history.transactionList)) + return cb(); + + var dateRange = setDateRange('all'); + bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) { + + if (!err && lodash.isEmpty(history.transactionList)) + self.getStated=true; + + return cb(); + }); }; this.update = function() { @@ -59,18 +55,18 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi $scope.loadingHistory = true; bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) { + $scope.loadingHistory = false; if (err) { $log.error(err); self.bitpayCardTransactionHistory = null; - self.bitpayCardCurrentBalance = null; + self.balance = null; popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get transactions')); return; } - setGetStarted(history, function(getStarted) { - self.getStarted = getStarted; + setGetStarted(history, function() { var txs = lodash.clone(history.txs); runningBalance = parseFloat(history.endingBalance); @@ -83,18 +79,21 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi _runningBalance(txs[i]); } self.bitpayCardTransactionHistory = txs; - self.bitpayCardCurrentBalance = history.currentCardBalance; + self.balance = history.currentCardBalance; + self.updatedOn = null; if ($scope.dateRange.value == 'last30Days') { - $log.debug('BitPay Card: store cache history'); - var cacheHistory = { - balance: history.currentCardBalance, - transactions: history.txs - }; - bitpayCardService.setBitpayDebitCardsHistory($scope.cardId, cacheHistory, {}, function(err) { - if (err) $log.error(err); - $scope.historyCached = true; - }); + + // TODO? + // $log.debug('BitPay Card: storing cache history'); + // var cacheHistory = { + // balance: history.currentCardBalance, + // transactions: history.txs + // }; + // bitpayCardService.setHistory($scope.cardId, cacheHistory, {}, function(err) { + // if (err) $log.error(err); + // $scope.historyCached = true; + // }); } $timeout(function() { $scope.$apply(); @@ -136,24 +135,26 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi $scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.cardId = data.stateParams.id; + if (!$scope.cardId) { - var msg = gettextCatalog.getString('Bad param'); $ionicHistory.nextViewOptions({ disableAnimate: true }); $state.go('tabs.home'); - popupService.showAlert(gettextCatalog.getString('Error'), msg); - } else { - updateHistoryFromCache(function() { - self.update(); - }); - bitpayCardService.getBitpayDebitCards(function(err, cards) { - if (err) return; - $scope.card = lodash.find(cards, function(card) { - return card.eid == $scope.cardId; - }); - }); } - }); + + bitpayCardService.get({ + cardId: $scope.cardId, + noRefresh: true, + }, function(err, cards) { + + if (cards && cards[0]) { + self.lastFourDigits = cards[0].lastFourDigits; + self.balance = cards[0].balance; + self.updatedOn = cards[0].updatedOn; + } + self.update(); + }); + }); }); diff --git a/src/js/controllers/bitpayCardIntro.js b/src/js/controllers/bitpayCardIntro.js index d829b918f..9fea27978 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, appIdentityService, bitpayService) { +angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, appIdentityService, bitpayService, lodash) { $scope.$on("$ionicView.beforeEnter", function(event, data) { if (data.stateParams && data.stateParams.secret) { @@ -18,27 +18,23 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f return; } if (paired) { - bitpayCardService.fetchBitpayDebitCards(apiContext, function(err, data) { - + bitpayCardService.sync(apiContext, function(err, cards) { if (err) { - popupService.showAlert(gettextCatalog.getString('Error fetching Debit Cards'), err); + popupService.showAlert(gettextCatalog.getString('Error updating Debit Cards'), 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 - }); - } - }); + + $ionicHistory.nextViewOptions({ + disableAnimate: true + }); + $state.go('tabs.home').then(function() { + if (cards[0]) { + $state.transitionTo('tabs.bitpayCard', { + id: cards[0].id + }); + } }); }); } diff --git a/src/js/controllers/buyAndSellCardController.js b/src/js/controllers/buyAndSellCardController.js new file mode 100644 index 000000000..1c33c8112 --- /dev/null +++ b/src/js/controllers/buyAndSellCardController.js @@ -0,0 +1,14 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('buyAndSellCardController', function($scope, nextStepsService, $ionicScrollDelegate, buyAndSellService) { + + $scope.services = buyAndSellService.getLinked(); + + $scope.toggle = function() { + $scope.hide = !$scope.hide; + $timeout(function() { + $ionicScrollDelegate.resize(); + $scope.$apply(); + }, 10); + }; +}); diff --git a/src/js/controllers/buyCoinbase.js b/src/js/controllers/buyCoinbase.js index 587ef778e..babc2a2fd 100644 --- a/src/js/controllers/buyCoinbase.js +++ b/src/js/controllers/buyCoinbase.js @@ -34,8 +34,6 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - coinbaseService.setCredentials(); - $scope.isFiat = data.stateParams.currency ? true : false; [amount, currency, $scope.amountUnitStr] = coinbaseService.parseAmount( data.stateParams.amount, diff --git a/src/js/controllers/buyandsellController.js b/src/js/controllers/buyandsellController.js index 93df71069..643ba401a 100644 --- a/src/js/controllers/buyandsellController.js +++ b/src/js/controllers/buyandsellController.js @@ -1,14 +1,11 @@ 'use strict'; -angular.module('copayApp.controllers').controller('buyandsellController', function($scope, $ionicHistory, configService) { +angular.module('copayApp.controllers').controller('buyandsellController', function($scope, $ionicHistory, buyAndSellService, lodash) { $scope.$on("$ionicView.beforeEnter", function(event, data) { - configService.whenAvailable(function(config) { - $scope.isCoinbaseEnabled = config.coinbaseV2; - $scope.isGlideraEnabled = config.glidera.enabled; + $scope.services = buyAndSellService.get(); - if (!$scope.isCoinbaseEnabled && !$scope.isGlideraEnabled) - $ionicHistory.goBack(); - }); + if (lodash.isEmpty($scope.services)) + $ionicHistory.goBack(); }); }); diff --git a/src/js/controllers/coinbase.js b/src/js/controllers/coinbase.js index 357cb4cab..9ac2fe924 100644 --- a/src/js/controllers/coinbase.js +++ b/src/js/controllers/coinbase.js @@ -137,7 +137,6 @@ angular.module('copayApp.controllers').controller('coinbaseController', function var self = this; $scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.showOauthForm = false; - coinbaseService.setCredentials(); if (data.stateParams && data.stateParams.code) { coinbaseService.getStoredToken(function(at) { if (!at) self.submitOauthCode(data.stateParams.code); diff --git a/src/js/controllers/homeIntegrations.js b/src/js/controllers/homeIntegrations.js new file mode 100644 index 000000000..fe01d816b --- /dev/null +++ b/src/js/controllers/homeIntegrations.js @@ -0,0 +1,16 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('homeIntegrationsController', function($scope, homeIntegrationsService, $ionicScrollDelegate, $timeout) { + + $scope.hide = false; + $scope.services = homeIntegrationsService.get(); + + $scope.toggle = function() { + $scope.hide = !$scope.hide; + $timeout(function() { + $ionicScrollDelegate.resize(); + $scope.$apply(); + }, 10); + }; + +}); diff --git a/src/js/controllers/nextStepsController.js b/src/js/controllers/nextStepsController.js new file mode 100644 index 000000000..b745825ec --- /dev/null +++ b/src/js/controllers/nextStepsController.js @@ -0,0 +1,16 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('nextStepsController', function($scope, nextStepsService, $ionicScrollDelegate, $timeout) { + + $scope.hide = false; + $scope.services = nextStepsService.get(); + + $scope.toggle = function() { + $scope.hide = !$scope.hide; + $timeout(function() { + $ionicScrollDelegate.resize(); + $scope.$apply(); + }, 10); + }; + +}); diff --git a/src/js/controllers/preferencesBitpayCard.js b/src/js/controllers/preferencesBitpayCard.js index 6b43b62b6..8ac6ba2d7 100644 --- a/src/js/controllers/preferencesBitpayCard.js +++ b/src/js/controllers/preferencesBitpayCard.js @@ -1,19 +1,21 @@ 'use strict'; angular.module('copayApp.controllers').controller('preferencesBitpayCardController', - function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog) { + function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog, $log) { $scope.remove = function(card) { 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) { - if (res) remove(card); + $log.info('Removing bitpay card:' + card.eid) + if (res) + remove(card.eid); }); }; - var remove = function(card) { - bitpayCardService.remove(card, function(err) { + var remove = function(cardEid) { + bitpayCardService.remove(cardEid, function(err) { if (err) { return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove card')); } @@ -25,8 +27,9 @@ angular.module('copayApp.controllers').controller('preferencesBitpayCardControll }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - bitpayCardService.getBitpayDebitCards(function(err, data) { + bitpayCardService.getCards(function(err, data) { if (err) return; + $scope.bitpayCards = data; }); }); diff --git a/src/js/controllers/preferencesCoinbase.js b/src/js/controllers/preferencesCoinbase.js index eea526bad..5537fb37b 100644 --- a/src/js/controllers/preferencesCoinbase.js +++ b/src/js/controllers/preferencesCoinbase.js @@ -16,7 +16,6 @@ angular.module('copayApp.controllers').controller('preferencesCoinbaseController }; $scope.$on("$ionicView.enter", function(event, data){ - coinbaseService.setCredentials(); ongoingProcess.set('connectingCoinbase', true); coinbaseService.init(function(err, data) { if (err || lodash.isEmpty(data)) { diff --git a/src/js/controllers/sellCoinbase.js b/src/js/controllers/sellCoinbase.js index d7b385e72..bd86bd0d1 100644 --- a/src/js/controllers/sellCoinbase.js +++ b/src/js/controllers/sellCoinbase.js @@ -117,8 +117,6 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - coinbaseService.setCredentials(); - $scope.isFiat = data.stateParams.currency ? true : false; [amount, currency, $scope.amountUnitStr] = coinbaseService.parseAmount( data.stateParams.amount, diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 0ca076aad..d3d6e9610 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('tabHomeController', - function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, bitpayCardService, startupService, addressbookService, feedbackService, bwcError, coinbaseService) { + function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, feedbackService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService) { var wallet; var listeners = []; var notifications = []; @@ -87,10 +87,7 @@ angular.module('copayApp.controllers').controller('tabHomeController', var wallet = profileService.getWallet(walletId); updateWallet(wallet); if ($scope.recentTransactionsEnabled) getNotifications(); - if ($scope.coinbaseEnabled && type == 'NewBlock' && n && n.data && n.data.network == 'livenet') { - // Update Coinbase - coinbaseService.updatePendingTransactions(); - } + }), $rootScope.$on('Local/TxAction', function(e, walletId) { $log.debug('Got action for wallet ' + walletId); @@ -100,31 +97,29 @@ angular.module('copayApp.controllers').controller('tabHomeController', }) ]; + + $scope.buyAndSellItems = buyAndSellService.getLinked(); + $scope.homeIntegrations = homeIntegrationsService.get(); + + bitpayCardService.get({}, function(err, cards) { + $scope.bitpayCardItems = cards; + }); + configService.whenAvailable(function() { - nextStep(function() { - var config = configService.getSync(); - var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova; + var config = configService.getSync(); + $scope.recentTransactionsEnabled = config.recentTransactions.enabled; + if ($scope.recentTransactionsEnabled) getNotifications(); - $scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp; - $scope.coinbaseEnabled = config.coinbaseV2 && !isWindowsPhoneApp; - $scope.amazonEnabled = config.amazon.enabled; - $scope.bitpayCardEnabled = config.bitpayCard.enabled; + if (config.hideNextSteps.enabled) { + $scope.nextStepsItems = null; + } else { + $scope.nextStepsItems = nextStepsService.get(); + } - var buyAndSellEnabled = !$scope.externalServices.BuyAndSell && ($scope.glideraEnabled || $scope.coinbaseEnabled); - var amazonEnabled = !$scope.externalServices.AmazonGiftCards && $scope.amazonEnabled; - var bitpayCardEnabled = !$scope.externalServices.BitpayCard && $scope.bitpayCardEnabled; - - $scope.nextStepEnabled = buyAndSellEnabled || amazonEnabled || bitpayCardEnabled; - $scope.recentTransactionsEnabled = config.recentTransactions.enabled; - - if ($scope.recentTransactionsEnabled) getNotifications(); - - if ($scope.bitpayCardEnabled) bitpayCardCache(); - $timeout(function() { - $ionicScrollDelegate.resize(); - $scope.$apply(); - }, 10); - }); + $timeout(function() { + $ionicScrollDelegate.resize(); + $scope.$apply(); + }, 10); }); }); @@ -223,6 +218,9 @@ angular.module('copayApp.controllers').controller('tabHomeController', } else { wallet.error = null; wallet.status = status; + + // TODO service refactor? not in profile service + profileService.setLastKnownBalance(wallet.id, wallet.status.totalBalanceStr, function() {}); } if (++j == i) { updateTxps(); @@ -269,46 +267,6 @@ angular.module('copayApp.controllers').controller('tabHomeController', }); }; - var nextStep = function(cb) { - var i = 0; - var services = ['AmazonGiftCards', 'BitpayCard', 'BuyAndSell']; - lodash.each(services, function(service) { - storageService.getNextStep(service, function(err, value) { - $scope.externalServices[service] = value == 'true' ? true : false; - if (++i == services.length) return cb(); - }); - }); - }; - - $scope.shouldHideNextSteps = function() { - $scope.hideNextSteps = !$scope.hideNextSteps; - $timeout(function() { - $ionicScrollDelegate.resize(); - $scope.$apply(); - }, 10); - }; - - var bitpayCardCache = function() { - bitpayCardService.getBitpayDebitCards(function(err, data) { - if (err) return; - if (lodash.isEmpty(data)) { - $scope.bitpayCards = null; - return; - } - $scope.bitpayCards = data; - $timeout(function() { - $scope.$digest(); - }, 100); - }); - bitpayCardService.getBitpayDebitCardsHistory(null, function(err, data) { - if (err) return; - if (lodash.isEmpty(data)) { - $scope.cardsHistory = null; - return; - } - $scope.cardsHistory = data; - }); - }; $scope.onRefresh = function() { $timeout(function() { diff --git a/src/js/controllers/tab-settings.js b/src/js/controllers/tab-settings.js index 4a3624c2f..98d2b1bf5 100644 --- a/src/js/controllers/tab-settings.js +++ b/src/js/controllers/tab-settings.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, coinbaseService, gettextCatalog) { +angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, coinbaseService, gettextCatalog, buyAndSellService) { var updateConfig = function() { var isCordova = platformInfo.isCordova; @@ -16,6 +16,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct $scope.currentFeeLevel = feeService.getCurrentFeeLevel(); $scope.wallets = profileService.getWallets(); + $scope.buyAndSellServices = buyAndSellService.getLinked(); configService.whenAvailable(function(config) { $scope.unitName = config.wallet.settings.unitName; @@ -24,30 +25,11 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct isoCode: config.wallet.settings.alternativeIsoCode }; - $scope.bitpayCardEnabled = config.bitpayCard.enabled; - $scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp; - $scope.coinbaseEnabled = config.coinbaseV2 && !isWindowsPhoneApp; - - if ($scope.bitpayCardEnabled) { - bitpayCardService.getBitpayDebitCards(function(err, cards) { - if (err) $log.error(err); - $scope.bitpayCards = cards && cards.length > 0; - }); - } - - if ($scope.glideraEnabled) { - storageService.getGlideraToken(glideraService.getEnvironment(), function(err, token) { - if (err) $log.error(err); - $scope.glideraToken = token; - }); - } - - if ($scope.coinbaseEnabled) { - coinbaseService.setCredentials(); - coinbaseService.getStoredToken(function(at) { - $scope.coinbaseToken = at; - }); - } + // TODO move this to a generic service + bitpayCardService.getCards(function(err, cards) { + if (err) $log.error(err); + $scope.bitpayCards = cards && cards.length > 0; + }); }); }; diff --git a/src/js/routes.js b/src/js/routes.js index cc8037381..59202bea4 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -1088,7 +1088,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }); }) - .run(function($rootScope, $state, $location, $log, $timeout, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService) { + .run(function($rootScope, $state, $location, $log, $timeout, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, /* plugins START HERE => */ coinbaseService, glideraService, amazonService, bitpayCardService) { uxLanguage.init(); @@ -1153,50 +1153,40 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr }); } - $log.info('Verifying storage...'); - storageService.verify(function(err) { + $log.info('Init profile...'); + // Try to open local profile + profileService.loadAndBindProfile(function(err) { + $ionicHistory.nextViewOptions({ + disableAnimate: true + }); if (err) { - $log.error('Storage failed to verify: ' + err); - // TODO - what next? - } else { - $log.info('Storage OK'); - } - - $log.info('Init profile...'); - // Try to open local profile - profileService.loadAndBindProfile(function(err) { - $ionicHistory.nextViewOptions({ - disableAnimate: true - }); - if (err) { - if (err.message && err.message.match('NOPROFILE')) { - $log.debug('No profile... redirecting'); + if (err.message && err.message.match('NOPROFILE')) { + $log.debug('No profile... redirecting'); + $state.go('onboarding.welcome'); + } else if (err.message && err.message.match('NONAGREEDDISCLAIMER')) { + if (lodash.isEmpty(profileService.getWallets())) { + $log.debug('No wallets and no disclaimer... redirecting'); $state.go('onboarding.welcome'); - } else if (err.message && err.message.match('NONAGREEDDISCLAIMER')) { - if (lodash.isEmpty(profileService.getWallets())) { - $log.debug('No wallets and no disclaimer... redirecting'); - $state.go('onboarding.welcome'); - } else { - $log.debug('Display disclaimer... redirecting'); - $state.go('onboarding.disclaimer', { - resume: true - }); - } } else { - throw new Error(err); // TODO + $log.debug('Display disclaimer... redirecting'); + $state.go('onboarding.disclaimer', { + resume: true + }); } } else { - profileService.storeProfileIfDirty(); - $log.debug('Profile loaded ... Starting UX.'); - scannerService.gentleInitialize(); - $state.go('tabs.home'); + throw new Error(err); // TODO } + } else { + profileService.storeProfileIfDirty(); + $log.debug('Profile loaded ... Starting UX.'); + scannerService.gentleInitialize(); + $state.go('tabs.home'); + } - // After everything have been loaded, initialize handler URL - $timeout(function() { - openURLService.init(); - }, 1000); - }); + // After everything have been loaded, initialize handler URL + $timeout(function() { + openURLService.init(); + }, 1000); }); }); diff --git a/src/js/services/amazonService.js b/src/js/services/amazonService.js index e44166172..d0c715220 100644 --- a/src/js/services/amazonService.js +++ b/src/js/services/amazonService.js @@ -1,24 +1,36 @@ 'use strict'; -angular.module('copayApp.services').factory('amazonService', function($http, $log, lodash, moment, storageService, configService, platformInfo) { +angular.module('copayApp.services').factory('amazonService', function($http, $log, lodash, moment, storageService, configService, platformInfo, nextStepsService, homeIntegrationsService) { var root = {}; var credentials = {}; - var _setCredentials = function() { - /* - * Development: 'testnet' - * Production: 'livenet' - */ - credentials.NETWORK = 'livenet'; + /* + * Development: 'testnet' + * Production: 'livenet' + */ + credentials.NETWORK = 'livenet'; + //credentials.NETWORK = 'testnet'; - if (credentials.NETWORK == 'testnet') { - credentials.BITPAY_API_URL = "https://test.bitpay.com"; - } else { - credentials.BITPAY_API_URL = "https://bitpay.com"; - }; + if (credentials.NETWORK == 'testnet') { + credentials.BITPAY_API_URL = "https://test.bitpay.com"; + } else { + credentials.BITPAY_API_URL = "https://bitpay.com"; + }; + + var homeItem = { + name: 'amazon', + title: 'Amazon Gift Cards', + icon: 'icon-amazon', + sref: 'tabs.giftcards.amazon', + }; + + var nextStepItem = { + name: 'amazon', + title: 'Buy a gift card', + icon: 'icon-amazon', + sref: 'tabs.giftcards.amazon', }; var _getBitPay = function(endpoint) { - _setCredentials(); return { method: 'GET', url: credentials.BITPAY_API_URL + endpoint, @@ -29,7 +41,6 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo }; var _postBitPay = function(endpoint, data) { - _setCredentials(); return { method: 'POST', url: credentials.BITPAY_API_URL + endpoint, @@ -41,7 +52,6 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo }; root.getNetwork = function() { - _setCredentials(); return credentials.NETWORK; }; @@ -65,12 +75,12 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo inv = JSON.stringify(inv); storageService.setAmazonGiftCards(network, inv, function(err) { + + homeIntegrationsService.register(homeItem); + nextStepsService.unregister(nextStepItem.name); return cb(err); }); }); - - // Show pending task from the UI - storageService.setNextStep('AmazonGiftCards', 'true', function(err) {}); }; root.getPendingGiftCards = function(cb) { @@ -144,6 +154,16 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo }); }; - return root; + var register = function() { + storageService.getAmazonGiftCards(root.getNetwork(), function(err, giftCards) { + if (giftCards) { + homeIntegrationsService.register(homeItem); + } else { + nextStepsService.register(nextStepItem); + } + }); + }; + register(); + return root; }); diff --git a/src/js/services/bitpayCardService.js b/src/js/services/bitpayCardService.js index d26b35c9b..a2ce4aa52 100644 --- a/src/js/services/bitpayCardService.js +++ b/src/js/services/bitpayCardService.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.services').factory('bitpayCardService', function($log, $rootScope, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService, bitpayService) { +angular.module('copayApp.services').factory('bitpayCardService', function($log, $rootScope, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService, bitpayService, nextStepsService) { var root = {}; var _setError = function(msg, e) { @@ -10,7 +10,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, }; var _processTransactions = function(invoices, history) { - invoices = invoices || []; + invoices = invoices ||  []; for (var i = 0; i < invoices.length; i++) { var matched = false; for (var j = 0; j < history.length; j++) { @@ -22,8 +22,8 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, if (!matched && isInvoiceLessThanOneDayOld) { var isInvoiceUnderpaid = invoices[i].exceptionStatus === 'paidPartial'; - if(['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) >= 0 - || (invoices[i].status === 'invalid' || isInvoiceUnderpaid)) { + if (['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) >= 0 || + (invoices[i].status === 'invalid' || isInvoiceUnderpaid)) { history.unshift({ timestamp: new Date(invoices[i].invoiceTime), @@ -39,7 +39,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, return history; }; - root.fetchBitpayDebitCards = function(apiContext, cb) { + root.sync = function(apiContext, cb) { var json = { method: 'getDebitCards' }; @@ -47,65 +47,109 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, 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}); + + var cards = []; + + lodash.each(data.data.data, function(x) { + var n = {}; + + if (!x.eid || !x.id || !x.lastFourDigits || !x.token) { + $log.warn('BAD data from Bitpay card' + JSON.stringify(x)); + return; + } + + n.eid = x.eid; + n.id = x.id; + n.lastFourDigits = x.lastFourDigits; + n.token = x.token; + cards.push(n); + }); + + storageService.setBitpayDebitCards(bitpayService.getEnvironment().network, apiContext.pairData.email, cards, function(err) { + register(); + + return cb(err, cards); + }); }, function(data) { return cb(_setError('BitPay Card Error: Get Debit Cards', data)); }); }; - root.getHistory = function(cardId, params, cb) { + // opts: range + root.getHistory = function(cardId, opts, cb) { var invoices, transactions; - params = params || {}; + opts = opts || {}; + var json = { method: 'getInvoiceHistory', - params: JSON.stringify(params) + params: JSON.stringify(opts) }; + appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) { if (err) return cb(err); - root.getBitpayDebitCards(function(err, data) { + + root.getCards(function(err, data) { if (err) return cb(err); - var card = lodash.find(data, {id : cardId}); - if (!card) return cb(_setError('Card not found')); + var card = lodash.find(data, { + id: cardId + }); + + if (!card) + return cb(_setError('Card not found')); + // Get invoices 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'); + + if (lodash.isEmpty(invoices)) + $log.info('No invoices'); + json = { method: 'getTransactionHistory', - params: JSON.stringify(params) + params: JSON.stringify(opts) }; // Get transactions list 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); + + root.setLastKnownBalance(cardId, transactions.currentCardBalance, function() {}); + return cb(data.data.error, transactions); }, function(data) { 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)); }); }); }); }; - root.topUp = function(cardId, params, cb) { - params = params || {}; + root.topUp = function(cardId, opts, cb) { + opts = opts || {}; var json = { method: 'generateTopUpInvoice', - params: JSON.stringify(params) + params: JSON.stringify(opts) }; appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) { if (err) return cb(err); - root.getBitpayDebitCards(function(err, data) { + + root.getCards(function(err, data) { if (err) return cb(err); - var card = lodash.find(data, {id : cardId}); - if (!card) return cb(_setError('Card not found')); + + var card = lodash.find(data, { + id: cardId + }); + + if (!card) + return cb(_setError('Card not found')); + bitpayService.post('/api/v2/' + card.token, json, function(data) { $log.info('BitPay TopUp: SUCCESS'); - if(data.data.error) { + if (data.data.error) { return cb(data.data.error); } else { return cb(null, data.data.data.invoice); @@ -126,75 +170,50 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, }); }; - root.getBitpayDebitCards = function(cb) { - storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, function(err, data) { - if (err) return cb(err); - if (lodash.isString(data)) { - data = JSON.parse(data); - } - data = data || {}; - return cb(null, data); - }); + // get all cards, for all accounts. + root.getCards = function(cb) { + storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, cb); }; - root.setBitpayDebitCards = function(data, cb) { - data = JSON.stringify(data); - storageService.setBitpayDebitCards(bitpayService.getEnvironment().network, data, function(err) { - if (err) return cb(err); + root.getLastKnownBalance = function(cardId, cb) { + storageService.getBalanceCache(cardId, cb); + }; + + root.addLastKnownBalance = function(card, cb) { + var now = Math.floor(Date.now() / 1000); + var showRange = 600; // 10min; + + root.getLastKnownBalance(card.eid, function(err, data) { + if (data) { + data = JSON.parse(data); + card.balance = data.balance; + card.updatedOn = (data.updatedOn < now - showRange) ? data.updatedOn : null; + } return cb(); }); }; - root.getBitpayDebitCardsHistory = function(cardId, cb) { - storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment().network, function(err, data) { - if (err) return cb(err); - if (lodash.isString(data)) { - data = JSON.parse(data); - } - data = data || {}; - if (cardId) data = data[cardId]; - return cb(null, data); - }); + root.setLastKnownBalance = function(cardId, balance, cb) { + + storageService.setBalanceCache(cardId, { + balance: balance, + updatedOn: Math.floor(Date.now() / 1000), + }, cb); }; - root.setBitpayDebitCardsHistory = function(cardId, data, opts, cb) { - storageService.getBitpayDebitCardsHistory(bitpayService.getEnvironment().network, function(err, oldData) { - if (lodash.isString(oldData)) { - oldData = JSON.parse(oldData); - } - if (lodash.isString(data)) { - data = JSON.parse(data); - } - var inv = oldData || {}; - inv[cardId] = data; - if (opts && opts.remove) { - delete(inv[cardId]); - } - inv = JSON.stringify(inv); - storageService.setBitpayDebitCardsHistory(bitpayService.getEnvironment().network, inv, function(err) { - return cb(err); - }); - }); - }; - - root.remove = function(card, cb) { - storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, card, function(err) { + root.remove = function(cardId, cb) { + storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, cardId, function(err) { if (err) { $log.error('Error removing BitPay debit card: ' + err); - // Continue, try to remove/cleanup card history + return cb(err); } - storageService.removeBitpayDebitCardHistory(bitpayService.getEnvironment().network, card, function(err) { - if (err) { - $log.error('Error removing BitPay debit card transaction history: ' + err); - return cb(err); - } - $log.info('Successfully removed BitPay debit card'); - return cb(); - }); + register(); + storageService.removeBalanceCache(cardId, cb); }); }; + root.getRates = function(currency, cb) { bitpayService.get('/rates/' + currency, function(data) { $log.info('BitPay Get Rates: SUCCESS'); @@ -204,6 +223,39 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, }); }; + + root.get = function(opts, cb) { + root.getCards(function(err, cards) { + if (err) return; + + if (lodash.isEmpty(cards)) { + return cb(); + } + + if (opts.cardId) { + cards = lodash.filter(cards, function(x) { + return opts.cardId == x.eid; + }); + } + + // Async, no problem + lodash.each(cards, function(x) { + + root.addLastKnownBalance(x, function() {}); + + // async refresh + if (!opts.noRefresh) { + root.getHistory(x.id, {}, function(err, data) { + if (err) return; + root.addLastKnownBalance(x, function() {}); + }); + } + }); + + return cb(null, cards); + }); + }; + /* * CONSTANTS */ @@ -1241,6 +1293,25 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, 'bp002': 'default' }; + var nextStepItem = { + name: 'bitpaycard', + title: 'Add Bitpay VISA Card', + icon: 'icon-bitpay-card', + sref: 'tabs.bitpayCardIntro', + }; + + + var register = function() { + root.getCards(function(err, cards) { + if (lodash.isEmpty(cards)) { + nextStepsService.register(nextStepItem); + } else { + nextStepsService.unregister(nextStepItem.name); + } + }); + }; + + register(); return root; }); diff --git a/src/js/services/bitpayService.js b/src/js/services/bitpayService.js index 6cf7f1eab..743d58922 100644 --- a/src/js/services/bitpayService.js +++ b/src/js/services/bitpayService.js @@ -136,13 +136,10 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt }; var setBitpayAccount = function(accountData, cb) { - var data = JSON.stringify(accountData); - storageService.setBitpayAccount(root.getEnvironment().network, data, function(err) { - if (err) return cb(err); - return cb(); - }); + storageService.setBitpayAccount(root.getEnvironment().network, accountData, cb); }; + var _get = function(endpoint) { return { method: 'GET', diff --git a/src/js/services/buyAndSellService.js b/src/js/services/buyAndSellService.js new file mode 100644 index 000000000..bbfbe6632 --- /dev/null +++ b/src/js/services/buyAndSellService.js @@ -0,0 +1,73 @@ +'use strict'; + +angular.module('copayApp.services').factory('buyAndSellService', function($log, nextStepsService, lodash, $ionicScrollDelegate, $timeout) { + var root = {}; + var services = []; + var linkedServices = []; + + root.update = function() { + + var newLinked = lodash.filter(services, function(x) { + return x.linked; + }); + + // This is to preserve linkedServices pointer + while (linkedServices.length) + linkedServices.pop(); + + while (newLinked.length) + linkedServices.push(newLinked.pop()); + // + + $log.debug('buyAndSell Service, updating nextSteps. linked/total: ' + linkedServices.length + '/' + services.length); + + if (linkedServices.length == 0) { + nextStepsService.register({ + title: 'Buy and Sell', + name: 'buyandsell', + icon: 'icon-buy-bitcoin', + sref: 'tabs.buyandsell', + }); + } else { + nextStepsService.unregister({ + name: 'buyandsell', + }); + }; + + $timeout(function() { + $ionicScrollDelegate.resize(); + }, 10); + }; + + var updateNextStepsDebunced = lodash.debounce(root.update, 1000); + + root.register = function(serviceInfo) { + services.push(serviceInfo); + $log.info('Adding Buy and Sell service:' + serviceInfo.name + ' linked:' + serviceInfo.linked); + updateNextStepsDebunced(); + }; + + + root.updateLink = function(name, linked) { + var service = lodash.find(services, function(x) { + return x.name == name; + }); + $log.info('Updating Buy and Sell service:' + name + ' linked:' + linked); + service.linked = linked + + root.update(); + }; + + + root.get = function() { + return services; + }; + + + root.getLinked = function() { + return linkedServices; + }; + + + return root; +}); diff --git a/src/js/services/bwcError.js b/src/js/services/bwcError.js index 6ea0b91d5..9740af88e 100644 --- a/src/js/services/bwcError.js +++ b/src/js/services/bwcError.js @@ -44,7 +44,7 @@ angular.module('copayApp.services') body = gettextCatalog.getString('Insufficient funds'); break; case 'CONNECTION_ERROR': - body = gettextCatalog.getString('Network connection error'); + body = gettextCatalog.getString('Network error'); break; case 'NOT_FOUND': body = gettextCatalog.getString('Wallet service not found'); diff --git a/src/js/services/coinbaseService.js b/src/js/services/coinbaseService.js index 7ab78909f..5a53149c6 100644 --- a/src/js/services/coinbaseService.js +++ b/src/js/services/coinbaseService.js @@ -1,37 +1,32 @@ 'use strict'; -angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService) { +angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService, buyAndSellService, $rootScope) { var root = {}; var credentials = {}; var isCordova = platformInfo.isCordova; var isNW = platformInfo.isNW; + var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova; - root.priceSensitivity = [ - { - value: 0.5, - name: '0.5%' - }, - { - value: 1, - name: '1%' - }, - { - value: 2, - name: '2%' - }, - { - value: 5, - name: '5%' - }, - { - value: 10, - name: '10%' - } - ]; + root.priceSensitivity = [{ + value: 0.5, + name: '0.5%' + }, { + value: 1, + name: '1%' + }, { + value: 2, + name: '2%' + }, { + value: 5, + name: '5%' + }, { + value: 10, + name: '10%' + }]; root.selectedPriceSensitivity = root.priceSensitivity[1]; - root.setCredentials = function() { + var setCredentials = function() { if (!$window.externalServices || !$window.externalServices.coinbase) { return; @@ -46,19 +41,19 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ credentials.NETWORK = 'livenet'; // Coinbase permissions - credentials.SCOPE = '' - + 'wallet:accounts:read,' - + 'wallet:addresses:read,' - + 'wallet:addresses:create,' - + 'wallet:user:read,' - + 'wallet:user:email,' - + 'wallet:buys:read,' - + 'wallet:buys:create,' - + 'wallet:sells:read,' - + 'wallet:sells:create,' - + 'wallet:transactions:read,' - + 'wallet:transactions:send,' - + 'wallet:payment-methods:read'; + credentials.SCOPE = '' + + 'wallet:accounts:read,' + + 'wallet:addresses:read,' + + 'wallet:addresses:create,' + + 'wallet:user:read,' + + 'wallet:user:email,' + + 'wallet:buys:read,' + + 'wallet:buys:create,' + + 'wallet:sells:read,' + + 'wallet:sells:create,' + + 'wallet:transactions:read,' + + 'wallet:transactions:send,' + + 'wallet:payment-methods:read'; // NW has a bug with Window Object if (isCordova) { @@ -72,8 +67,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ credentials.API = coinbase.sandbox.api; credentials.CLIENT_ID = coinbase.sandbox.client_id; credentials.CLIENT_SECRET = coinbase.sandbox.client_secret; - } - else { + } else { credentials.HOST = coinbase.production.host; credentials.API = coinbase.production.api; credentials.CLIENT_ID = coinbase.production.client_id; @@ -85,6 +79,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ if (data && data.access_token && data.refresh_token) { storageService.setCoinbaseToken(credentials.NETWORK, data.access_token, function() { storageService.setCoinbaseRefreshToken(credentials.NETWORK, data.refresh_token, function() { + buyAndSellService.updateLink('coinbase', true); return cb(null, data.access_token); }); }); @@ -107,8 +102,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ root.getAvailableCurrency = function() { var config = configService.getSync().wallet.settings; // ONLY "USD" - switch(config.alternativeIsoCode) { - default : return 'USD' + switch (config.alternativeIsoCode) { + default: return 'USD' }; }; @@ -141,14 +136,14 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ } root.getOauthCodeUrl = function() { - return credentials.HOST - + '/oauth/authorize?response_type=code&client_id=' - + credentials.CLIENT_ID - + '&redirect_uri=' - + credentials.REDIRECT_URI - + '&state=SECURE_RANDOM&scope=' - + credentials.SCOPE - + '&meta[send_limit_amount]=1000&meta[send_limit_currency]=USD&meta[send_limit_period]=day'; + return credentials.HOST + + '/oauth/authorize?response_type=code&client_id=' + + credentials.CLIENT_ID + + '&redirect_uri=' + + credentials.REDIRECT_URI + + '&state=SECURE_RANDOM&scope=' + + credentials.SCOPE + + '&meta[send_limit_amount]=1000&meta[send_limit_currency]=USD&meta[send_limit_period]=day'; }; root.getToken = function(code, cb) { @@ -160,9 +155,9 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ 'Accept': 'application/json' }, data: { - grant_type : 'authorization_code', + grant_type: 'authorization_code', code: code, - client_id : credentials.CLIENT_ID, + client_id: credentials.CLIENT_ID, client_secret: credentials.CLIENT_SECRET, redirect_uri: credentials.REDIRECT_URI } @@ -171,7 +166,6 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ $http(req).then(function(data) { $log.info('Coinbase Authorization Access Token: SUCCESS'); // Show pending task from the UI - storageService.setNextStep('BuyAndSell', 'true', function(err) {}); _afterTokenReceived(data.data, cb); }, function(data) { $log.error('Coinbase Authorization Access Token: ERROR ' + data.statusText); @@ -188,8 +182,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ 'Accept': 'application/json' }, data: { - grant_type : 'refresh_token', - client_id : credentials.CLIENT_ID, + grant_type: 'refresh_token', + client_id: credentials.CLIENT_ID, client_secret: credentials.CLIENT_SECRET, redirect_uri: credentials.REDIRECT_URI, refresh_token: refreshToken @@ -219,6 +213,19 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ }); }; + root.isActive = function(cb) { + + if (isWindowsPhoneApp) + return cb(); + + if (lodash.isEmpty(credentials.CLIENT_ID)) + return cb(); + + storageService.getCoinbaseToken(credentials.NETWORK, function(err, accessToken) { + return cb(err, !!accessToken); + }); + } + root.init = lodash.throttle(function(cb) { if (lodash.isEmpty(credentials.CLIENT_ID)) { return cb('Coinbase is Disabled'); @@ -238,7 +245,10 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ if (err) return cb(err); _getMainAccountId(newToken, function(err, accountId) { if (err) return cb(err); - return cb(null, {accessToken: newToken, accountId: accountId}); + return cb(null, { + accessToken: newToken, + accountId: accountId + }); }); }); }); @@ -246,7 +256,10 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ return cb(err); } } else { - return cb(null, {accessToken: accessToken, accountId: accountId}); + return cb(null, { + accessToken: accessToken, + accountId: accountId + }); } }); } @@ -410,7 +423,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ var data = { amount: data.amount, currency: data.currency, - payment_method: data.payment_method || null, + payment_method: data.payment_method ||  null, commit: data.commit || false, quote: data.quote || false }; @@ -598,9 +611,10 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ }; root.updatePendingTransactions = lodash.throttle(function() { - $log.debug('Updating pending transactions...'); - root.setCredentials(); - var pendingTransactions = { data: {} }; + $log.debug('Updating coinbase pending transactions...'); + var pendingTransactions = { + data: {} + }; root.getPendingTransactions(pendingTransactions); }, 20000); @@ -707,6 +721,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ root.logout = function(cb) { storageService.removeCoinbaseToken(credentials.NETWORK, function() { + buyAndSellService.updateLink('coinbase', false); storageService.removeCoinbaseRefreshToken(credentials.NETWORK, function() { storageService.removeCoinbaseTxs(credentials.NETWORK, function() { return cb(); @@ -715,6 +730,33 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $ }); }; - return root; + var register = function() { + root.isActive(function(err, isActive){ + if (err) return; + + buyAndSellService.register({ + name: 'coinbase', + logo: 'img/coinbase-logo.png', + location: '33 Countries', + sref: 'tabs.buyandsell.coinbase', + configSref: 'tabs.preferences.coinbase', + linked: isActive, + }); + }); + }; + + setCredentials(); + register(); + + $rootScope.$on('bwsEvent', function(e, walletId, type, n) { + if (type == 'NewBlock' && n && n.data && n.data.network == 'livenet') { + root.isActive(function(err,isActive){ + // Update Coinbase + if (isActive) + root.updatePendingTransactions(); + }); + } + }); + return root; }); diff --git a/src/js/services/configService.js b/src/js/services/configService.js index fd6b3206a..3b2ef3826 100644 --- a/src/js/services/configService.js +++ b/src/js/services/configService.js @@ -43,26 +43,14 @@ angular.module('copayApp.services').factory('configService', function(storageSer }, // External services - glidera: { - enabled: true, - testnet: false - }, - - coinbaseV2: true, - - bitpayCard: { - enabled: true - }, - - amazon: { - enabled: true - }, - - recentTransactions: { enabled: true, }, + hideNextSteps: { + enabled: false, + }, + rates: { url: 'https://insight.bitpay.com:443/api/rates', }, @@ -191,18 +179,11 @@ angular.module('copayApp.services').factory('configService', function(storageSer if (!configCache.wallet.settings.unitCode) { configCache.wallet.settings.unitCode = defaultConfig.wallet.settings.unitCode; } - if (!configCache.glidera) { - configCache.glidera = defaultConfig.glidera; - } - if (!configCache.coinbaseV2) { - configCache.coinbaseV2 = defaultConfig.coinbaseV2; - } - if (!configCache.amazon) { - configCache.amazon = defaultConfig.amazon; - } - if (!configCache.bitpayCard) { - configCache.bitpayCard = defaultConfig.bitpayCard; + + if (!configCache.hideNextSteps) { + configCache.hideNextSteps = defaultConfig.hideNextSteps; } + if (!configCache.recentTransactions) { configCache.recentTransactions = defaultConfig.recentTransactions; } diff --git a/src/js/services/fileStorage.js b/src/js/services/fileStorage.js index b93bde832..643effe83 100644 --- a/src/js/services/fileStorage.js +++ b/src/js/services/fileStorage.js @@ -93,7 +93,7 @@ angular.module('copayApp.services') if (lodash.isObject(v)) v = JSON.stringify(v); - if (!lodash.isString(v)) { + if (v && !lodash.isString(v)) { v = v.toString(); } diff --git a/src/js/services/glideraService.js b/src/js/services/glideraService.js index 94efde61a..987ae5845 100644 --- a/src/js/services/glideraService.js +++ b/src/js/services/glideraService.js @@ -1,11 +1,12 @@ 'use strict'; -angular.module('copayApp.services').factory('glideraService', function($http, $log, $window, platformInfo, storageService) { +angular.module('copayApp.services').factory('glideraService', function($http, $log, $window, platformInfo, storageService, buyAndSellService) { var root = {}; var credentials = {}; var isCordova = platformInfo.isCordova; + var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova; - var _setCredentials = function() { + var setCredentials = function() { if (!$window.externalServices || !$window.externalServices.glidera) { return; } @@ -17,6 +18,7 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l * Production: 'livenet' */ credentials.NETWORK = 'livenet'; + //credentials.NETWORK = 'testnet'; if (credentials.NETWORK == 'testnet') { credentials.HOST = glidera.sandbox.host; @@ -44,7 +46,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l }; root.getEnvironment = function() { - _setCredentials(); return credentials.NETWORK; }; @@ -57,19 +58,17 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l } root.getOauthCodeUrl = function() { - _setCredentials(); return credentials.HOST + '/oauth2/auth?response_type=code&client_id=' + credentials.CLIENT_ID + '&redirect_uri=' + credentials.REDIRECT_URI; }; root.removeToken = function(cb) { - _setCredentials(); storageService.removeGlideraToken(credentials.NETWORK, function() { + buyAndSellService.updateLink('glidera', false); return cb(); }); }; root.getToken = function(code, cb) { - _setCredentials(); var req = { method: 'POST', url: credentials.HOST + '/api/v1/oauth/token', @@ -98,7 +97,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l }; var _get = function(endpoint, token) { - _setCredentials(); return { method: 'GET', url: credentials.HOST + '/api/v1' + endpoint, @@ -216,7 +214,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l }; var _post = function(endpoint, token, twoFaCode, data) { - _setCredentials(); return { method: 'POST', url: credentials.HOST + '/api/v1' + endpoint, @@ -293,7 +290,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l }; root.init = function(accessToken, cb) { - _setCredentials(); $log.debug('Init Glidera...'); var glidera = { @@ -312,6 +308,8 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l getToken(function(err, accessToken) { if (err || !accessToken) return cb(); else { + buyAndSellService.updateLink('glidera', true); + root.getAccessTokenPermissions(accessToken, function(err, p) { if (err) { return cb(err); @@ -325,6 +323,25 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l }); }; - return root; + var register = function() { + if (isWindowsPhoneApp) return; + + storageService.getGlideraToken(credentials.NETWORK, function(err, token) { + if (err) return cb(err); + + buyAndSellService.register({ + name: 'glidera', + logo: 'img/glidera-logo.png', + location: 'US Only', + sref: 'tabs.buyandsell.glidera', + configSref: 'tabs.preferences.glidera', + linked: !!token, + }); + }); + }; + + setCredentials(); + register(); + return root; }); diff --git a/src/js/services/homeIntegrations.js b/src/js/services/homeIntegrations.js new file mode 100644 index 000000000..d99588a80 --- /dev/null +++ b/src/js/services/homeIntegrations.js @@ -0,0 +1,23 @@ + 'use strict'; + angular.module('copayApp.services').factory('', function(configService, $log) { + var root = {}; + var services = []; + + root.register = function(serviceInfo) { + $log.info('Adding homeIntegration entry:' + serviceInfo.name); + services.push(serviceInfo); + }; + + root.unregister = function(serviceName) { + services = lodash.filter(services, function(x) { + return x.name != serviceName + }); + }; + + root.get = function() { + return services; + }; + + return root; + + }); diff --git a/src/js/services/homeIntegrationsService.js b/src/js/services/homeIntegrationsService.js new file mode 100644 index 000000000..5f3b067ff --- /dev/null +++ b/src/js/services/homeIntegrationsService.js @@ -0,0 +1,23 @@ + 'use strict'; + angular.module('copayApp.services').factory('homeIntegrationsService', function(configService, $log) { + var root = {}; + var services = []; + + root.register = function(serviceInfo) { + $log.info('Adding home Integrations entry:' + serviceInfo.name); + services.push(serviceInfo); + }; + + root.unregister = function(serviceName) { + services = lodash.filter(services, function(x) { + return x.name != serviceName + }); + }; + + root.get = function() { + return services; + }; + + return root; + + }); diff --git a/src/js/services/localStorage.js b/src/js/services/localStorage.js index e6327e300..c772b7eef 100644 --- a/src/js/services/localStorage.js +++ b/src/js/services/localStorage.js @@ -43,16 +43,17 @@ angular.module('copayApp.services') }; root.set = function(k, v, cb) { + + if (lodash.isObject(v)) { + v = JSON.stringify(v); + } + if (v && !lodash.isString(v)) { + v = v.toString(); + } + if (isChromeApp || isNW) { var obj = {}; - if (lodash.isObject(v)) { - v = JSON.stringify(v); - } - if (!lodash.isString(v)) { - v = v.toString(); - } - obj[k] = v; chrome.storage.local.set(obj, cb); @@ -60,7 +61,6 @@ angular.module('copayApp.services') ls.setItem(k, v); return cb(); } - }; root.remove = function(k, cb) { diff --git a/src/js/services/nextStepsService.js b/src/js/services/nextStepsService.js new file mode 100644 index 000000000..566efd7be --- /dev/null +++ b/src/js/services/nextStepsService.js @@ -0,0 +1,40 @@ + 'use strict'; + angular.module('copayApp.services').factory('nextStepsService', function(configService, $log, lodash) { + var root = {}; + var services = []; + + root.register = function(serviceInfo) { + $log.info('Adding NextSteps entry:' + serviceInfo.name); + + if (!lodash.find(services, function(x) { + return x.name == serviceInfo.name; + })) { + services.push(serviceInfo); + } + }; + + root.unregister = function(serviceName) { + + var newS = lodash.filter(services, function(x) { + return x.name != serviceName; + }); + + // Found? + if (newS.length == services.length) return; + + $log.info('Removing NextSteps entry:' + serviceName); + // This is to preserve services pointer + while (services.length) + services.pop(); + + while (newS.length) + services.push(newS.pop()); + }; + + root.get = function() { + return services; + }; + + return root; + + }); diff --git a/src/js/services/openURL.js b/src/js/services/openURL.js index adc4c6f72..ff01dc78c 100644 --- a/src/js/services/openURL.js +++ b/src/js/services/openURL.js @@ -84,6 +84,7 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop $log.debug('Registering Browser handlers base:' + base); navigator.registerProtocolHandler('bitcoin', url, 'Copay Bitcoin Handler'); navigator.registerProtocolHandler('web+copay', url, 'Copay Wallet Handler'); + navigator.registerProtocolHandler('web+bitpay', url, 'Bitpay Wallet Handler'); } } }; diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js index a9416fcf3..964b48eac 100644 --- a/src/js/services/profileService.js +++ b/src/js/services/profileService.js @@ -747,6 +747,31 @@ angular.module('copayApp.services') storageService.storeProfile(root.profile, cb); }; + root.getLastKnownBalance = function(wid, cb) { + storageService.getBalanceCache(wid, cb); + }; + + root.addLastKnownBalance = function(wallet, cb) { + var now = Math.floor(Date.now() / 1000); + var showRange = 600; // 10min; + + root.getLastKnownBalance(wallet.id, function(err, data) { + if (data) { + data = JSON.parse(data); + wallet.cachedBalance = data.balance; + wallet.cachedBalanceUpdatedOn = (data.updatedOn < now - showRange) ? data.updatedOn : null; + } + return cb(); + }); + }; + + root.setLastKnownBalance = function(wid, balance, cb) { + storageService.setBalanceCache(wid, { + balance: balance, + updatedOn: Math.floor(Date.now() / 1000), + }, cb); + }; + root.getWallets = function(opts) { if (opts && !lodash.isObject(opts)) @@ -780,6 +805,12 @@ angular.module('copayApp.services') }); } else {} + // Add cached balance async + lodash.each(ret, function(x) { + root.addLastKnownBalance(x, function() {}); + }); + + return lodash.sortBy(ret, [ function(x) { @@ -796,7 +827,7 @@ angular.module('copayApp.services') root.getNotifications = function(opts, cb) { opts = opts || {}; - var TIME_STAMP = 60 * 60 * 6; + var TIME_STAMP = 60 * 60 * 6; var MAX = 30; var typeFilter = { diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 526c65a87..5f78725a3 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -24,6 +24,9 @@ angular.module('copayApp.services') }, cb); }; + // This is only used in Copay, we used to encrypt profile + // using device's UUID. + var decryptOnMobile = function(text, cb) { var json; try { @@ -74,312 +77,8 @@ angular.module('copayApp.services') }); }; - //////////////////////////////////////////////////////////////////////////// - // - // UPGRADING STORAGE - // - // 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: - // - // `x` is the name of the storage key - // `key` is the name of the storage key being upgraded - // `network` is one of 'livenet', 'testnet' - // - // 2. Add the storage key to `_upgraders` object using the name of the key as the `_upgrader` object key - // with the value being the name of the upgrade function (e.g., _upgrade_x). In order to avoid conflicts - // 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. - // - // 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 - '02_bitpayAccounts' : _upgrade_bitpayAccounts, // 2016-12: Upgrade bitpayAccounts-x to bitpayAccounts-v2-x - '03_bitpayAccounts-v2' : _validate_bitpayAccounts_v2 // 2017-01: Validate keys on bitpayAccounts-v2-x, remove if not valid - }; - - function _upgrade_bitpayDebitCards(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 || {}; - _00_setBitpayDebitCards(network, data, function(err) { - if (err) return cb(err); - storage.remove(key, function() { - cb(null, 'replaced with \'bitpayAccounts\''); - }); - }); - } else { - cb(); - } - }); - }; - - function _upgrade_bitpayCardCredentials(key, network, cb) { - key += '-' + network; - storage.get(key, function(err, data) { - if (err) return cb(err); - if (data != null) { - // Needs upgrade - _01_setAppIdentity(network, data, function(err) { - if (err) return cb(err); - storage.remove(key, function() { - cb(null, 'replaced with \'appIdentity\''); - }); - }); - } else { - cb(); - } - }); - }; - - 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 = ''; - _asyncEach(Object.keys(data), function(key, callback) { - // Keys are account emails - if (!data[key]['bitpayApi-' + network]) { - // Needs upgrade - upgraded += ' ' + key; - var acctData = { - acct: data[key], - token: data[key]['bitpayDebitCards-' + network].token, - email: key - }; - _02_setBitpayAccount(network, acctData, function(err) { - if (err) return cb(err); - - _02_setBitpayDebitCards(network, data[key]['bitpayDebitCards-' + network], function(err) { - if (err) return cb(err); - callback(); - }); - }); - } - callback(); - }, function() { - // done - // Remove obsolete key. - storage.remove('bitpayAccounts-' + network, function() { - if (upgraded.length > 0) { - cb(null, 'upgraded to \'bitpayAccounts-v2-' + network + '\':' + upgraded); - } else { - cb(); - } - }); - }); - }); - }; - - function _validate_bitpayAccounts_v2(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 verified = ''; - var toRemove = []; - _asyncEach(Object.keys(data), function(key, callback) { - // Verify account API data - if (!data[key]['bitpayApi-' + network] || - !data[key]['bitpayApi-' + network].token) { - // Invalid entry - one or more keys are missing - toRemove.push(key); - return callback(); - } - // Verify debit cards - if (Array.isArray(data[key]['bitpayDebitCards-' + network])) { - for (var i = 0; i < data[key]['bitpayDebitCards-' + network].length; i++) { - if (!data[key]['bitpayDebitCards-' + network][i].token || - !data[key]['bitpayDebitCards-' + network][i].eid || - !data[key]['bitpayDebitCards-' + network][i].id || - !data[key]['bitpayDebitCards-' + network][i].lastFourDigits) { - // Invalid entry - one or more keys are missing - toRemove.push(key); - return callback(); - } - } - } - verified += ' ' + key; - return callback(); - }, function() { - // done, remove invalid account entrys - if (toRemove.length > 0) { - var removed = ''; - for (var i = 0; i < toRemove.length; i++) { - removed += ' ' + toRemove[i]; - delete data[toRemove[i]]; - } - storage.set('bitpayAccounts-v2-' + network, JSON.stringify(data), function(err) { - if (err) return cb(err); - // Ensure next step for cards is visible - storage.get('bitpayAccounts-v2-' + network, function(err, data) { - if (err) return cb(err); - if (lodash.isEmpty(data)) { - root.removeNextStep('BitpayCard', function(err) {}); - } - }); - cb(null, 'removed invalid account records, please re-pair cards for these accounts:' + removed + '; ' + - 'the following accounts validated OK: ' + (verified.length > 0 ? verified : 'none')); - }); - } else { - cb(null, (verified.length > 0 ? 'accounts OK: ' + verified : 'no accounts found')); - } - }); - }); - }; - // - //////////////////////////////////////////////////////////////////////////// - // - // 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 || !data.acct) return cb('No account to set'); - storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { - if (err) return cb(err); - if (lodash.isString(bitpayAccounts)) { - bitpayAccounts = JSON.parse(bitpayAccounts); - } - bitpayAccounts = bitpayAccounts || {}; - bitpayAccounts[data.email] = data.acct; - bitpayAccounts[data.email]['bitpayApi-' + network] = {}; - bitpayAccounts[data.email]['bitpayApi-' + network].token = data.token; - storage.set('bitpayAccounts-v2-' + 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('Cannot set cards: no account to set'); - storage.get('bitpayAccounts-v2-' + 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-v2-' + network, JSON.stringify(bitpayAccounts), cb); - }); - }; - // - //////////////////////////////////////////////////////////////////////////// - - // IMPORTANT: This function is designed to block execution until it completes. - // Ideally storage should not be used until it has been verified. - root.verify = function(cb) { - _upgrade(function(err) { - cb(err); - }); - }; - - function _handleUpgradeError(key, err) { - $log.error('Failed to upgrade storage for \'' + key + '\': ' + err); - }; - - function _handleUpgradeSuccess(key, msg) { - $log.info('Storage upgraded for \'' + key + '\': ' + msg); - }; - - // IMPORTANT: This function is designed to block execution until it completes. - // Ideally storage should not be used until it has been verified. - function _upgrade(cb) { - var errorCount = 0; - var errorMessage = undefined; - var keys = Object.keys(_upgraders).sort(); - var networks = ['livenet', 'testnet']; - _asyncEach(keys, function(key, callback_keys) { - _asyncEach(networks, function(network, callback_networks) { - var storagekey = key.split('_')[1]; - _upgraders[key](storagekey, network, function(err, msg) { - if (err) { - _handleUpgradeError(storagekey + '-' + network, err); - errorCount++; - errorMessage = errorCount + ' storage upgrade failures'; - } - if (msg) _handleUpgradeSuccess(storagekey + '-' + network, msg); - callback_networks(); - }); - }, function() { - // done - networks - callback_keys(); - }); - }, function() { - //done - keys - cb(errorMessage); - }); - }; - - function _asyncEach(iterableList, callback, done) { - var i = -1; - var length = iterableList.length; - - function loop() { - i++; - if (i === length) { - done(); - return; - } else if (i < length) { - callback(iterableList[i], loop); - } else { - return; - } - } - loop(); - }; - + // This is only use in Copay, for very old instalations + // in which we use to use localStorage instead of fileStorage root.tryToMigrate = function(cb) { if (!shouldUseFileStorage) return cb(); @@ -645,51 +344,35 @@ angular.module('copayApp.services') storage.remove('coinbaseTxs-' + network, cb); }; - root.setBitpayDebitCardsHistory = function(network, data, cb) { - storage.set('bitpayDebitCardsHistory-' + network, data, cb); + root.setBalanceCache = function(cardId, data, cb) { + storage.set('balanceCache-' + cardId, data, cb); }; - root.getBitpayDebitCardsHistory = function(network, cb) { - storage.get('bitpayDebitCardsHistory-' + network, cb); + root.getBalanceCache = function(cardId, cb) { + storage.get('balanceCache-' + cardId, cb); }; - root.removeBitpayDebitCardHistory = function(network, card, cb) { - root.getBitpayDebitCardsHistory(network, function(err, data) { - if (err) return cb(err); - if (lodash.isString(data)) { - data = JSON.parse(data); - } - data = data || {}; - delete data[card.eid]; - root.setBitpayDebitCardsHistory(network, JSON.stringify(data), cb); - }); + root.removeBalanceCache = function(cardId, cb) { + storage.remove('balanceCache-' + cardId, cb); }; - // 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); - } - data = data || {}; - if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set'); - storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { + root.setBitpayDebitCards = function(network, email, cards, cb) { + + root.getBitpayAccounts(network, function(err, allAccounts) { if (err) return cb(err); - if (lodash.isString(bitpayAccounts)) { - bitpayAccounts = JSON.parse(bitpayAccounts); + + if (!allAccounts[email]) { + return cb('Cannot set cards for unknown account ' + email); } - bitpayAccounts = bitpayAccounts || {}; - bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; - bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards; - storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb); + + allAccounts[email].cards = cards; + storage.set('bitpayAccounts-v2-' + network, allAccounts, cb); }); }; @@ -702,24 +385,24 @@ angular.module('copayApp.services') // email: account email // ] root.getBitpayDebitCards = function(network, cb) { - storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { - if (lodash.isString(bitpayAccounts)) { - bitpayAccounts = JSON.parse(bitpayAccounts); - } - bitpayAccounts = bitpayAccounts || {}; - var cards = []; - _asyncEach(Object.keys(bitpayAccounts), function(email, callback) { - // For the UI, add the account email to the card object. - var acctCards = bitpayAccounts[email]['bitpayDebitCards-' + network] || []; - for (var i = 0; i < acctCards.length; i++) { - acctCards[i].email = email; - } - cards = cards.concat(acctCards); - callback(); - }, function() { - // done - cb(err, cards); + + root.getBitpayAccounts(network, function(err, allAccounts) { + if (err) return cb(err); + + var allCards = []; + + lodash.each(allAccounts, function(account, email) { + + // Add account's email to card list, for convenience + var cards = lodash.clone(account.cards); + lodash.each(cards, function(x) { + x.email = email; + }); + + allCards = allCards.concat(cards); }); + + return cb(null, allCards); }); }; @@ -729,95 +412,101 @@ angular.module('copayApp.services') // lastFourDigits: card number // token: card token // } - root.removeBitpayDebitCard = function(network, card, cb) { - if (lodash.isString(card)) { - card = JSON.parse(card); - } - card = card || {}; - if (lodash.isEmpty(card) || !card.eid) return cb('No card to remove'); - storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { - if (err) cb(err); - if (lodash.isString(bitpayAccounts)) { - bitpayAccounts = JSON.parse(bitpayAccounts); - } - bitpayAccounts = bitpayAccounts || {}; - _asyncEach(Object.keys(bitpayAccounts), function(email, callback) { - 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. - root.getBitpayDebitCards(network, function(err, cards) { - if (err) cb(err); - if (cards.length == 0) { - root.removeNextStep('BitpayCard', callback()); - } else { - callback() - } - }); - }); - }, function() { - // done - cb(); - }); - }); - }; + root.removeBitpayDebitCard = function(network, cardEid, cb) { - // 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-v2-' + 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-v2-' + network, JSON.stringify(bitpayAccounts), cb); + root.getBitpayAccounts(network, function(err, allAccounts){ + + lodash.each(allAccounts, function(account){ + account.cards = lodash.reject(account.cards, { + 'eid': cardEid + }); + }); + + storage.set('bitpayAccounts-v2-' + network, allAccounts, cb); }); }; // cb(err, accounts) // accounts: { // email_1: { - // bitpayApi-: { - // token: account token - // } - // bitpayDebitCards-: { + // token: account token + // cards: { // // } // } // ... // email_n: { - // bitpayApi-: { - // token: account token - // } - // bitpayDebitCards-: { + // token: account token + // cards: { // // } // } // } + // root.getBitpayAccounts = function(network, cb) { - storage.get('bitpayAccounts-v2-' + network, function(err, bitpayAccounts) { + storage.get('bitpayAccounts-v2-' + network, function(err, allAccountsStr) { if (err) return cb(err); - if (lodash.isString(bitpayAccounts)) { - bitpayAccounts = JSON.parse(bitpayAccounts); - } - cb(err, bitpayAccounts); + + var allAccounts = {}; + try { + allAccounts = JSON.parse(allAccountsStr); + } catch (e) {}; + + var anyMigration; + + lodash.each(allAccounts, function(account, email) { + + // Migrate old `'bitpayApi-' + network` key, if exists + if (!account.token && account['bitpayApi-' + network].token) { + + $log.info('Migrating all bitpayApi-network branch'); + account.token = account['bitpayApi-' + network].token; + account.cards = lodash.clone(account['bitpayApi-' + network].cards); + if (!account.cards) { + account.cards = lodash.clone(account['bitpayDebitCards-' + network]); + } + + delete account['bitpayDebitCards-' + network]; + delete account['bitpayApi-' + network]; + anyMigration = true; + + } + }); + + if (anyMigration) { + storage.set('bitpayAccounts-v2-' + network, allAccounts, function(){ + return cb(err, allAccounts); + }); + } else + return cb(err, allAccounts); + + }); + }; + + + // data: { + // email: account email + // token: account token + // } + root.setBitpayAccount = function(network, data, cb) { + + if (!lodash.isObject(data) || !data.email || !data.token) + return cb('No account to set'); + + var email = data.email; + var token = data.token; + + + root.getBitpayAccounts(network, function(err, allAccounts) { + if (err) return cb(err); + + var account = allAccounts[email] || {}; + account.token = token; + + allAccounts[email] = account; + + $log.info('Storing BitPay accounts with new account:' + email); + storage.set('bitpayAccounts-v2-' + network, allAccounts, cb); }); }; @@ -828,10 +517,7 @@ angular.module('copayApp.services') root.getAppIdentity = function(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); + cb(err, JSON.parse(data || '{}')); }); }; diff --git a/www/views/advancedSettings.html b/www/views/advancedSettings.html index afbbf764b..03632f52c 100644 --- a/www/views/advancedSettings.html +++ b/www/views/advancedSettings.html @@ -7,24 +7,6 @@
-
Enabled Integrations
- - - Enable BitPay Card Integration - - - - Enable Amazon Integration - - - - Enable Glidera Service - - - - Enable Coinbase Service - -
Wallet Operation
@@ -41,6 +23,13 @@ If enabled, the Recent Transactions card - a list of transactions occuring across all wallets - will appear in the Home tab.
+ + + Hide Next Steps Card + + + +
diff --git a/www/views/bitpayCard.html b/www/views/bitpayCard.html index 46371ff19..8ea540d74 100644 --- a/www/views/bitpayCard.html +++ b/www/views/bitpayCard.html @@ -2,7 +2,7 @@ - BitPay Visa® Card ({{card.lastFourDigits}}) + BitPay Visa® Card ({{bitpayCard.lastFourDigits}}) @@ -14,8 +14,13 @@
-
-
${{bitpayCard.bitpayCardCurrentBalance}}
+
+
${{bitpayCard.balance}}
+ +
+ {{bitpayCard.updatedOn * 1000 | amTimeAgo}} +
+ @@ -23,7 +28,7 @@ {{'Add Funds'|translate}}
-
+
...
diff --git a/www/views/buyandsell.html b/www/views/buyandsell.html index 7bd442f9e..0feb987b1 100644 --- a/www/views/buyandsell.html +++ b/www/views/buyandsell.html @@ -14,17 +14,12 @@
Buy and sell bitcoin directly from your wallet by connecting your exchange accounts.
- - - - 33 Countries - - - - - US Only - - +
+
+ + {{service.location}} + +
diff --git a/www/views/includes/bitpayCardsCard.html b/www/views/includes/bitpayCardsCard.html new file mode 100644 index 000000000..b3b909c6e --- /dev/null +++ b/www/views/includes/bitpayCardsCard.html @@ -0,0 +1,21 @@ + + + + diff --git a/www/views/includes/buyAndSellCard.html b/www/views/includes/buyAndSellCard.html new file mode 100644 index 000000000..a736b77fa --- /dev/null +++ b/www/views/includes/buyAndSellCard.html @@ -0,0 +1,13 @@ + +
+
+ Buy & Sell Bitcoin + +
+ +
diff --git a/www/views/includes/homeIntegrations.html b/www/views/includes/homeIntegrations.html new file mode 100644 index 000000000..34ae4702d --- /dev/null +++ b/www/views/includes/homeIntegrations.html @@ -0,0 +1,20 @@ + + + diff --git a/www/views/includes/nextSteps.html b/www/views/includes/nextSteps.html new file mode 100644 index 000000000..caa6d1bf5 --- /dev/null +++ b/www/views/includes/nextSteps.html @@ -0,0 +1,19 @@ +
+
+ Next steps + + +
+ +
+ diff --git a/www/views/tab-home.html b/www/views/tab-home.html index 643d5c465..c750802b1 100644 --- a/www/views/tab-home.html +++ b/www/views/tab-home.html @@ -90,7 +90,8 @@ Incomplete - {{wallet.status.totalBalanceStr}} + {{wallet.status.totalBalanceStr ? wallet.status.totalBalanceStr : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' · ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} + [Balance Hidden] {{wallet.m}}-of-{{wallet.n}} @@ -104,84 +105,10 @@
- - - -
-
- Buy & Sell Bitcoin - -
- -
- - - - +
+
+
+
diff --git a/www/views/tab-settings.html b/www/views/tab-settings.html index 868388c56..3474e80fd 100644 --- a/www/views/tab-settings.html +++ b/www/views/tab-settings.html @@ -117,7 +117,7 @@
@@ -126,19 +126,13 @@
- - - - - - - - - +