Skip to content

Commit e9693c4

Browse files
authored
Merge pull request #53 from lightninglabs/socket-streaming
Implement server-side streaming to update UI
2 parents 05f2d96 + df3c978 commit e9693c4

26 files changed

+541
-190
lines changed

app/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"storybook": "start-storybook -p 9009 -s public",
1717
"build-storybook": "build-storybook -s public"
1818
},
19-
"proxy": "https://localhost:8443",
2019
"dependencies": {
2120
"@emotion/core": "10.0.28",
2221
"@emotion/styled": "10.0.27",
@@ -28,8 +27,10 @@
2827
"debug": "4.1.1",
2928
"emotion-theming": "10.0.27",
3029
"file-saver": "2.0.2",
30+
"http-proxy-middleware": "1.0.4",
3131
"i18next": "19.4.4",
3232
"i18next-browser-languagedetector": "4.1.1",
33+
"lodash": "4.17.15",
3334
"lottie-web": "5.6.8",
3435
"mobx": "5.15.4",
3536
"mobx-react-lite": "2.0.6",
@@ -55,6 +56,7 @@
5556
"@types/debug": "4.1.5",
5657
"@types/google-protobuf": "3.7.2",
5758
"@types/jest": "^25.2.1",
59+
"@types/lodash": "4.14.155",
5860
"@types/node": "^13.13.5",
5961
"@types/react": "^16.9.34",
6062
"@types/react-dom": "^16.9.7",
@@ -88,6 +90,7 @@
8890
"!src/types/**/*.{js,ts}",
8991
"!src/i18n/**/*.{js,ts}",
9092
"!src/util/tests/**/*.{ts,tsx}",
93+
"!src/setupProxy.js",
9194
"!src/index.tsx"
9295
]
9396
},

app/src/__mocks__/@improbable-eng/grpc-web.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const grpc = {
99
OK: 0,
1010
Canceled: 1,
1111
},
12+
WebsocketTransport: jest.fn(),
1213
// mock unary function to simulate GRPC requests
1314
unary: jest
1415
.fn()
@@ -31,4 +32,14 @@ export const grpc = {
3132
});
3233
},
3334
),
35+
// mock client function to simulate server-side streaming
36+
client: jest.fn().mockImplementation(() => {
37+
return {
38+
onHeaders: jest.fn(func => func()),
39+
onMessage: jest.fn(func => func({ toObject: jest.fn() })),
40+
onEnd: jest.fn(func => func()),
41+
start: jest.fn(),
42+
send: jest.fn(),
43+
};
44+
}),
3445
};

app/src/__stories__/ChannelBalance.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { lndListChannelsOne } from 'util/tests/sampleData';
2+
import { lndChannel } from 'util/tests/sampleData';
33
import { Store, useStore } from 'store';
44
import { Channel } from 'store/models';
55
import ChannelBalance from 'components/loop/ChannelBalance';
@@ -11,7 +11,7 @@ export default {
1111
};
1212

