From d4ac5f9551e540fe601e44f1ee008bbb8f99ea1e Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Sun, 22 Jan 2017 15:22:59 -0500 Subject: [PATCH 1/9] Synchronize all storage upgrade. --- src/js/services/storageService.js | 48 ++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index aafc4b759..883bcb54a 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -156,7 +156,7 @@ angular.module('copayApp.services') } data = data || {}; var upgraded = ''; - Object.keys(data).forEach(function(key) { + Object.asyncEach(data, function(key, callback) { // Keys are account emails if (!data[key]['bitpayApi-' + network]) { // Needs upgrade @@ -170,17 +170,20 @@ angular.module('copayApp.services') _02_setBitpayDebitCards(network, data[key]['bitpayDebitCards-' + network], function(err) { if (err) return cb(err); + callback(); }); }); } - }); - // Remove obsolete key. - storage.remove('bitpayAccounts-' + network, function() { - if (upgraded.length > 0) { - cb(null, 'upgraded to \'bitpayAccounts-v2-' + network + '\':' + upgraded); - } else { - cb(); - } + }, function() { + // done + // Remove obsolete key. + storage.remove('bitpayAccounts-' + network, function() { + if (upgraded.length > 0) { + cb(null, 'upgraded to \'bitpayAccounts-v2-' + network + '\':' + upgraded); + } else { + cb(); + } + }); }); }); }; @@ -267,12 +270,14 @@ angular.module('copayApp.services') $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']; - keys.forEach(function(key) { + Object.asyncEach(keys, function(key, callback) { networks.forEach(function(network) { var storagekey = key.split('_')[1]; _upgraders[key](storagekey, network, function(err, msg) { @@ -282,10 +287,31 @@ angular.module('copayApp.services') errorMessage = errorCount + ' storage upgrade failures'; } if (msg) _handleUpgradeSuccess(storagekey + '-' + network, msg); + callback(); }); }); + }, function() { + //done + cb(errorMessage); }); - cb(errorMessage); + }; + + Object.asyncEach = function(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(); }; root.tryToMigrate = function(cb) { From 6747e0eae5248b3a09bfdae19a08e1a1c6f510e6 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Mon, 23 Jan 2017 15:22:10 -0500 Subject: [PATCH 2/9] Do not add to Object prototype, use private local function. --- src/js/services/storageService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 883bcb54a..16e715632 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -156,7 +156,7 @@ angular.module('copayApp.services') } data = data || {}; var upgraded = ''; - Object.asyncEach(data, function(key, callback) { + _asyncEach(data, function(key, callback) { // Keys are account emails if (!data[key]['bitpayApi-' + network]) { // Needs upgrade @@ -277,7 +277,7 @@ angular.module('copayApp.services') var errorMessage = undefined; var keys = Object.keys(_upgraders).sort(); var networks = ['livenet', 'testnet']; - Object.asyncEach(keys, function(key, callback) { + _asyncEach(keys, function(key, callback) { networks.forEach(function(network) { var storagekey = key.split('_')[1]; _upgraders[key](storagekey, network, function(err, msg) { @@ -296,7 +296,7 @@ angular.module('copayApp.services') }); }; - Object.asyncEach = function(iterableList, callback, done) { + function _asyncEach(iterableList, callback, done) { var i = -1; var length = iterableList.length; From 17210d52bb4bda74099ccaf554ea02af7d36a19c Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Mon, 23 Jan 2017 16:15:40 -0500 Subject: [PATCH 3/9] Implements a validator for bitpayAccounts-v2 --- src/js/services/storageService.js | 45 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 16e715632..6e82d5aa7 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -104,7 +104,8 @@ 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: Upgrade tpayAccounts-x to bitpayAccounts-v2-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) { @@ -156,7 +157,7 @@ angular.module('copayApp.services') } data = data || {}; var upgraded = ''; - _asyncEach(data, function(key, callback) { + _asyncEach(data, function(key, callback) { // Keys are account emails if (!data[key]['bitpayApi-' + network]) { // Needs upgrade @@ -187,6 +188,46 @@ angular.module('copayApp.services') }); }); }; + + 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) { + if (!data[key]['bitpayApi-' + network] || + !data[key]['bitpayDebitCards-' + network]) { + // Invalid entry - 'bitpayApi-' key missing + // Invalid entry - 'bitpayDebitCards-' key missing + toRemove.push(key); + } else { + 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); + 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 : '')); + } + }); + }); + }; // //////////////////////////////////////////////////////////////////////////// // From 91b6d2774b6844af8149c0b15425186e12cc9094 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Mon, 23 Jan 2017 17:01:47 -0500 Subject: [PATCH 4/9] Perform deep object verification on bitpayAccounts-v2. Ensure that nextStep for bitpay card is visible when no cards are cached. --- src/js/controllers/tab-home.js | 2 ++ src/js/services/storageService.js | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index dcd495d89..ea37847b5 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -289,6 +289,8 @@ angular.module('copayApp.controllers').controller('tabHomeController', if (err) return; if (lodash.isEmpty(data)) { $scope.bitpayCards = null; + // Ensure next step for cards is visible + storageService.setNextStep('BitpayCard', 'false', function(err) {}); return; } $scope.bitpayCards = data; diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 6e82d5aa7..5bcf7bd3a 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -200,14 +200,27 @@ angular.module('copayApp.services') var verified = ''; var toRemove = []; _asyncEach(Object.keys(data), function(key, callback) { + // Verify account API data if (!data[key]['bitpayApi-' + network] || - !data[key]['bitpayDebitCards-' + network]) { - // Invalid entry - 'bitpayApi-' key missing - // Invalid entry - 'bitpayDebitCards-' key missing + !data[key]['bitpayApi-' + network].token) { + // Invalid entry - one or more keys are missing toRemove.push(key); - } else { - verified += ' ' + 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 From 495d9191318fcb46ba6e6548b5de14e5597609eb Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Mon, 23 Jan 2017 17:16:08 -0500 Subject: [PATCH 5/9] Adds missing attribute reference for network. --- src/js/controllers/bitpayCardIntro.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/bitpayCardIntro.js b/src/js/controllers/bitpayCardIntro.js index 13ec6208d..d829b918f 100644 --- a/src/js/controllers/bitpayCardIntro.js +++ b/src/js/controllers/bitpayCardIntro.js @@ -44,7 +44,7 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f } }); } else { - appIdentityService.getIdentity(bitpayService.getEnvironment(), function(err, appIdentity) { + appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) { if (err) popupService.showAlert(null, err); else $log.info('App identity: OK'); }); From 46fa9b46af7829805ce71adc60a568135a8eeeec Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Tue, 24 Jan 2017 08:52:58 -0500 Subject: [PATCH 6/9] Iterable is keys, not an object. --- src/js/services/storageService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 5bcf7bd3a..55a675df3 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -157,7 +157,7 @@ angular.module('copayApp.services') } data = data || {}; var upgraded = ''; - _asyncEach(data, function(key, callback) { + _asyncEach(Object.keys(data), function(key, callback) { // Keys are account emails if (!data[key]['bitpayApi-' + network]) { // Needs upgrade From e9f30fa05384cc6bd66edd460f68813ce8b5a506 Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Tue, 24 Jan 2017 09:42:30 -0500 Subject: [PATCH 7/9] Remove setNextStep call from card cache setup. --- src/js/controllers/tab-home.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index ea37847b5..dcd495d89 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -289,8 +289,6 @@ angular.module('copayApp.controllers').controller('tabHomeController', if (err) return; if (lodash.isEmpty(data)) { $scope.bitpayCards = null; - // Ensure next step for cards is visible - storageService.setNextStep('BitpayCard', 'false', function(err) {}); return; } $scope.bitpayCards = data; From 56bff1db411176533db69923cb95a25c7bcca22b Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Tue, 24 Jan 2017 10:08:31 -0500 Subject: [PATCH 8/9] Upgrade only one account at a time. --- src/js/services/storageService.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 55a675df3..520747d0c 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -280,11 +280,12 @@ angular.module('copayApp.services') 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); + // Ensure only the specified account is set (data.email) + var upgradedBitpayAccount = {}; + upgradedBitpayAccount[data.email] = bitpayAccounts[data.email] || {}; + upgradedBitpayAccount[data.email]['bitpayApi-' + network] = {}; + upgradedBitpayAccount[data.email]['bitpayApi-' + network].token = data.token; + storage.set('bitpayAccounts-v2-' + network, JSON.stringify(upgradedBitpayAccount), cb); }); }; From 484e09e4ff8c874b44934c90916b433013acb55d Mon Sep 17 00:00:00 2001 From: Andy Phillipson Date: Tue, 24 Jan 2017 10:33:08 -0500 Subject: [PATCH 9/9] Fix upgrade accounts so they don't overwrite each other. --- src/js/services/storageService.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 520747d0c..a9ebe98e2 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -163,6 +163,7 @@ angular.module('copayApp.services') // Needs upgrade upgraded += ' ' + key; var acctData = { + acct: data[key], token: data[key]['bitpayDebitCards-' + network].token, email: key }; @@ -274,18 +275,17 @@ angular.module('copayApp.services') 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 (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); } - // Ensure only the specified account is set (data.email) - var upgradedBitpayAccount = {}; - upgradedBitpayAccount[data.email] = bitpayAccounts[data.email] || {}; - upgradedBitpayAccount[data.email]['bitpayApi-' + network] = {}; - upgradedBitpayAccount[data.email]['bitpayApi-' + network].token = data.token; - storage.set('bitpayAccounts-v2-' + network, JSON.stringify(upgradedBitpayAccount), cb); + 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); }); };