diff --git a/Gruntfile.js b/Gruntfile.js
index 7a1c4ca00..2ea3bf006 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -165,11 +165,22 @@ module.exports = function(grunt) {
src: [
'src/js/app.js',
'src/js/routes.js',
+
'src/js/directives/*.js',
+ '!src/js/directives/*.spec.js',
+
'src/js/filters/*.js',
+ '!src/js/filters/*.spec.js',
+
'src/js/models/*.js',
+ '!src/js/models/*.spec.js',
+
'src/js/services/*.js',
+ '!src/js/services/*.spec.js',
+
'src/js/controllers/**/*.js',
+ '!src/js/controllers/**/*.spec.js',
+
'src/js/translations.js',
'src/js/appConfig.js',
'src/js/externalServices.js',
diff --git a/app-template/config-template.xml b/app-template/config-template.xml
index 8031c8110..39b67d212 100644
--- a/app-template/config-template.xml
+++ b/app-template/config-template.xml
@@ -72,6 +72,8 @@
+
+
diff --git a/app-template/package-template.json b/app-template/package-template.json
index a0d4228df..53236022e 100644
--- a/app-template/package-template.json
+++ b/app-template/package-template.json
@@ -117,8 +117,8 @@
"sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && $ANDROID_HOME/build-tools/27.0.1/zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk",
"apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare",
"apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare",
- "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare",
- "test": "echo \"no package tests configured\"",
+ "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare",
+ "test": "karma start test/karma.conf.js --single-run",
"clean": "trash platforms && trash plugins && cordova prepare",
"unstage-package": "git reset package.json",
"clean-all": "git clean -dfx"
@@ -127,6 +127,10 @@
"cordova": "^6.3.1",
"grunt": "^1.0.1",
"ionic": "^3.6.0",
+ "jasmine-core": "^3.1.0",
+ "karma": "^2.0.2",
+ "karma-chrome-launcher": "^2.2.0",
+ "karma-jasmine": "^1.1.2",
"trash-cli": "^1.4.0",
"lodash": "^4.17.4",
"pre-commit": "^1.1.3"
diff --git a/src/js/controllers/tab-scan.js b/src/js/controllers/tab-scan.js
index 83d6f733e..a96591a25 100644
--- a/src/js/controllers/tab-scan.js
+++ b/src/js/controllers/tab-scan.js
@@ -60,11 +60,14 @@ angular.module('copayApp.controllers').controller('tabScanController', function(
});
$scope.$on("$ionicView.afterEnter", function() {
- // try initializing and refreshing status any time the view is entered
- if(!scannerService.isInitialized()) {
- scannerService.gentleInitialize();
+ var capabilities = scannerService.getCapabilities();
+ if (capabilities.hasPermission) {
+ // try initializing and refreshing status any time the view is entered
+ if(!scannerService.isInitialized()) {
+ scannerService.gentleInitialize();
+ }
+ activate();
}
- activate();
});
function activate(){
diff --git a/src/js/models/profile.js b/src/js/models/profile.js
index 74b0c33b9..7690d1c2d 100644
--- a/src/js/models/profile.js
+++ b/src/js/models/profile.js
@@ -9,12 +9,12 @@ function Profile() {
this.version = '1.0.0';
};
-Profile.create = function(opts) {
- opts = opts || {};
+Profile.create = function(appVersion) {
var x = new Profile();
+ x.appVersion = appVersion;
x.createdOn = Date.now();
- x.credentials = opts.credentials || [];
+ x.credentials = [];
x.disclaimerAccepted = true;
x.checked = {};
return x;
@@ -23,6 +23,7 @@ Profile.create = function(opts) {
Profile.fromObj = function(obj) {
var x = new Profile();
+ x.appVersion = obj.appVersion;
x.createdOn = obj.createdOn;
x.credentials = obj.credentials;
x.disclaimerAccepted = obj.disclaimerAccepted;
@@ -62,6 +63,39 @@ Profile.prototype.isDeviceChecked = function(ua) {
return this.checkedUA == ua;
};
+/**
+ *
+ * @param {Profile} other
+ */
+Profile.prototype.merge = function(other) {
+
+ var newCredentials = [];
+ var otherCredentialsLength = other.credentials.length;
+ var thisProfile = this;
+
+ other.credentials.forEach(function(otherCredential) {
+ var credentialExists = false;
+ thisProfile.credentials.forEach(function(thisCredential) {
+ if (otherCredential.walletId === thisCredential.walletId) {
+ credentialExists = true;
+ }
+ });
+ if (!credentialExists) {
+ newCredentials.push(otherCredential);
+ }
+ });
+
+ Array.prototype.push.apply(this.credentials, newCredentials);
+};
+
+/**
+ * It's a simple operation, but it means that all the profile logic stays
+ * in this file.
+ * @param {string} appVersion - ie "4.11.0"
+ */
+Profile.prototype.setAppVersion = function(appVersion) {
+ this.appVersion = appVersion;
+}
Profile.prototype.setChecked = function(ua, walletId) {
if (this.checkedUA != ua) {
diff --git a/src/js/services/configService.js b/src/js/services/configService.js
index 1e46da03a..e8ed93d88 100644
--- a/src/js/services/configService.js
+++ b/src/js/services/configService.js
@@ -115,8 +115,8 @@ angular.module('copayApp.services').factory('configService', function(storageSer
bitcoinAlias: 'btc',
bitcoinCashAlias: 'bch',
- bitcoinWalletColor: '#fab915', // Observatory
- bitcoinCashWalletColor: '#26B03C', // Dollar Green
+ bitcoinWalletColor: '#535353', // Dark Grey
+ bitcoinCashWalletColor: '#eeb640', // Observatory
homeSectionIsHidden: {
services: false
diff --git a/src/js/services/desktopSecureStorageService.js b/src/js/services/desktopSecureStorageService.js
new file mode 100644
index 000000000..6e148da2c
--- /dev/null
+++ b/src/js/services/desktopSecureStorageService.js
@@ -0,0 +1,6 @@
+'use strict';
+
+angular.module('copayApp.services').factory('desktopSecureStorageService', function($log) {
+ // Placeholder
+ return {};
+});
\ No newline at end of file
diff --git a/src/js/services/localStorage.js b/src/js/services/localStorage.js
index c772b7eef..ba0db231b 100644
--- a/src/js/services/localStorage.js
+++ b/src/js/services/localStorage.js
@@ -20,8 +20,7 @@ angular.module('copayApp.services')
if (isChromeApp || isNW) {
chrome.storage.local.get(k,
function(data) {
- //TODO check for errors
- return cb(null, data[k]);
+ return cb(chrome.runtime.lastError, data[k]);
});
} else {
return cb(null, ls.getItem(k));
@@ -56,16 +55,24 @@ angular.module('copayApp.services')
obj[k] = v;
- chrome.storage.local.set(obj, cb);
+ chrome.storage.local.set(obj, function(){
+ cb(chrome.runtime.lastError);
+ });
} else {
- ls.setItem(k, v);
+ try {
+ ls.setItem(k, v);
+ } catch (e) {
+ return cb(e);
+ }
return cb();
}
};
root.remove = function(k, cb) {
if (isChromeApp || isNW) {
- chrome.storage.local.remove(k, cb);
+ chrome.storage.local.remove(k, function(){
+ cb(chrome.runtime.lastError);
+ });
} else {
ls.removeItem(k);
return cb();
diff --git a/src/js/services/mobileSecureStorageService.js b/src/js/services/mobileSecureStorageService.js
new file mode 100644
index 000000000..f9994fdf8
--- /dev/null
+++ b/src/js/services/mobileSecureStorageService.js
@@ -0,0 +1,88 @@
+'use strict';
+
+angular.module('copayApp.services').factory('mobileSecureStorageService', function($log, appConfigService, platformInfo) {
+ var root = {};
+
+ var isReady = false;
+ var initialisationFailed = false;
+ var pending = [];
+
+ var storage = null;
+
+ if (platformInfo.isCordova) {
+ storage = new cordova.plugins.SecureStorage(
+ function () {
+ isReady = true;
+ for (var i = 0; i < pending.length; i++) {
+ pending[i]();
+ }
+ pending = [];
+ },
+ function (error) {
+ initialisationFailed = true;
+ },
+ appConfigService.packageNameId);
+ }
+
+ root.get = function(key, cb) {
+
+ if (!platformInfo.isMobile) {
+ cb(new Error('mobileSecureStorageService is only available on mobile.'));
+ return;
+ }
+
+ if (!isReady) {
+ if (initialisationFailed) {
+ cb(new Error('mobileSecureStorageService initialisation failed.'));
+ } else {
+ pending.push(function(){ root.get(key, cb); });
+ }
+ return;
+ }
+
+ storage.get(
+ function (value) {
+ cb(null, value);
+ },
+ function (error) {
+ $log.debug('mss get failed. ' + error);
+ if (error.message === 'Failure in SecureStorage.get() - The specified item could not be found in the keychain' || // iOS
+ error.message === 'Key [_SS_profile] not found.') { // Android
+ // The callback expects no error, but also no value, if it cannot be found.
+ cb(null, null);
+ } else {
+ cb(new Error(error));
+ }
+ },
+ key);
+ };
+
+ root.set = function(key, value, cb) {
+
+ if (!platformInfo.isMobile) {
+ cb(new Error('mobileSecureStorageService is only available on mobile.'));
+ }
+
+ if (!isReady) {
+ if (initialisationFailed) {
+ cb(new Error('mobileSecureStorageService initialisation failed.'));
+ } else {
+ pending.push(function(){ root.set(key, value, cb); });
+ }
+ return;
+ }
+
+ storage.set(
+ function (value) {
+ cb();
+ },
+ function (error) {
+ cb(new Error(error));
+ },
+ key, value);
+
+ };
+
+ return root;
+});
+
diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js
index dac88169f..25f2a6852 100644
--- a/src/js/services/profileService.js
+++ b/src/js/services/profileService.js
@@ -706,7 +706,7 @@ angular.module('copayApp.services')
configService.get(function(err) {
if (err) $log.debug(err);
- var p = Profile.create();
+ var p = Profile.create(appConfigService.version);
storageService.storeNewProfile(p, function(err) {
if (err) return cb(err);
root.bindProfile(p, function(err) {
diff --git a/src/js/services/rateService.spec.js b/src/js/services/rateService.spec.js
new file mode 100644
index 000000000..35397eb7f
--- /dev/null
+++ b/src/js/services/rateService.spec.js
@@ -0,0 +1,53 @@
+describe('rateService', function() {
+ var $httpBackend, rateService, requestHandler;
+
+ beforeEach(function() {
+ module('ngLodash');
+ module('copayApp.services');
+
+ inject(function($injector){
+ $httpBackend = $injector.get('$httpBackend');
+
+ requestHandler = $httpBackend.when('GET', 'https://www.bitcoin.com/special/rates.json')
+ .respond([
+ {
+ "code": "BTC",
+ "name": "Bitcoin",
+ "rate": 1
+ },
+ {
+ "code": "BCH_BTC",
+ "name": "Bitcoin Cash",
+ "rate": 6.739397
+ },
+ {
+ "code": "USD",
+ "name": "US Dollar",
+ "rate": 7602.04
+ }
+ ]);
+
+ rateService = $injector.get('rateService');
+
+ $httpBackend.flush();
+ });
+ });
+
+ afterEach(function() {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('get rates', function() {
+
+ $httpBackend.expectGET('https://www.bitcoin.com/special/rates.json');
+
+ rateService.updateRates();
+
+ $httpBackend.flush();
+
+ var usdRate = rateService.getRate('USD');
+
+ expect(usdRate).toEqual(7602.04);
+ });
+});
\ No newline at end of file
diff --git a/src/js/services/secureStorageService.js b/src/js/services/secureStorageService.js
new file mode 100644
index 000000000..c066109c2
--- /dev/null
+++ b/src/js/services/secureStorageService.js
@@ -0,0 +1,32 @@
+'use strict';
+
+angular.module('copayApp.services').factory('secureStorageService', function(desktopSecureStorageService, localStorageService, $log, mobileSecureStorageService, platformInfo) {
+ var root = {};
+
+ // To make wrong code look wrong
+ function alteredKeyIndicatingDesireForSecureStorage(key) {
+ return key + ":desiredSecure";
+ }
+
+ root.get = function(k, cb) {
+ if (platformInfo.isMobile) {
+ mobileSecureStorageService.get(k, cb);
+ } else if (platformInfo.isNW) {
+ desktopSecureStorageService.get(k, cb);
+ } else { // Browser
+ localStorageService.get(alteredKeyIndicatingDesireForSecureStorage(k), cb);
+ }
+ }
+
+ root.set = function(k, v, cb) {
+ if (platformInfo.isMobile) {
+ mobileSecureStorageService.set(k, v, cb);
+ } else if (platformInfo.isNW) {
+ desktopSecureStorageService.set(k, v, cb);
+ } else { // Browser
+ localStorageService.set(alteredKeyIndicatingDesireForSecureStorage(k), v, cb);
+ }
+ }
+
+ return root;
+});
\ No newline at end of file
diff --git a/src/js/services/secureStorageService.spec.js b/src/js/services/secureStorageService.spec.js
new file mode 100644
index 000000000..abfa5d947
--- /dev/null
+++ b/src/js/services/secureStorageService.spec.js
@@ -0,0 +1,308 @@
+describe('secureStorageService in browser', function(){
+ var localStorage,
+ sss;
+
+ beforeEach(function(){
+ module('ngLodash');
+ module('copayApp.services');
+
+ localStorage = {
+ get: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ platformInfoStub = {
+ };
+
+ module(function($provide) {
+ $provide.value('localStorageService', localStorage);
+ $provide.value('platformInfo', platformInfoStub);
+ });
+
+ inject(function($injector){
+ sss = $injector.get('secureStorageService');
+ });
+
+ });
+
+ it('get fails', function() {
+ var error, key, result;
+
+ localStorage.get.and.callFake(function(k, cb){
+ key = k;
+ cb(new Error('Get error.'), null);
+ });
+
+ sss.get('a1234', function(e, res) {
+ error = e;
+ result = res;
+ });
+
+ expect(error.message).toBe('Get error.');
+ expect(result).toBeFalsy();
+ expect(key).toBe('a1234:desiredSecure');
+ });
+
+ it('get succeeds', function() {
+ var error, key, result;
+
+ localStorage.get.and.callFake(function(k, cb){
+ key = k;
+ cb(null, 'The result 1.');
+ });
+
+ sss.get('a123', function(e, res) {
+ error = e;
+ result = res;
+ });
+
+ expect(error).toBeFalsy();
+ expect(result).toBe('The result 1.');
+ expect(key).toBe('a123:desiredSecure');
+ });
+
+ it('set fails', function() {
+ var error, key, value;
+
+ localStorage.set.and.callFake(function(k, v, cb){
+ key = k;
+ value = v;
+ cb(new Error('Set error.'));
+ });
+
+ sss.set('a12345', 'The value 1.', function(e) {
+ error = e;
+ });
+
+ expect(error.message).toBe('Set error.');
+ expect(key).toBe('a12345:desiredSecure');
+ expect(value).toBe('The value 1.');
+ });
+
+ it('set succeeds', function() {
+ var error, key, value;
+
+ localStorage.set.and.callFake(function(k, v, cb){
+ key = k;
+ value = v;
+ cb(null);
+ });
+
+ sss.set('ab123', 'The value 2.', function(e) {
+ error = e;
+ });
+
+ expect(error).toBeFalsy();
+ expect(key).toBe('ab123:desiredSecure');
+ expect(value).toBe('The value 2.')
+ });
+
+});
+
+
+describe('secureStorageService on desktop', function(){
+ var desktopSss,
+ sss;
+
+ beforeEach(function(){
+ module('ngLodash');
+ module('copayApp.services');
+
+ desktopSss = {
+ get: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ platformInfoStub = {
+ isNW: true
+ };
+
+ module(function($provide) {
+ $provide.value('desktopSecureStorageService', desktopSss);
+ $provide.value('platformInfo', platformInfoStub);
+ });
+
+ inject(function($injector){
+ sss = $injector.get('secureStorageService');
+ });
+
+ });
+
+ it('get fails', function() {
+ var error, key, result;
+
+ desktopSss.get.and.callFake(function(k, cb){
+ key = k;
+ cb(new Error('Get error.'), null);
+ });
+
+ sss.get('a1234', function(e, res) {
+ error = e;
+ result = res;
+ });
+
+ expect(error.message).toBe('Get error.');
+ expect(result).toBeFalsy();
+ expect(key).toBe('a1234');
+ });
+
+ it('get succeeds', function() {
+ var error, key, result;
+
+ desktopSss.get.and.callFake(function(k, cb){
+ key = k;
+ cb(null, 'The result 1.');
+ });
+
+ sss.get('a123', function(e, res) {
+ error = e;
+ result = res;
+ });
+
+ expect(error).toBeFalsy();
+ expect(result).toBe('The result 1.');
+ expect(key).toBe('a123');
+ });
+
+ it('set fails', function() {
+ var error, key, value;
+
+ desktopSss.set.and.callFake(function(k, v, cb){
+ key = k;
+ value = v;
+ cb(new Error('Set error.'));
+ });
+
+ sss.set('a12345', 'The value 1.', function(e) {
+ error = e;
+ });
+
+ expect(error.message).toBe('Set error.');
+ expect(key).toBe('a12345');
+ expect(value).toBe('The value 1.');
+ });
+
+ it('set succeeds', function() {
+ var error, key, value;
+
+ desktopSss.set.and.callFake(function(k, v, cb){
+ key = k;
+ value = v;
+ cb(null);
+ });
+
+ sss.set('ab123', 'The value 2.', function(e) {
+ error = e;
+ });
+
+ expect(error).toBeFalsy();
+ expect(key).toBe('ab123');
+ expect(value).toBe('The value 2.')
+ });
+
+});
+
+describe('secureStorageService on mobile', function(){
+ var mobileSss,
+ sss;
+
+ beforeEach(function(){
+ module('ngLodash');
+ module('copayApp.services');
+
+ mobileSss = {
+ get: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ platformInfoStub = {
+ isMobile: true
+ };
+
+ module(function($provide) {
+ $provide.value('mobileSecureStorageService', mobileSss);
+ $provide.value('platformInfo', platformInfoStub);
+ });
+
+ inject(function($injector){
+ sss = $injector.get('secureStorageService');
+ });
+
+ });
+
+ it('get fails', function() {
+ var error, key, result;
+
+ mobileSss.get.and.callFake(function(k, cb){
+ key = k;
+ cb(new Error('Get error.'), null);
+ });
+
+ sss.get('a1234', function(e, res) {
+ error = e;
+ result = res;
+ });
+
+ expect(error.message).toBe('Get error.');
+ expect(result).toBeFalsy();
+ expect(key).toBe('a1234');
+ });
+
+ it('get succeeds', function() {
+ var error, key, result;
+
+ mobileSss.get.and.callFake(function(k, cb){
+ key = k;
+ cb(null, 'The result 1.');
+ });
+
+ sss.get('a123', function(e, res) {
+ error = e;
+ result = res;
+ });
+
+ expect(error).toBeFalsy();
+ expect(result).toBe('The result 1.');
+ expect(key).toBe('a123');
+ });
+
+ it('set fails', function() {
+ var error, key, value;
+
+ mobileSss.set.and.callFake(function(k, v, cb){
+ key = k;
+ value = v;
+ cb(new Error('Set error.'));
+ });
+
+ sss.set('a12345', 'The value 1.', function(e) {
+ error = e;
+ });
+
+ expect(error.message).toBe('Set error.');
+ expect(key).toBe('a12345');
+ expect(value).toBe('The value 1.');
+ });
+
+ it('set succeeds', function() {
+ var error, key, value;
+
+ mobileSss.set.and.callFake(function(k, v, cb){
+ key = k;
+ value = v;
+ cb(null);
+ });
+
+ sss.set('ab123', 'The value 2.', function(e) {
+ error = e;
+ });
+
+ expect(error).toBeFalsy();
+ expect(key).toBe('ab123');
+ expect(value).toBe('The value 2.')
+ });
+
+});
+
+
+
\ No newline at end of file
diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js
index 3d1ecfeef..a2d85950b 100644
--- a/src/js/services/storageService.js
+++ b/src/js/services/storageService.js
@@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services')
- .factory('storageService', function(logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, $timeout) {
+ .factory('storageService', function(appConfigService, logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, secureStorageService, $timeout) {
var root = {};
var storage;
@@ -116,34 +116,138 @@ angular.module('copayApp.services')
};
root.storeNewProfile = function(profile, cb) {
- storage.create('profile', profile.toObj(), cb);
+ root.storeProfile(profile, cb);
};
root.storeProfile = function(profile, cb) {
- storage.set('profile', profile.toObj(), cb);
+ var profileString = profile.toObj();
+ if (platformInfo.isNW) {
+ storage.set('profile', profileString, cb);
+ } else {
+ secureStorageService.set('profile', profileString, cb);
+ }
};
- root.getProfile = function(cb) {
- storage.get('profile', function(err, str) {
- if (err || !str)
- return cb(err);
+ /**
+ * @callback getProfileCallback
+ * @param {Error} error - falsy if profile not found.
+ * @param {Profile} profile - falsy if error or profile not found.
+ */
- decryptOnMobile(str, function(err, str) {
- if (err) return cb(err);
- var p, err;
- try {
- p = Profile.fromString(str);
- } catch (e) {
- $log.debug('Could not read profile:', e);
- err = new Error('Could not read profile:' + p);
+
+ /**
+ * @param {Error} error
+ * @param {String} profileStr - containing the profile
+ * @param {getProfileCallback} cb
+ */
+ function _onOldProfileRetrieved(error, profileStr, cb) {
+ if (error) {
+ return cb(error, null);
+ }
+
+ if (!profileStr) {
+ // No profiles found. No errors either.
+ return cb(null, null);
+ }
+
+ decryptOnMobile(profileStr, function(decryptErr, decryptedStr) {
+ if (decryptErr) return cb(decryptErr, null);
+ var profile;
+ try {
+ profile = Profile.fromString(decryptedStr);
+ } catch (e) {
+ $log.debug('Could not read profile:', e);
+ return(new Error('Could not read profile.'), null);
+ }
+ cb(null, profile)
+ });
+ }
+
+
+
+ /**
+ *
+ * @param {Profile} oldProfile
+ * @param {Profile} secureProfile - may be falsy if no secure profile found.
+ * @param {getProfileCallback} cb
+ */
+ function _migrateProfiles(oldProfile, secureProfile, cb) {
+ var newProfile;
+
+ if (secureProfile) {
+ secureProfile.merge(oldProfile);
+ newProfile = secureProfile;
+ } else {
+ newProfile = oldProfile;
+ newProfile.setAppVersion(appConfigService.version);
+ }
+
+ root.storeNewProfile(newProfile, function(storeErr) {
+ if (storeErr) {
+ cb(storeErr, null);
+ return;
+ }
+
+ storage.remove('profile', function(removeErr){
+ if (removeErr) {
+ cb(removeErr, null);
+ return;
}
- return cb(err, p);
+
+ cb(null, newProfile);
});
+
});
};
- root.deleteProfile = function(cb) {
- storage.remove('profile', cb);
+ /**
+ *
+ * @param {getProfileCallback} cb
+ */
+ root.getProfile = function(cb) {
+ if (platformInfo.isNW) {
+ storage.get('profile', function(getErr, getStr) {
+ _onOldProfileRetrieved(getErr, getStr, cb);
+ });
+ return
+ }
+
+ secureStorageService.get('profile', function(secureErr, secureStr) {
+ var secureProfile;
+ var oldProfile;
+
+ if (secureErr) {
+ return cb(secureErr, null);
+ }
+
+ if (secureStr) {
+ try {
+ secureProfile = Profile.fromString(secureStr);
+ $log.debug('profile: ' + JSON.stringify(secureProfile));
+ } catch (e) {
+ $log.error(e);
+ return cb(e, null);
+ }
+ }
+
+ storage.get('profile', function(getErr, getStr) {
+ _onOldProfileRetrieved(getErr, getStr, function(oldErr, oldProfile){
+ if (oldErr) {
+ return cb(oldErr, null);
+ }
+
+ if (!oldProfile) {
+ if (secureProfile) {
+ return cb(null, secureProfile);
+ } else {
+ // No profiles found. No errors either.
+ return cb(null, null);
+ }
+ }
+ _migrateProfiles(oldProfile, secureProfile, cb);
+ });
+ });
+ });
};
root.setFeedbackInfo = function(feedbackValues, cb) {
diff --git a/src/js/services/storageService.spec.js b/src/js/services/storageService.spec.js
new file mode 100644
index 000000000..493678b97
--- /dev/null
+++ b/src/js/services/storageService.spec.js
@@ -0,0 +1,1119 @@
+xdescribe('storageService on desktop', function(){
+ var appConfig,
+ expectedOldProfileSavedToSecure,
+ expectedOldProfileMergedWithSecure,
+ localStorageServiceMock,
+ log,
+ oldProfile,
+ platformInfoStub,
+ savedSecureProfile,
+ secureStorageService,
+ secureStorageServiceMock,
+ storageService;
+
+ expectedOldProfileMergedWithSecure = '{"version":"1.0.0","appVersion":"4.11.0","createdOn":1528363260283,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"81f52508c14d50cdde2ad527920f209cbf51162b0dbaa7ceac298ed6d34d1ff8","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"9580929b-417d-4fce-bcbf-de8e16a51c25","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"54dd6773fec23b07eff5cda33fd0ad2591de31db356c67cd3e5dc67211d7c8ac","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"PptIrH74qd63DPMC1LQ/dQ==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"6f3c19e90d6eb9096a57199d53494fd6d62852ffaaa62fb5a5baef9f65753ce1","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"ef78459e-52b1-418a-b89d-4df2ef1d27ea","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"2ac4835b2c883e095f4b187d712e53701781cb0d24e8813e736fd2d8a3219fec","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"WMcSMqfwZ+qfhP58S9l6OA==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"9580929b-417d-4fce-bcbf-de8e16a51c25":true,"ef78459e-52b1-418a-b89d-4df2ef1d27ea":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ expectedOldProfileSavedToSecure = '{"version":"1.0.0","appVersion":"${appVersion}","createdOn":1528363022385,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"a8ea9291-1369-4862-90a1-d80a5d4bcc20":true,"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ oldProfile = '{"version":"1.0.0","createdOn":1528363022385,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"a8ea9291-1369-4862-90a1-d80a5d4bcc20":true,"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ secureProfile = '{"version":"1.0.0","appVersion":"4.11.0","createdOn":1528363260283,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"81f52508c14d50cdde2ad527920f209cbf51162b0dbaa7ceac298ed6d34d1ff8","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"9580929b-417d-4fce-bcbf-de8e16a51c25","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"54dd6773fec23b07eff5cda33fd0ad2591de31db356c67cd3e5dc67211d7c8ac","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"PptIrH74qd63DPMC1LQ/dQ==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"6f3c19e90d6eb9096a57199d53494fd6d62852ffaaa62fb5a5baef9f65753ce1","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"ef78459e-52b1-418a-b89d-4df2ef1d27ea","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"2ac4835b2c883e095f4b187d712e53701781cb0d24e8813e736fd2d8a3219fec","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"WMcSMqfwZ+qfhP58S9l6OA==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"9580929b-417d-4fce-bcbf-de8e16a51c25":true,"ef78459e-52b1-418a-b89d-4df2ef1d27ea":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ secureProfileFromOldOnly = '{"version":"1.0.0","appVersion":"${appVersion}","createdOn":1528363022385,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"a8ea9291-1369-4862-90a1-d80a5d4bcc20":true,"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+
+ log = {
+ debug: function(s){ console.log(s); },
+ error: function(s){ console.log(s); },
+ info: function(s){ console.log(s); }
+ };
+
+ beforeEach(function(){
+ module('ngLodash');
+ module('bwcModule');
+ module('copayApp.services');
+
+ localStorageServiceMock = {
+ get: jasmine.createSpy(),
+ remove: jasmine.createSpy()
+ };
+
+ platformInfoStub = {
+ isCordova: false
+ };
+
+ secureStorageServiceMock = {
+ get: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ module(function($provide) {
+ $provide.value('localStorageService', localStorageServiceMock);
+ //$provide.value('$log', log); // Handy for debugging test failures
+ $provide.value('platformInfo', platformInfoStub);
+ $provide.value('secureStorageService', secureStorageServiceMock);
+ });
+
+ inject(function($injector){
+ appConfig = $injector.get('appConfigService');
+ storageService = $injector.get('storageService');
+ });
+
+ secureProfileFromOldOnly = secureProfileFromOldOnly.replace('${appVersion}', appConfig.version);
+ expectedOldProfileSavedToSecure = expectedOldProfileSavedToSecure.replace('${appVersion}', appConfig.version);
+
+ });
+
+ it('getProfile() from local storage.', function() {
+ var error, keySecureGet, keyLocalGet, keySecureSet, keyLocalRemove, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, null);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(null);
+ });
+
+ localStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyLocalRemove = k;
+ cb(null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error).toBeFalsy();
+ expect(profile).toBeTruthy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyLocalRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileSavedToSecure);
+ expect(localStorageServiceMock.remove.calls.any()).toBe(true);
+
+ expect(profile.appVersion).toBe(appConfig.version);
+ expect(profile.createdOn).toBe(1528363022385);
+
+ expect(profile.credentials[0].coin).toBe('bch');
+ expect(profile.credentials[0].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[0].walletId).toBe('a8ea9291-1369-4862-90a1-d80a5d4bcc20');
+
+ expect(profile.credentials[1].coin).toBe('btc');
+ expect(profile.credentials[1].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[1].walletId).toBe('f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b');
+ });
+
+ it('getProfile() from local storage, remove fails.', function() {
+ var error, keySecureGet, keyLocalGet, keySecureSet, keyLocalRemove, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, null);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(null);
+ });
+
+ localStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyLocalRemove = k;
+ cb(new Error('Remove error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Remove error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyLocalRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileSavedToSecure);
+ });
+
+ it('getProfile() from local storage, secure set fails, not removed.', function() {
+ var error, keySecureGet, keyLocalGet, keySecureSet, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, null);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(new Error('Set error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Set error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileSavedToSecure);
+
+ expect(localStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile(), secure get fails.', function() {
+ var error, keySecureGet, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(new Error('Secure get error.'), null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Secure get error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+
+ expect(localStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile(), secure get succeeds, local storage get fails.', function() {
+ var error, keySecureGet, keyLocalGet, profile, profile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(new Error('Local storage get error.'), null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Local storage get error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+
+ expect(localStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile() from secure storage.', function() {
+ var error, keySecureGet, keyLocalGet, profile, profile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error).toBeFalsy();
+ expect(profile).toBeTruthy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+
+ expect(profile.appVersion).toBe('4.11.0');
+ expect(profile.createdOn).toBe(1528363260283);
+
+ expect(profile.credentials[0].coin).toBe('bch');
+ expect(profile.credentials[0].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[0].walletId).toBe('9580929b-417d-4fce-bcbf-de8e16a51c25');
+
+ expect(profile.credentials[1].coin).toBe('btc');
+ expect(profile.credentials[1].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[1].walletId).toBe('ef78459e-52b1-418a-b89d-4df2ef1d27ea');
+ });
+
+ it('getProfile() merge from local and secure storage.', function() {
+ var error, keySecureGet, keyLocalGet, keySecureSet, keyLocalRemove, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(null);
+ });
+
+ localStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyLocalRemove = k;
+ cb(null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error).toBeFalsy();
+ expect(profile).toBeTruthy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyLocalRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileMergedWithSecure);
+
+ expect(profile.appVersion).toBe('4.11.0');
+ expect(profile.createdOn).toBe(1528363260283);
+
+ // Existing secure
+ expect(profile.credentials[0].coin).toBe('bch');
+ expect(profile.credentials[0].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[0].walletId).toBe('9580929b-417d-4fce-bcbf-de8e16a51c25');
+
+ expect(profile.credentials[1].coin).toBe('btc');
+ expect(profile.credentials[1].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[1].walletId).toBe('ef78459e-52b1-418a-b89d-4df2ef1d27ea');
+
+ // Old
+ expect(profile.credentials[2].coin).toBe('bch');
+ expect(profile.credentials[2].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[2].walletId).toBe('a8ea9291-1369-4862-90a1-d80a5d4bcc20');
+
+ expect(profile.credentials[3].coin).toBe('btc');
+ expect(profile.credentials[3].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[3].walletId).toBe('f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b');
+
+ });
+
+ it('getProfile() merge from local and secure storage, secure set fails, not removed from local.', function() {
+ var error, keySecureGet, keyLocalGet, keySecureSet, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(new Error('Secure set error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Secure set error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileMergedWithSecure);
+
+ expect(localStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile() merge from local and secure storage, remove from local fails.', function() {
+ var error, keySecureGet, keyLocalGet, keySecureSet, keyLocalRemove, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k
+ savedProfile = v;
+ cb(null);
+ });
+
+ localStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyLocalRemove = k;
+ cb(new Error('Remove error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Remove error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyLocalGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyLocalRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileMergedWithSecure);
+ });
+
+
+
+
+});
+
+describe('storageService on desktop using local storage', function(){
+ var appConfig,
+ localStorageServiceMock,
+ log,
+ oldProfile,
+ oldProfileString,
+ platformInfoStub,
+ secureStorageService,
+ secureStorageServiceMock,
+ storageService;
+
+ oldProfileString = '{"version":"1.0.0","createdOn":1528363022385,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"a8ea9291-1369-4862-90a1-d80a5d4bcc20":true,"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ oldProfile = Profile.fromString(oldProfileString);
+
+ log = {
+ debug: function(s){ console.log(s); },
+ error: function(s){ console.log(s); },
+ info: function(s){ console.log(s); }
+ };
+
+ beforeEach(function(){
+ module('ngLodash');
+ module('bwcModule');
+ module('copayApp.services');
+
+ localStorageServiceMock = {
+ get: jasmine.createSpy(),
+ remove: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ platformInfoStub = {
+ isNW: true
+ };
+
+ secureStorageServiceMock = {
+ get: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ module(function($provide) {
+ $provide.value('localStorageService', localStorageServiceMock);
+ //$provide.value('$log', log); // Handy for debugging test failures
+ $provide.value('platformInfo', platformInfoStub);
+ $provide.value('secureStorageService', secureStorageServiceMock);
+ });
+
+ inject(function($injector){
+ appConfig = $injector.get('appConfigService');
+ storageService = $injector.get('storageService');
+ });
+ });
+
+ it('getProfile().', function() {
+ var error, keyLocalGet, profile;
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(null, oldProfileString);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error).toBeFalsy();
+ expect(profile).toBeTruthy();
+
+ expect(keyLocalGet).toBe('profile');
+
+ expect(localStorageServiceMock.remove.calls.any()).toBe(false);
+ expect(secureStorageServiceMock.get.calls.any()).toBe(false);
+ expect(secureStorageServiceMock.set.calls.any()).toBe(false);
+
+ expect(profile.appVersion).toBeUndefined();
+ expect(profile.createdOn).toBe(1528363022385);
+
+ expect(profile.credentials[0].coin).toBe('bch');
+ expect(profile.credentials[0].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[0].walletId).toBe('a8ea9291-1369-4862-90a1-d80a5d4bcc20');
+
+ expect(profile.credentials[1].coin).toBe('btc');
+ expect(profile.credentials[1].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[1].walletId).toBe('f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b');
+ });
+
+ it('getProfile(), get fails.', function() {
+ var error, keyLocalGet, profile;
+
+ localStorageServiceMock.get.and.callFake(function(k, cb){
+ keyLocalGet = k;
+ cb(new Error('Local get error.'), null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Local get error.');
+ expect(profile).toBeFalsy();
+
+ expect(keyLocalGet).toBe('profile');
+
+ expect(localStorageServiceMock.remove.calls.any()).toBe(false);
+ expect(secureStorageServiceMock.get.calls.any()).toBe(false);
+ expect(secureStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+ it('storeNewProfile() to local storage.', function() {
+ var error, keyLocalSet, savedProfileString;
+
+ localStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keyLocalSet = k;
+ savedProfileString = v;
+ cb(null);
+ });
+
+ storageService.storeNewProfile(oldProfile, function(err){
+ error = err;
+ });
+
+ expect(error).toBeFalsy();
+ expect(savedProfileString).toBeTruthy();
+
+ expect(keyLocalSet).toBe('profile');
+
+ expect(savedProfileString).toBe(oldProfileString);
+ expect(secureStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+ it('storeNewProfile() to local storage, set fails.', function() {
+ var error, keyLocalSet, savedProfileString;
+
+ localStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keyLocalSet = k;
+ savedProfileString = v;
+ cb(new Error('Local set failed.'));
+ });
+
+ storageService.storeNewProfile(oldProfile, function(err){
+ error = err;
+ });
+
+ expect(error.message).toBe('Local set failed.');
+ expect(savedProfileString).toBe(oldProfileString);
+
+ expect(keyLocalSet).toBe('profile');
+
+ expect(savedProfileString).toBe(oldProfileString);
+ expect(secureStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+ it('storeProfile() to local storage.', function() {
+ var error, keyLocalSet, savedProfileString;
+
+ localStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keyLocalSet = k;
+ savedProfileString = v;
+ cb(null);
+ });
+
+ storageService.storeProfile(oldProfile, function(err){
+ error = err;
+ });
+
+ expect(error).toBeFalsy();
+ expect(savedProfileString).toBeTruthy();
+
+ expect(keyLocalSet).toBe('profile');
+
+ expect(savedProfileString).toBe(oldProfileString);
+ expect(secureStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+ it('storeProfile() to local storage, set fails.', function() {
+ var error, keyLocalSet, savedProfileString;
+
+ localStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keyLocalSet = k;
+ savedProfileString = v;
+ cb(new Error('Local set failed.'));
+ });
+
+ storageService.storeProfile(oldProfile, function(err){
+ error = err;
+ });
+
+ expect(error.message).toBe('Local set failed.');
+ expect(savedProfileString).toBe(oldProfileString);
+
+ expect(keyLocalSet).toBe('profile');
+
+ expect(savedProfileString).toBe(oldProfileString);
+ expect(secureStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+});
+
+describe('storageService on mobile', function(){
+ var appConfig,
+ expectedOldProfileSavedToSecure,
+ expectedOldProfileMergedWithSecure,
+ fileStorageServiceMock,
+ log,
+ oldProfile,
+ platformInfoStub,
+ savedSecureProfile,
+ secureProfileObj,
+ secureStorageService,
+ secureStorageServiceMock,
+ storageService;
+
+ expectedOldProfileMergedWithSecure = '{"version":"1.0.0","appVersion":"4.11.0","createdOn":1528363260283,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"81f52508c14d50cdde2ad527920f209cbf51162b0dbaa7ceac298ed6d34d1ff8","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"9580929b-417d-4fce-bcbf-de8e16a51c25","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"54dd6773fec23b07eff5cda33fd0ad2591de31db356c67cd3e5dc67211d7c8ac","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"PptIrH74qd63DPMC1LQ/dQ==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"6f3c19e90d6eb9096a57199d53494fd6d62852ffaaa62fb5a5baef9f65753ce1","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"ef78459e-52b1-418a-b89d-4df2ef1d27ea","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"2ac4835b2c883e095f4b187d712e53701781cb0d24e8813e736fd2d8a3219fec","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"WMcSMqfwZ+qfhP58S9l6OA==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"9580929b-417d-4fce-bcbf-de8e16a51c25":true,"ef78459e-52b1-418a-b89d-4df2ef1d27ea":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ expectedOldProfileSavedToSecure = '{"version":"1.0.0","appVersion":"${appVersion}","createdOn":1528363022385,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"a8ea9291-1369-4862-90a1-d80a5d4bcc20":true,"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ oldProfile = '{"version":"1.0.0","createdOn":1528363022385,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"a8ea9291-1369-4862-90a1-d80a5d4bcc20":true,"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ secureProfile = '{"version":"1.0.0","appVersion":"4.11.0","createdOn":1528363260283,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"81f52508c14d50cdde2ad527920f209cbf51162b0dbaa7ceac298ed6d34d1ff8","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"9580929b-417d-4fce-bcbf-de8e16a51c25","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"54dd6773fec23b07eff5cda33fd0ad2591de31db356c67cd3e5dc67211d7c8ac","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"PptIrH74qd63DPMC1LQ/dQ==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K4Mge6QumKYh1aSYLB26z6QhkDz8tJLuXdumCJy9PYBrHMrTW3boiaodkVNTciR7PcPAcLXZeUWSehMJc3GXJp1uR68x3Nh5","xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPrivKey":"8fde6c8da5cf59cc0b19e87ea102aef2799047b9062f3e08668a92ef4582e040","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2","copayerId":"6f3c19e90d6eb9096a57199d53494fd6d62852ffaaa62fb5a5baef9f65753ce1","publicKeyRing":[{"xPubKey":"xpub6CGZNmTZ9KmHyxgbqZhfcJKwhrgN5EfHh2P7YppRXPGvUg6QkAuErmaQQa3cjyS9NMuFnvxm1eNUcbUEuiVikzUmZmVrtVcU7uvjWUNrRTG","requestPubKey":"0366db5dd83550ebefa8946d770e68ea8bb0e197076713bb681fb80d6fbc4278b2"}],"walletId":"ef78459e-52b1-418a-b89d-4df2ef1d27ea","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"2ac4835b2c883e095f4b187d712e53701781cb0d24e8813e736fd2d8a3219fec","personalEncryptingKey":"r5Tpd+/YD6uGXKZeeqZBPg==","sharedEncryptingKey":"WMcSMqfwZ+qfhP58S9l6OA==","copayerName":"me","mnemonic":"forget camera antique cement army ahead quantum leisure claim behind climb eight","entropySource":"fc2357f9d0176aa3a571bdfdea9e12cd16c27019e87b80ab0f08ddf15101d532","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"9580929b-417d-4fce-bcbf-de8e16a51c25":true,"ef78459e-52b1-418a-b89d-4df2ef1d27ea":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ secureProfileFromOldOnly = '{"version":"1.0.0","appVersion":"${appVersion}","createdOn":1528363022385,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"cc5667792d8378ad61dc30a65bafea3d03d9179c5615d9f183738b002d978659","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"a8ea9291-1369-4862-90a1-d80a5d4bcc20","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"8437d2824b17f31d548fc2855577e9092ac5a7f9c985e5329acab34a8e786fb8","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"ZhMBX+t9/0n2kCasR5KH0w==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2vd69iX1D5R2Acdjx6hzsSncBqnTri7UUad3SxSxFGukcjCUBKfWtZx3KGVjSd94ypEz4gB5RzATenxCEVPPZsgVJpoXkRq","xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPrivKey":"c1cac5328bf71c0f73f64ef868ddea66356ba797f87af4939390d58a7ff1aeda","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d","copayerId":"8430d4ca7a324ce0176e782c2d48f333666bd8f9b66fdd432a7f1ad1c80341ec","publicKeyRing":[{"xPubKey":"xpub6CZLbRhS7jEN2UT3ZhGeia6jPxr4guckZDa7ogncrrES2GyMj7Pq5U4oYLV2FhAMuuYA8qzxWV3TDXXDSkGTaqHstjRANCgCjrMDA1r7AN8","requestPubKey":"02b41c465aaf8f41192f2444a07c6e64d6147a080c5b82a6e73b3b232f11e1575d"}],"walletId":"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"30df9228ff38258afe363a29cb02bff6d76f9f66ed36250de493717f4c941cc1","personalEncryptingKey":"qZmFZypS3TufwM5+WzvNJw==","sharedEncryptingKey":"2wQyQJGV3vyRPE/uil9ZRA==","copayerName":"me","mnemonic":"morning conduct milk catch victory smoke ship little dutch original legal gadget","entropySource":"3f88849ae9522574a2aaab870594b25a4e90b9dc632724ef3675fc3c49aa93b9","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"}],"disclaimerAccepted":true,"checked":{"a8ea9291-1369-4862-90a1-d80a5d4bcc20":true,"f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"}';
+ secureProfileObj = Profile.fromString(secureProfile);
+
+ log = {
+ debug: function(s){ console.log(s); },
+ error: function(s){ console.log(s); },
+ info: function(s){ console.log(s); }
+ };
+
+ beforeEach(function(){
+ module('ngLodash');
+ module('bwcModule');
+ module('copayApp.services');
+
+ fileStorageServiceMock = {
+ get: jasmine.createSpy(),
+ remove: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ platformInfoStub = {
+ isCordova: true,
+ isWP: false
+ };
+
+ secureStorageServiceMock = {
+ get: jasmine.createSpy(),
+ set: jasmine.createSpy()
+ };
+
+ module(function($provide) {
+ $provide.value('fileStorageService', fileStorageServiceMock);
+ $provide.value('platformInfo', platformInfoStub);
+ $provide.value('secureStorageService', secureStorageServiceMock);
+ });
+
+ inject(function($injector){
+ appConfig = $injector.get('appConfigService');
+ storageService = $injector.get('storageService');
+ });
+
+ secureProfileFromOldOnly = secureProfileFromOldOnly.replace('${appVersion}', appConfig.version);
+ expectedOldProfileSavedToSecure = expectedOldProfileSavedToSecure.replace('${appVersion}', appConfig.version);
+
+ });
+
+ it('getProfile() from file storage.', function() {
+ var error, keySecureGet, keyFileGet, keySecureSet, keyFileRemove, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, null);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(null);
+ });
+
+ fileStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyFileRemove = k;
+ cb(null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error).toBeFalsy();
+ expect(profile).toBeTruthy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyFileRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileSavedToSecure);
+ expect(fileStorageServiceMock.remove.calls.any()).toBe(true);
+
+ expect(profile.appVersion).toBe(appConfig.version);
+ expect(profile.createdOn).toBe(1528363022385);
+
+ expect(profile.credentials[0].coin).toBe('bch');
+ expect(profile.credentials[0].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[0].walletId).toBe('a8ea9291-1369-4862-90a1-d80a5d4bcc20');
+
+ expect(profile.credentials[1].coin).toBe('btc');
+ expect(profile.credentials[1].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[1].walletId).toBe('f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b');
+ });
+
+ it('getProfile() from file storage, remove fails.', function() {
+ var error, keySecureGet, keyFileGet, keySecureSet, keyFileRemove, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, null);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(null);
+ });
+
+ fileStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyFileRemove = k;
+ cb(new Error('Remove error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Remove error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyFileRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileSavedToSecure);
+ });
+
+ it('getProfile() from file storage, secure set fails, not removed.', function() {
+ var error, keySecureGet, keyFileGet, keySecureSet, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, null);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(new Error('Set error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Set error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileSavedToSecure);
+
+ expect(fileStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile(), secure get fails.', function() {
+ var error, keySecureGet, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(new Error('Secure get error.'), null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Secure get error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+
+ expect(fileStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile(), secure get succeeds, file storage get fails.', function() {
+ var error, keySecureGet, keyFileGet, profile, profile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(new Error('File storage get error.'), null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('File storage get error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+
+ expect(fileStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile() from secure storage.', function() {
+ var error, keySecureGet, keyFileGet, profile, profile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(null, null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error).toBeFalsy();
+ expect(profile).toBeTruthy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+
+ expect(profile.appVersion).toBe('4.11.0');
+ expect(profile.createdOn).toBe(1528363260283);
+
+ expect(profile.credentials[0].coin).toBe('bch');
+ expect(profile.credentials[0].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[0].walletId).toBe('9580929b-417d-4fce-bcbf-de8e16a51c25');
+
+ expect(profile.credentials[1].coin).toBe('btc');
+ expect(profile.credentials[1].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[1].walletId).toBe('ef78459e-52b1-418a-b89d-4df2ef1d27ea');
+ });
+
+ it('getProfile() merge from local and secure storage.', function() {
+ var error, keySecureGet, keyFileGet, keySecureSet, keyFileRemove, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(null);
+ });
+
+ fileStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyFileRemove = k;
+ cb(null);
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error).toBeFalsy();
+ expect(profile).toBeTruthy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyFileRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileMergedWithSecure);
+
+ expect(profile.appVersion).toBe('4.11.0');
+ expect(profile.createdOn).toBe(1528363260283);
+
+ // Existing secure
+ expect(profile.credentials[0].coin).toBe('bch');
+ expect(profile.credentials[0].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[0].walletId).toBe('9580929b-417d-4fce-bcbf-de8e16a51c25');
+
+ expect(profile.credentials[1].coin).toBe('btc');
+ expect(profile.credentials[1].mnemonic).toBe('forget camera antique cement army ahead quantum leisure claim behind climb eight');
+ expect(profile.credentials[1].walletId).toBe('ef78459e-52b1-418a-b89d-4df2ef1d27ea');
+
+ // Old
+ expect(profile.credentials[2].coin).toBe('bch');
+ expect(profile.credentials[2].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[2].walletId).toBe('a8ea9291-1369-4862-90a1-d80a5d4bcc20');
+
+ expect(profile.credentials[3].coin).toBe('btc');
+ expect(profile.credentials[3].mnemonic).toBe('morning conduct milk catch victory smoke ship little dutch original legal gadget');
+ expect(profile.credentials[3].walletId).toBe('f4ff4629-ff53-4bc7-8c98-e7c8e0149d3b');
+
+ });
+
+ it('getProfile() merge from local and secure storage, secure set fails, not removed from local.', function() {
+ var error, keySecureGet, keyFileGet, keySecureSet, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfile = v;
+ cb(new Error('Secure set error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Secure set error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileMergedWithSecure);
+
+ expect(fileStorageServiceMock.remove.calls.any()).toBe(false);
+ });
+
+ it('getProfile() merge from local and secure storage, remove from local fails.', function() {
+ var error, keySecureGet, keyFileGet, keySecureSet, keyFileRemove, profile, profile, savedProfile;
+
+ secureStorageServiceMock.get.and.callFake(function(k, cb){
+ keySecureGet = k;
+ cb(null, secureProfile);
+ });
+
+ fileStorageServiceMock.get.and.callFake(function(k, cb){
+ keyFileGet = k;
+ cb(null, oldProfile);
+ });
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k
+ savedProfile = v;
+ cb(null);
+ });
+
+ fileStorageServiceMock.remove.and.callFake(function(k, cb){
+ keyFileRemove = k;
+ cb(new Error('Remove error.'));
+ });
+
+ storageService.getProfile(function(err, p){
+ error = err;
+ profile = p;
+ });
+
+ expect(error.message).toBe('Remove error.');
+ expect(profile).toBeFalsy();
+
+ expect(keySecureGet).toBe('profile');
+ expect(keyFileGet).toBe('profile');
+ expect(keySecureSet).toBe('profile');
+ expect(keyFileRemove).toBe('profile');
+
+ expect(savedProfile).toBe(expectedOldProfileMergedWithSecure);
+ });
+
+ it('storeNewProfile().', function() {
+ var error, keySecureSet, savedProfileString;
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfileString = v;
+ cb(null);
+ });
+
+ storageService.storeNewProfile(secureProfileObj, function(err){
+ error = err;
+ });
+
+ expect(error).toBeFalsy();
+ expect(savedProfileString).toBeTruthy();
+
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfileString).toBe(secureProfile);
+ expect(fileStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+ it('storeNewProfile(), secure set fails.', function() {
+ var error, keySecureSet, savedProfileString;
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfileString = v;
+ cb(new Error('Secure set failed.'));
+ });
+
+ storageService.storeNewProfile(secureProfileObj, function(err){
+ error = err;
+ });
+
+ expect(error.message).toBe('Secure set failed.');
+ expect(savedProfileString).toBeTruthy();
+
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfileString).toBe(secureProfile);
+ expect(fileStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+ it('storeProfile().', function() {
+ var error, keySecureSet, savedProfileString;
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfileString = v;
+ cb(null);
+ });
+
+ storageService.storeProfile(secureProfileObj, function(err){
+ error = err;
+ });
+
+ expect(error).toBeFalsy();
+ expect(savedProfileString).toBeTruthy();
+
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfileString).toBe(secureProfile);
+ expect(fileStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+ it('storeProfile(), secure set fails.', function() {
+ var error, keySecureSet, savedProfileString;
+
+ secureStorageServiceMock.set.and.callFake(function(k, v, cb){
+ keySecureSet = k;
+ savedProfileString = v;
+ cb(new Error('Secure set failed.'));
+ });
+
+ storageService.storeProfile(secureProfileObj, function(err){
+ error = err;
+ });
+
+ expect(error.message).toBe('Secure set failed.');
+ expect(savedProfileString).toBeTruthy();
+
+ expect(keySecureSet).toBe('profile');
+
+ expect(savedProfileString).toBe(secureProfile);
+ expect(fileStorageServiceMock.set.calls.any()).toBe(false);
+ });
+
+});
\ No newline at end of file
diff --git a/src/sass/icons.scss b/src/sass/icons.scss
index 4693025f6..7d14f8886 100644
--- a/src/sass/icons.scss
+++ b/src/sass/icons.scss
@@ -40,7 +40,7 @@
border-radius: $v-icon-border-radius;
width: 40px;
height: 40px;
- box-shadow: $v-hovering-box-shadow;
+ box-shadow: 0px 0px 9px 0px rgba(0, 0, 0, 0.3);
background-repeat:no-repeat;
background-clip: padding-box;
background-size: 103%;
diff --git a/src/sass/variables.scss b/src/sass/variables.scss
index e5bd2712d..cb21c030a 100644
--- a/src/sass/variables.scss
+++ b/src/sass/variables.scss
@@ -33,8 +33,8 @@ $v-wallet-color-map: (
3: (color: #d0b136, name: 'Metallic Gold'),
4: (color: #9edd72, name: 'Feijoa'),
5: (color: #29bb9c, name: 'Shamrock'),
- 6: (color: #26B03C, name: 'Dollar Green'),
- 7: (color: #fab915, name: 'Observatory'),
+ 6: (color: #eeb640, name: 'Light Orange'),
+ 7: (color: #535353, name: 'Dark Grey'),
8: (color: #77dada, name: 'Turquoise Blue'),
9: (color: #4a90e2, name: 'Cornflower Blue'),
10: (color: #484ed3, name: 'Free Speech Blue'),
diff --git a/src/sass/views/amount.scss b/src/sass/views/amount.scss
index 3000ea696..c712d85e5 100644
--- a/src/sass/views/amount.scss
+++ b/src/sass/views/amount.scss
@@ -474,4 +474,10 @@
}
}
}
+ background: #494949;
+
+ ion-content {
+ margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */
+ margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */
+ }
}
\ No newline at end of file
diff --git a/src/sass/views/confirm.scss b/src/sass/views/confirm.scss
index 9ceee92c4..47f61fd7b 100644
--- a/src/sass/views/confirm.scss
+++ b/src/sass/views/confirm.scss
@@ -1,5 +1,5 @@
#view-confirm {
- background-color: #ffffff;
+ background-color: #494949;
@extend .deflash-blue;
.item-note {
float: none;
@@ -30,4 +30,11 @@
.toggle {
cursor: pointer;
}
+ ion-content {
+ background-color: #ffffff;
+ }
+ slide-to-accept, slide-to-accept-success {
+ margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */
+ margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */
+ }
}
diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss
index 1a33de7b1..9e651f871 100644
--- a/src/sass/views/walletDetails.scss
+++ b/src/sass/views/walletDetails.scss
@@ -131,6 +131,7 @@
.bp-content {
position: relative;
height: 100%;
+ height: calc(100% - env(safe-area-inset-bottom) * 2);
&.status-bar {
margin-top: 20px;
@@ -157,6 +158,8 @@
padding-top: 0;
top: 0;
+ margin-bottom: 16px;
+
.scroll {
background: rgb(248, 248, 249);
min-height: 300px;
diff --git a/test/karma.conf.js b/test/karma.conf.js
new file mode 100644
index 000000000..002d40c91
--- /dev/null
+++ b/test/karma.conf.js
@@ -0,0 +1,91 @@
+// Karma configuration
+// Generated on Tue Jun 05 2018 16:39:51 GMT+1200 (NZST)
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '..',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'node_modules/angular/angular.js',
+
+ // From Gruntfile.js
+ 'bower_components/qrcode-generator/js/qrcode.js',
+ 'bower_components/qrcode-generator/js/qrcode_UTF8.js',
+ 'bower_components/moment/min/moment-with-locales.js',
+ 'bower_components/angular-moment/angular-moment.js',
+ 'bower_components/ng-lodash/build/ng-lodash.js',
+ 'bower_components/angular-qrcode/angular-qrcode.js',
+ 'bower_components/angular-gettext/dist/angular-gettext.js',
+ 'bower_components/ng-csv/build/ng-csv.js',
+ 'bower_components/ionic-toast/dist/ionic-toast.bundle.min.js',
+ 'bower_components/angular-clipboard/angular-clipboard.js',
+ 'bower_components/angular-md5/angular-md5.js',
+ 'bower_components/angular-mocks/angular-mocks.js',
+ 'bower_components/ngtouch/src/ngTouch.js',
+ 'angular-bitauth/angular-bitauth.js',
+ 'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js',
+
+ 'bower_components/ionic/release/js/ionic.bundle.min.js',
+ 'bitcoin-cash-js/bitcoin-cash-js.js',
+
+ 'src/js/**/*.js'
+ ],
+
+
+ // list of files / patterns to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: Infinity
+ })
+}
diff --git a/www/css/main.css b/www/css/main.css
index 4e7faac0a..b4e67edac 100644
--- a/www/css/main.css
+++ b/www/css/main.css
@@ -10005,7 +10005,7 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm
border-radius: 3px;
width: 40px;
height: 40px;
- box-shadow: 0px 6px 12px 0px rgba(0, 0, 0, 0.3);
+ box-shadow: 0px 0px 9px 0px rgba(0, 0, 0, 0.3);
background-repeat: no-repeat;
background-clip: padding-box;
background-size: 103%; }
@@ -10726,6 +10726,12 @@ textarea.d-block {
#tab-home .card .item-sub:before {
width: 90%; } }
+#tab-home .card-banner {
+ padding: 0; }
+ #tab-home .card-banner__img {
+ width: 100%;
+ display: block; }
+
#tab-home .wallet-coin-logo {
vertical-align: middle;
margin-right: 5px; }
@@ -11305,13 +11311,13 @@ textarea.d-block {
/* background-color and color defaults should be the same */
.wallet-background-color-default {
- background-color: #fab915; }
+ background-color: #535353; }
.wallet-color-default {
- color: #fab915; }
+ color: #535353; }
.cashwallet-color-default {
- color: #26B03C; }
+ color: #eeb640; }
/* generate classes for all colors */
.wallet-color-0 {
@@ -11357,17 +11363,17 @@ textarea.d-block {
margin-left: 2.4rem; }
.wallet-color-6 {
- background: #26B03C; }
+ background: #eeb640; }
.wallet-color-6:before {
- content: "Dollar Green";
+ content: "Light Orange";
margin-left: 2.4rem; }
.wallet-color-7 {
- background: #fab915; }
+ background: #535353; }
.wallet-color-7:before {
- content: "Observatory";
+ content: "Dark Grey";
margin-left: 2.4rem; }
.wallet-color-8 {
diff --git a/www/img/icon-wallet.svg b/www/img/icon-wallet.svg
index fba9bca0b..74c7055f2 100644
--- a/www/img/icon-wallet.svg
+++ b/www/img/icon-wallet.svg
@@ -1,62 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file