1313
const getChannel = (store: Store, ratio: number) => {
14-
const channel = new Channel(store, lndListChannelsOne.channelsList[0]);
14+
const channel = new Channel(store, lndChannel);
1515
channel.localBalance = channel.capacity.mul(ratio);
1616
channel.remoteBalance = channel.capacity.mul(1 - ratio);
1717
return channel;

app/src/__stories__/HistoryPage.stories.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ const updateSwapsStatus = (swaps: Swap[]) => {
2323

2424
export const Default = () => {
2525
const store = useStore();
26-
store.swapStore.stopAutoPolling();
2726
updateSwapsStatus(store.swapStore.sortedSwaps);
2827
return <HistoryPage />;
2928
};
3029

3130
export const InsideLayout = () => {
3231
const store = useStore();
3332
store.uiStore.page = 'history';
34-
store.swapStore.stopAutoPolling();
3533
updateSwapsStatus(store.swapStore.sortedSwaps);
3634
return (
3735
<Layout>

app/src/__stories__/ProcessingSwaps.stories.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,12 @@ const delay = (timeout: number) =>
6666

6767
export const AllSwapStates = () => {
6868
const store = useStore();
69-
store.swapStore.stopAutoPolling();
7069
store.swapStore.swaps = createSwaps();
7170
return <ProcessingSwaps />;
7271
};
7372

7473
export const LoopInProgress = () => {
7574
const store = useStore();
76-
store.swapStore.stopAutoPolling();
7775
const swap = mockSwap(LOOP_IN, INITIATED);
7876
store.swapStore.swaps = observable.map({ [swap.id]: swap });
7977

@@ -98,7 +96,6 @@ export const LoopInProgress = () => {
9896

9997
export const LoopOutProgress = () => {
10098
const store = useStore();
101-
store.swapStore.stopAutoPolling();
10299
const swap = mockSwap(LOOP_OUT, INITIATED);
103100
store.swapStore.swaps = observable.map({ [swap.id]: swap });
104101

app/src/__stories__/StoryWrapper.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const grpc = {
2020
const response: any = { toObject: () => data };
2121
return Promise.resolve(response);
2222
},
23+
subscribe: () => undefined,
2324
};
2425

2526
// fake the AppStorage dependency so that settings aren't shared across stories

app/src/__tests__/components/loop/ChannelBalance.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { renderWithProviders } from 'util/tests';
3-
import { lndListChannelsOne } from 'util/tests/sampleData';
3+
import { lndChannel } from 'util/tests/sampleData';
44
import { createStore } from 'store';
55
import { Channel } from 'store/models';
66
import ChannelBalance from 'components/loop/ChannelBalance';
@@ -13,7 +13,7 @@ describe('ChannelBalance component', () => {
1313

1414
const render = (ratio: number, active = true) => {
1515
const store = createStore();
16-
channel = new Channel(store, lndListChannelsOne.channelsList[0]);
16+
channel = new Channel(store, lndChannel);
1717
channel.localBalance = channel.capacity.mul(ratio);
1818
channel.remoteBalance = channel.capacity.mul(1 - ratio);
1919
channel.active = active;

app/src/__tests__/store/authStore.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { grpc } from '@improbable-eng/grpc-web';
2+
import { waitFor } from '@testing-library/react';
23
import AppStorage from 'util/appStorage';
4+
import { lndListChannels } from 'util/tests/sampleData';
35
import { AuthStore, createStore, Store } from 'store';
46
import { PersistentSettings } from 'store/stores/settingsStore';
57

@@ -65,4 +67,16 @@ describe('AuthStore', () => {
6567
await store.init();
6668
expect(store.credentials).toBe('');
6769
});
70+
71+
it('should fetch data after authentication succeeds', async () => {
72+
await rootStore.init();
73+
expect(rootStore.channelStore.channels.size).toBe(0);
74+
await store.login('test-pw');
75+
expect(store.authenticated).toBe(true);
76+
await waitFor(() => {
77+
expect(rootStore.channelStore.channels.size).toBe(
78+
lndListChannels.channelsList.length,
79+
);
80+
});
81+
});
6882
});

app/src/__tests__/store/channelStore.spec.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { observable, ObservableMap, values } from 'mobx';
2+
import * as LND from 'types/generated/lnd_pb';
23
import { grpc } from '@improbable-eng/grpc-web';
34
import { waitFor } from '@testing-library/react';
45
import Big from 'big.js';
56
import { BalanceMode } from 'util/constants';
6-
import { lndListChannels } from 'util/tests/sampleData';
7+
import { lndChannelEvent, lndListChannels } from 'util/tests/sampleData';
78
import { createStore, Store } from 'store';
89
import Channel from 'store/models/channel';
910
import ChannelStore from 'store/stores/channelStore';
@@ -118,4 +119,60 @@ describe('ChannelStore', () => {
118119

119120
expect(+store.totalOutbound).toBe(outbound);
120121
});
122+
123+
describe('onChannelEvent', () => {
124+
const {
125+
OPEN_CHANNEL,
126+
CLOSED_CHANNEL,
127+
ACTIVE_CHANNEL,
128+
INACTIVE_CHANNEL,
129+
} = LND.ChannelEventUpdate.UpdateType;
130+
131+
beforeEach(async () => {
132+
await store.fetchChannels();
133+
});
134+
135+
it('should handle inactive channel event', async () => {
136+
const event = { ...lndChannelEvent, type: INACTIVE_CHANNEL };
137+
const len = lndListChannels.channelsList.length;
138+
expect(store.activeChannels).toHaveLength(len);
139+
store.onChannelEvent(event);
140+
expect(store.activeChannels).toHaveLength(len - 1);
141+
});
142+
143+
it('should handle active channel event', async () => {
144+
await store.fetchChannels();
145+
const chan = store.channels.get(lndListChannels.channelsList[0].chanId) as Channel;
146+
chan.active = false;
147+
const event = { ...lndChannelEvent, type: ACTIVE_CHANNEL };
148+
const len = lndListChannels.channelsList.length;
149+
expect(store.activeChannels).toHaveLength(len - 1);
150+
store.onChannelEvent(event);
151+
expect(store.activeChannels).toHaveLength(len);
152+
});
153+
154+
it('should handle open channel event', async () => {
155+
const event = { ...lndChannelEvent, type: OPEN_CHANNEL };
156+
event.openChannel.chanId = '12345';
157+
expect(store.channels.get('12345')).toBeUndefined();
158+
store.onChannelEvent(event);
159+
expect(store.channels.get('12345')).toBeDefined();
160+
});
161+
162+
it('should handle close channel event', async () => {
163+
const event = { ...lndChannelEvent, type: CLOSED_CHANNEL };
164+
const chanId = event.closedChannel.chanId;
165+
expect(store.channels.get(chanId)).toBeDefined();
166+
store.onChannelEvent(event);
167+
expect(store.channels.get(chanId)).toBeUndefined();
168+
});
169+
170+
it('should do nothing for unknown channel event type', async () => {
171+
const event = { ...lndChannelEvent, type: 99 };
172+
const len = lndListChannels.channelsList.length;
173+
expect(store.activeChannels).toHaveLength(len);
174+
store.onChannelEvent(event as any);
175+
expect(store.activeChannels).toHaveLength(len);
176+
});
177+
});
121178
});

app/src/__tests__/store/nodeStore.spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { values } from 'mobx';
22
import { grpc } from '@improbable-eng/grpc-web';
33
import { waitFor } from '@testing-library/react';
4-
import { lndChannelBalance, lndGetInfo, lndWalletBalance } from 'util/tests/sampleData';
4+
import {
5+
lndChannelBalance,
6+
lndGetInfo,
7+
lndTransaction,
8+
lndWalletBalance,
9+
} from 'util/tests/sampleData';
510
import { createStore, NodeStore, Store } from 'store';
611

712
const grpcMock = grpc as jest.Mocked<typeof grpc>;
@@ -60,4 +65,18 @@ describe('NodeStore', () => {
6065
expect(values(rootStore.uiStore.alerts)[0].message).toBe('test-err');
6166
});
6267
});
68+
69+
it('should handle a transaction event', () => {
70+
expect(+store.wallet.walletBalance).toBe(0);
71+
store.onTransaction(lndTransaction);
72+
expect(+store.wallet.walletBalance).toBe(lndTransaction.amount);
73+
});
74+
75+
it('should handle duplicate transaction events', () => {
76+
expect(+store.wallet.walletBalance).toBe(0);
77+
store.onTransaction(lndTransaction);
78+
expect(+store.wallet.walletBalance).toBe(lndTransaction.amount);
79+
store.onTransaction(lndTransaction);
80+
expect(+store.wallet.walletBalance).toBe(lndTransaction.amount);
81+
});
6382
});

0 commit comments

Comments
 (0)