Skip to content
This repository was archived by the owner on Feb 23, 2021. It is now read-only.

Commit 691b3b0

Browse files
authored
Merge pull request #422 from lightninglabs/save-settings
Save settings
2 parents 90a7735 + 5174f02 commit 691b3b0

File tree

13 files changed

+232
-64
lines changed

13 files changed

+232
-64
lines changed

src/action/app-storage.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as log from './log';
2+
3+
class AppStorage {
4+
constructor(store, AsyncStorage) {
5+
this._store = store;
6+
this._AsyncStorage = AsyncStorage;
7+
}
8+
9+
async restore() {
10+
try {
11+
const stateString = await this._AsyncStorage.getItem('settings');
12+
if (!stateString) return;
13+
const state = JSON.parse(stateString);
14+
Object.keys(state).forEach(key => {
15+
if (typeof this._store.settings[key] !== 'undefined') {
16+
this._store.settings[key] = state[key];
17+
}
18+
});
19+
} catch (err) {
20+
log.error('Store load error', err);
21+
} finally {
22+
log.info('Loaded initial state');
23+
this._store.loaded = true;
24+
}
25+
}
26+
27+
async save() {
28+
try {
29+
const state = JSON.stringify(this._store.settings);
30+
await this._AsyncStorage.setItem('settings', state);
31+
log.info('Saved state');
32+
} catch (error) {
33+
log.error('Store save error', error);
34+
}
35+
}
36+
37+
async clear() {
38+
try {
39+
await this._AsyncStorage.clear();
40+
log.info('State cleared');
41+
} catch (error) {
42+
log.error('Store clear error', error);
43+
}
44+
}
45+
}
46+
47+
export default AppStorage;

src/action/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { observe } from 'mobx';
22
import { AsyncStorage, Clipboard } from 'react-native';
33
import { nap } from '../helper';
44
import store from '../store';
5+
import AppStorage from './app-storage';
56
import GrpcAction from './grpc';
67
import NavAction from './nav';
78
import WalletAction from './wallet';
@@ -21,13 +22,13 @@ const ipcRenderer = window.ipcRenderer; // exposed to sandbox via preload.js
2122
//
2223

2324
store.init();
24-
store.restore(AsyncStorage);
2525

26+
export const db = new AppStorage(store, AsyncStorage);
2627
export const log = new LogAction(store, ipcRenderer);
2728
export const nav = new NavAction(store, ipcRenderer);
2829
export const grpc = new GrpcAction(store, ipcRenderer);
2930
export const notify = new NotificationAction(store, nav);
30-
export const wallet = new WalletAction(store, grpc, nav, notify);
31+
export const wallet = new WalletAction(store, grpc, db, nav, notify);
3132
export const info = new InfoAction(store, grpc, notify);
3233
export const channel = new ChannelAction(store, grpc, nav, notify);
3334
export const transaction = new TransactionAction(store, grpc, wallet, nav);
@@ -40,12 +41,14 @@ export const invoice = new InvoiceAction(
4041
Clipboard
4142
);
4243
export const payment = new PaymentAction(store, grpc, transaction, nav, notify);
43-
export const setting = new SettingAction(store, wallet);
44+
export const setting = new SettingAction(store, wallet, db);
4445

4546
//
4647
// Init actions
4748
//
4849

50+
db.restore();
51+
4952
observe(store, 'loaded', async () => {
5053
await grpc.initUnlocker();
5154
});

src/action/setting.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { UNITS, FIATS } from '../config';
22

33
class SettingAction {
4-
constructor(store, wallet) {
4+
constructor(store, wallet, db) {
55
this._store = store;
66
this._wallet = wallet;
7+
this._db = db;
78
}
89

910
setBitcoinUnit({ unit }) {
1011
if (!UNITS[unit]) {
1112
throw new Error(`Invalid bitcoin unit: ${unit}`);
1213
}
1314
this._store.settings.unit = unit;
15+
this._db.save();
1416
}
1517

1618
setFiatCurrency({ fiat }) {
@@ -19,6 +21,7 @@ class SettingAction {
1921
}
2022
this._store.settings.fiat = fiat;
2123
this._wallet.getExchangeRate();
24+
this._db.save();
2225
}
2326
}
2427

src/action/wallet.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { MIN_PASSWORD_LENGTH, NOTIFICATION_DELAY } from '../config';
33
import * as log from './log';
44

55
class WalletAction {
6-
constructor(store, grpc, nav, notification) {
6+
constructor(store, grpc, db, nav, notification) {
77
this._store = store;
88
this._grpc = grpc;
9+
this._db = db;
910
this._nav = nav;
1011
this._notification = notification;
1112
}
@@ -139,6 +140,7 @@ class WalletAction {
139140

140141
toggleDisplayFiat() {
141142
this._store.settings.displayFiat = !this._store.settings.displayFiat;
143+
this._db.save();
142144
}
143145

144146
async getBalance() {
@@ -181,6 +183,7 @@ class WalletAction {
181183
const uri = `https://blockchain.info/tobtc?currency=${fiat}&value=1`;
182184
const response = checkHttpStatus(await fetch(uri));
183185
this._store.settings.exchangeRate[fiat] = Number(await response.text());
186+
this._db.save();
184187
} catch (err) {
185188
log.error('Getting exchange rate failed', err);
186189
}

src/helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export const calculateExchangeRate = (satoshis, settings) => {
9898
) {
9999
throw new Error('Invalid input!');
100100
}
101-
const rate = settings.exchangeRate[settings.fiat];
101+
const rate = settings.exchangeRate[settings.fiat] || 0;
102102
const balance = satoshis / rate / UNITS.btc.denominator;
103103
return formatFiat(balance, settings.fiat);
104104
};

src/store.js

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extendObservable, action } from 'mobx';
1+
import { extendObservable } from 'mobx';
22
import ComputedWallet from './computed/wallet';
33
import ComputedTransaction from './computed/transaction';
44
import ComputedChannel from './computed/channel';
@@ -8,7 +8,6 @@ import ComputedNotification from './computed/notification';
88
import ComputedSetting from './computed/setting';
99
import ComputedSeed from './computed/seed';
1010
import { DEFAULT_ROUTE, DEFAULT_UNIT, DEFAULT_FIAT } from './config';
11-
import * as log from './action/log';
1211

1312
export class Store {
1413
constructor() {
@@ -67,10 +66,7 @@ export class Store {
6766
unit: DEFAULT_UNIT,
6867
fiat: DEFAULT_FIAT,
6968
displayFiat: false,
70-
exchangeRate: {
71-
usd: null,
72-
eur: null,
73-
},
69+
exchangeRate: {},
7470
},
7571
});
7672
}
@@ -85,44 +81,6 @@ export class Store {
8581
ComputedSetting(this);
8682
ComputedSeed(this);
8783
}
88-
89-
restore(AsyncStorage) {
90-
this._AsyncStorage = AsyncStorage;
91-
try {
92-
this._AsyncStorage.getItem('settings').then(
93-
action(stateString => {
94-
const state = JSON.parse(stateString);
95-
state &&
96-
Object.keys(state).forEach(key => {
97-
if (typeof this.settings[key] !== 'undefined') {
98-
this.settings[key] = state[key];
99-
}
100-
});
101-
log.info('Loaded initial state');
102-
this.loaded = true;
103-
})
104-
);
105-
} catch (err) {
106-
log.info('Store load error', err);
107-
this.loaded = true;
108-
}
109-
}
110-
111-
save() {
112-
try {
113-
const state = JSON.stringify(this.settings);
114-
this._AsyncStorage.setItem('settings', state);
115-
log.info('Saved state');
116-
} catch (error) {
117-
log.info('Store Error', error);
118-
}
119-
}
120-
121-
clear() {
122-
log.info('!!!!!!!!!CLEARING ALL PERSISTENT DATA!!!!!!');
123-
Object.keys(this.settings).map(key => (this.settings[key] = null));
124-
this.save();
125-
}
12684
}
12785

12886
export default new Store();

stories/screen.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import sinon from 'sinon';
66
import { Store } from '../src/store';
77
import NavAction from '../src/action/nav';
88
import GrpcAction from '../src/action/grpc';
9+
import AppStorage from '../src/action/app-storage';
910
import NotificationAction from '../src/action/notification';
1011
import SettingAction from '../src/action/setting';
1112
import WalletAction from '../src/action/wallet';
@@ -46,10 +47,11 @@ import NewAddress from '../src/view/new-address';
4647
const store = new Store();
4748
store.init();
4849
const nav = sinon.createStubInstance(NavAction);
50+
const db = sinon.createStubInstance(AppStorage);
4951
const grpc = sinon.createStubInstance(GrpcAction);
5052
const notify = sinon.createStubInstance(NotificationAction);
51-
const wallet = new WalletAction(store, grpc, nav, notify);
52-
const setting = new SettingAction(store, wallet);
53+
const wallet = new WalletAction(store, grpc, db, nav, notify);
54+
const setting = new SettingAction(store, wallet, db);
5355
sinon.stub(wallet, 'update');
5456
sinon.stub(wallet, 'checkSeed');
5557
sinon.stub(wallet, 'checkNewPassword');

test/integration/action/action-integration.spec.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { rmdir, poll, isPortOpen } from './test-util';
22
import { Store } from '../../../src/store';
33
import GrpcAction from '../../../src/action/grpc';
4+
import AppStorage from '../../../src/action/app-storage';
45
import NavAction from '../../../src/action/nav';
56
import * as logger from '../../../src/action/log';
67
import NotificationAction from '../../../src/action/notification';
@@ -57,6 +58,8 @@ describe('Action Integration Tests', function() {
5758

5859
let store1;
5960
let store2;
61+
let db1;
62+
let db2;
6063
let sandbox;
6164
let lndProcess1;
6265
let lndProcess2;
@@ -133,21 +136,23 @@ describe('Action Integration Tests', function() {
133136
macaroonsEnabled: MACAROONS_ENABLED,
134137
});
135138

139+
db1 = sinon.createStubInstance(AppStorage);
136140
nav1 = sinon.createStubInstance(NavAction);
137141
notify1 = sinon.createStubInstance(NotificationAction, nav1);
138142
grpc1 = new GrpcAction(store1, ipcRendererStub1);
139143
info1 = new InfoAction(store1, grpc1, notify1);
140-
wallet1 = new WalletAction(store1, grpc1, nav1, notify1);
144+
wallet1 = new WalletAction(store1, grpc1, db1, nav1, notify1);
141145
channels1 = new ChannelAction(store1, grpc1, nav1, notify1);
142146
transactions1 = new TransactionAction(store1, grpc1, wallet1, nav1);
143147
invoice1 = new InvoiceAction(store1, grpc1, transactions1, nav1, notify1);
144148
payments1 = new PaymentAction(store1, grpc1, transactions1, nav1, notify1);
145149

150+
db2 = sinon.createStubInstance(AppStorage);
146151
nav2 = sinon.createStubInstance(NavAction);
147152
notify2 = sinon.createStubInstance(NotificationAction, nav2);
148153
grpc2 = new GrpcAction(store2, ipcRendererStub2);
149154
info2 = new InfoAction(store2, grpc2, notify2);
150-
wallet2 = new WalletAction(store2, grpc2, nav2, notify2);
155+
wallet2 = new WalletAction(store2, grpc2, db2, nav2, notify2);
151156
channels2 = new ChannelAction(store2, grpc2, nav2, notify2);
152157
transactions2 = new TransactionAction(store2, grpc2, wallet2, nav2);
153158
invoice2 = new InvoiceAction(store2, grpc2, transactions2, nav2, notify2);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Store } from '../../../src/store';
2+
import AppStorage from '../../../src/action/app-storage';
3+
import * as logger from '../../../src/action/log';
4+
5+
describe('Action App Storage Unit Tests', () => {
6+
let sandbox;
7+
let store;
8+
let AsyncStorageStub;
9+
let db;
10+
11+
beforeEach(() => {
12+
sandbox = sinon.createSandbox({});
13+
sandbox.stub(logger);
14+
store = new Store();
15+
AsyncStorageStub = {
16+
getItem: sinon.stub(),
17+
setItem: sinon.stub(),
18+
clear: sinon.stub(),
19+
};
20+
db = new AppStorage(store, AsyncStorageStub);
21+
});
22+
23+
afterEach(() => {
24+
sandbox.restore();
25+
});
26+
27+
describe('restore()', () => {
28+
it('should use default if nothing is saved yet', async () => {
29+
AsyncStorageStub.getItem.resolves(undefined);
30+
await db.restore(AsyncStorageStub);
31+
expect(store.settings.unit, 'to equal', 'btc');
32+
expect(logger.error, 'was not called');
33+
expect(store.loaded, 'to be', true);
34+
});
35+
36+
it('should set supported setting', async () => {
37+
AsyncStorageStub.getItem
38+
.withArgs('settings')
39+
.resolves(JSON.stringify({ unit: 'sat' }));
40+
await db.restore(AsyncStorageStub);
41+
expect(store.settings.unit, 'to equal', 'sat');
42+
expect(store.loaded, 'to be', true);
43+
});
44+
45+
it('should set supported object', async () => {
46+
AsyncStorageStub.getItem.resolves(
47+
JSON.stringify({ exchangeRate: { usd: 0.1 } })
48+
);
49+
await db.restore(AsyncStorageStub);
50+
expect(store.settings.exchangeRate, 'to equal', { usd: 0.1 });
51+
});
52+
53+
it('should not set supported setting', async () => {
54+
AsyncStorageStub.getItem.resolves(JSON.stringify({ invalid: 'bar' }));
55+
await db.restore(AsyncStorageStub);
56+
expect(store.settings.invalid, 'to be', undefined);
57+
});
58+
59+
it('should log error', async () => {
60+
AsyncStorageStub.getItem.rejects(new Error('Boom!'));
61+
await db.restore(AsyncStorageStub);
62+
expect(logger.error, 'was called once');
63+
expect(store.loaded, 'to be', true);
64+
});
65+
});
66+
67+
describe('save()', () => {
68+
it('should save all settings', async () => {
69+
store.settings = { foo: 'bar' };
70+
await db.save();
71+
const state = JSON.stringify(store.settings);
72+
expect(AsyncStorageStub.setItem, 'was called with', 'settings', state);
73+
});
74+
75+
it('should log error', async () => {
76+
AsyncStorageStub.setItem.rejects(new Error('Boom!'));
77+
await db.save();
78+
expect(logger.error, 'was called once');
79+
});
80+
});
81+
82+
describe('clear()', () => {
83+
it('should clear all settings', async () => {
84+
await db.clear();
85+
expect(AsyncStorageStub.clear, 'was called once');
86+
});
87+
88+
it('should log error', async () => {
89+
AsyncStorageStub.clear.rejects(new Error('Boom!'));
90+
await db.clear();
91+
expect(logger.error, 'was called once');
92+
});
93+
});
94+
});

0 commit comments

Comments
 (0)