@ledgerhq/live-countervalues-react 0.2.45-nightly.0 → 0.2.45-nightly.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +20 -0
- package/lib/CountervaluesMarketcapProvider.d.ts +23 -0
- package/lib/CountervaluesMarketcapProvider.d.ts.map +1 -0
- package/lib/CountervaluesMarketcapProvider.js +89 -0
- package/lib/CountervaluesMarketcapProvider.js.map +1 -0
- package/lib/index.d.ts +44 -18
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +81 -131
- package/lib/index.js.map +1 -1
- package/lib-es/CountervaluesMarketcapProvider.d.ts +23 -0
- package/lib-es/CountervaluesMarketcapProvider.d.ts.map +1 -0
- package/lib-es/CountervaluesMarketcapProvider.js +58 -0
- package/lib-es/CountervaluesMarketcapProvider.js.map +1 -0
- package/lib-es/index.d.ts +44 -18
- package/lib-es/index.d.ts.map +1 -1
- package/lib-es/index.js +78 -124
- package/lib-es/index.js.map +1 -1
- package/package.json +5 -5
- package/src/CountervaluesMarketcapProvider.tsx +94 -0
- package/src/index.tsx +120 -180
package/src/index.tsx
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { getAccountCurrency } from "@ledgerhq/coin-framework/account/helpers";
|
|
2
|
-
import api from "@ledgerhq/live-countervalues/api/index";
|
|
3
2
|
import {
|
|
4
3
|
calculate,
|
|
5
|
-
exportCountervalues,
|
|
6
4
|
importCountervalues,
|
|
7
5
|
inferTrackingPairForAccounts,
|
|
8
|
-
initialState,
|
|
9
6
|
loadCountervalues,
|
|
10
7
|
trackingPairForTopCoins,
|
|
11
8
|
} from "@ledgerhq/live-countervalues/logic";
|
|
@@ -16,7 +13,6 @@ import type {
|
|
|
16
13
|
TrackingPair,
|
|
17
14
|
} from "@ledgerhq/live-countervalues/types";
|
|
18
15
|
import { useDebounce } from "@ledgerhq/live-hooks/useDebounce";
|
|
19
|
-
import { log } from "@ledgerhq/logs";
|
|
20
16
|
import type { Currency, Unit } from "@ledgerhq/types-cryptoassets";
|
|
21
17
|
import type { Account, AccountLike } from "@ledgerhq/types-live";
|
|
22
18
|
import { BigNumber } from "bignumber.js";
|
|
@@ -27,17 +23,33 @@ import React, {
|
|
|
27
23
|
useContext,
|
|
28
24
|
useEffect,
|
|
29
25
|
useMemo,
|
|
30
|
-
useReducer,
|
|
31
|
-
useState,
|
|
32
26
|
} from "react";
|
|
27
|
+
import { useMarketcapIds } from "./CountervaluesMarketcapProvider";
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
export { CountervaluesMarketcapProvider, useMarketcapIds } from "./CountervaluesMarketcapProvider";
|
|
30
|
+
|
|
31
|
+
export interface PollingState {
|
|
32
|
+
isPolling: boolean;
|
|
33
|
+
triggerRef: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Bridge enabling platform-specific persistence of countervalues state.
|
|
38
|
+
* @note: make sure that the object is memoized to avoid re-renders.
|
|
39
|
+
*/
|
|
40
|
+
export interface CountervaluesBridge {
|
|
41
|
+
setPollingIsPolling(polling: boolean): void;
|
|
42
|
+
setPollingTriggerLoad(triggerLoad: boolean): void;
|
|
43
|
+
setState(state: CounterValuesState): void;
|
|
44
|
+
setStateError(error: Error): void;
|
|
45
|
+
setStatePending(pending: boolean): void;
|
|
46
|
+
usePollingIsPolling(): boolean;
|
|
47
|
+
usePollingTriggerLoad(): boolean;
|
|
48
|
+
useStateError(): Error | null;
|
|
49
|
+
useStatePending(): boolean;
|
|
50
|
+
useState(): CounterValuesState;
|
|
51
|
+
useUserSettings(): CountervaluesSettings;
|
|
52
|
+
wipe(): void;
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
// Polling is the control object you get from the high level <PollingConsumer>{ polling => ...
|
|
@@ -59,36 +71,32 @@ export type Polling = {
|
|
|
59
71
|
};
|
|
60
72
|
|
|
61
73
|
export type Props = {
|
|
74
|
+
/** Bridge enabling platform-specific persistence of countervalues state. */
|
|
75
|
+
bridge: CountervaluesBridge;
|
|
62
76
|
children: React.ReactNode;
|
|
63
|
-
|
|
64
|
-
// the time to wait before the first poll when app starts (allow things to render to not do all at boot time)
|
|
77
|
+
/** the time to wait before the first poll when app starts (allow things to render to not do all at boot time) */
|
|
65
78
|
pollInitDelay?: number;
|
|
66
|
-
|
|
79
|
+
/** the minimum time to wait before two automatic polls (then one that happen whatever network/appstate events) */
|
|
67
80
|
autopollInterval?: number;
|
|
68
|
-
|
|
81
|
+
/** debounce time before actually fetching */
|
|
69
82
|
debounceDelay?: number;
|
|
70
83
|
savedState?: CounterValuesStateRaw;
|
|
71
84
|
};
|
|
72
85
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
stop: () => {},
|
|
78
|
-
pending: false,
|
|
79
|
-
error: null,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const CountervaluesUserSettingsContext = createContext<CountervaluesSettings>({
|
|
83
|
-
// dummy values that are overriden by the context provider
|
|
84
|
-
trackingPairs: [],
|
|
85
|
-
autofillGaps: true,
|
|
86
|
-
refreshRate: 0,
|
|
87
|
-
marketCapBatchingAfterRank: 0,
|
|
88
|
-
});
|
|
86
|
+
/**
|
|
87
|
+
* Base Countervalues Context to use without polling logic.
|
|
88
|
+
*/
|
|
89
|
+
export const CountervaluesContext = createContext<CountervaluesBridge | null>(null);
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
const
|
|
91
|
+
function useCountervaluesBridgeContext() {
|
|
92
|
+
const bridge = useContext(CountervaluesContext);
|
|
93
|
+
if (!bridge) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
"'useCountervaluesBridgeContext' must be used within a 'CountervaluesProvider'",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return bridge;
|
|
99
|
+
}
|
|
92
100
|
|
|
93
101
|
function trackingPairsHash(a: TrackingPair[]) {
|
|
94
102
|
return a
|
|
@@ -97,71 +105,20 @@ function trackingPairsHash(a: TrackingPair[]) {
|
|
|
97
105
|
.join("|");
|
|
98
106
|
}
|
|
99
107
|
|
|
100
|
-
const marketcapRefresh = 30 * 60000;
|
|
101
|
-
const marketcapRefreshOnError = 60000;
|
|
102
|
-
|
|
103
|
-
/** Provides market-cap ids via the supplied bridge. */
|
|
104
|
-
export function CountervaluesMarketcapProvider({
|
|
105
|
-
children,
|
|
106
|
-
bridge,
|
|
107
|
-
}: {
|
|
108
|
-
children: React.ReactNode;
|
|
109
|
-
bridge: CountervaluesMarketcapBridge;
|
|
110
|
-
}): ReactElement {
|
|
111
|
-
const ids = bridge.useIds();
|
|
112
|
-
const lastUpdated = bridge.useLastUpdated();
|
|
113
|
-
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
114
|
-
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
117
|
-
const now = Date.now();
|
|
118
|
-
|
|
119
|
-
if (!lastUpdated || now - lastUpdated > marketcapRefresh) {
|
|
120
|
-
bridge.setLoading(true);
|
|
121
|
-
api.fetchIdsSortedByMarketcap().then(
|
|
122
|
-
fetchedIds => {
|
|
123
|
-
bridge.setIds(fetchedIds);
|
|
124
|
-
timeout = setTimeout(() => forceUpdate(), marketcapRefresh);
|
|
125
|
-
},
|
|
126
|
-
error => {
|
|
127
|
-
log("countervalues", "error fetching marketcap ids " + error);
|
|
128
|
-
bridge.setError(error.message);
|
|
129
|
-
timeout = setTimeout(() => forceUpdate(), marketcapRefreshOnError);
|
|
130
|
-
},
|
|
131
|
-
);
|
|
132
|
-
} else {
|
|
133
|
-
timeout = setTimeout(() => forceUpdate(), marketcapRefresh - (now - lastUpdated));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return () => {
|
|
137
|
-
if (timeout) clearTimeout(timeout);
|
|
138
|
-
};
|
|
139
|
-
}, [lastUpdated, bridge]);
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<CountervaluesMarketcapIdsContext.Provider value={ids}>
|
|
143
|
-
{children}
|
|
144
|
-
</CountervaluesMarketcapIdsContext.Provider>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
108
|
/**
|
|
149
|
-
*
|
|
109
|
+
* Call side effects outside of the primary render tree, avoiding costly child re-renders
|
|
150
110
|
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
userSettings,
|
|
154
|
-
pollInitDelay = 3 * 1000,
|
|
155
|
-
debounceDelay = 1000,
|
|
111
|
+
function Effect({
|
|
112
|
+
bridge,
|
|
156
113
|
savedState,
|
|
157
|
-
|
|
158
|
-
|
|
114
|
+
debounceDelay = 1000,
|
|
115
|
+
pollInitDelay = 3 * 1000,
|
|
116
|
+
}: Pick<Props, "autopollInterval" | "bridge" | "debounceDelay" | "pollInitDelay" | "savedState">) {
|
|
117
|
+
const userSettings = bridge.useUserSettings();
|
|
118
|
+
const { refreshRate, marketCapBatchingAfterRank } = userSettings;
|
|
159
119
|
const debouncedUserSettings = useDebounce(userSettings, debounceDelay);
|
|
160
|
-
const [{ state, pending, error }, dispatch] = useReducer(fetchReducer, initialFetchState);
|
|
161
120
|
|
|
162
|
-
|
|
163
|
-
const marketcapIds = useContext(CountervaluesMarketcapIdsContext);
|
|
164
|
-
const { marketCapBatchingAfterRank } = userSettings;
|
|
121
|
+
const marketcapIds = useMarketcapIds();
|
|
165
122
|
|
|
166
123
|
const batchStrategySolver = useMemo(
|
|
167
124
|
() => ({
|
|
@@ -175,125 +132,106 @@ export function CountervaluesProvider({
|
|
|
175
132
|
);
|
|
176
133
|
|
|
177
134
|
// flag used to trigger a loadCountervalues
|
|
178
|
-
const
|
|
179
|
-
|
|
135
|
+
const triggerLoad = bridge.usePollingTriggerLoad();
|
|
136
|
+
|
|
137
|
+
// trigger poll only when userSettings changes in a debounced way
|
|
180
138
|
useEffect(() => {
|
|
181
|
-
|
|
182
|
-
}, [debouncedUserSettings]);
|
|
139
|
+
bridge.setPollingTriggerLoad(true);
|
|
140
|
+
}, [bridge, debouncedUserSettings]);
|
|
183
141
|
|
|
184
142
|
// loadCountervalues logic
|
|
143
|
+
const currentState = bridge.useState();
|
|
144
|
+
const pending = bridge.useStatePending();
|
|
145
|
+
|
|
146
|
+
// loadCountervalues logic using bridge
|
|
185
147
|
useEffect(() => {
|
|
186
148
|
if (pending || !triggerLoad) return;
|
|
187
|
-
|
|
188
|
-
|
|
149
|
+
bridge.setPollingTriggerLoad(false);
|
|
150
|
+
|
|
151
|
+
bridge.setStatePending(true);
|
|
189
152
|
loadCountervalues(
|
|
190
|
-
|
|
153
|
+
currentState,
|
|
191
154
|
userSettings,
|
|
192
155
|
batchStrategySolver,
|
|
193
156
|
userSettings.granularitiesRates,
|
|
194
157
|
).then(
|
|
195
|
-
|
|
196
|
-
|
|
158
|
+
s => {
|
|
159
|
+
bridge.setState(s);
|
|
160
|
+
bridge.setStatePending(false);
|
|
161
|
+
},
|
|
162
|
+
e => {
|
|
163
|
+
bridge.setStateError(e);
|
|
164
|
+
bridge.setStatePending(false);
|
|
165
|
+
},
|
|
197
166
|
);
|
|
198
|
-
}, [pending,
|
|
167
|
+
}, [pending, currentState, userSettings, triggerLoad, batchStrategySolver, bridge]);
|
|
199
168
|
|
|
200
169
|
// save the state when it changes
|
|
201
170
|
useEffect(() => {
|
|
202
171
|
if (!savedState?.status || !Object.keys(savedState.status).length) return;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
payload: importCountervalues(savedState, userSettings),
|
|
206
|
-
});
|
|
207
|
-
}, [savedState, userSettings]);
|
|
172
|
+
bridge.setState(importCountervalues(savedState, userSettings));
|
|
173
|
+
}, [bridge, savedState, userSettings]);
|
|
208
174
|
|
|
209
|
-
// manage the auto polling loop
|
|
210
|
-
const
|
|
175
|
+
// manage the auto polling loop
|
|
176
|
+
const isPolling = bridge.usePollingIsPolling();
|
|
211
177
|
useEffect(() => {
|
|
212
178
|
if (!isPolling) return;
|
|
213
179
|
let pollingTimeout: ReturnType<typeof setTimeout>;
|
|
214
180
|
function pollingLoop() {
|
|
215
|
-
|
|
216
|
-
pollingTimeout = setTimeout(pollingLoop,
|
|
181
|
+
bridge.setPollingTriggerLoad(true);
|
|
182
|
+
pollingTimeout = setTimeout(pollingLoop, refreshRate);
|
|
217
183
|
}
|
|
218
184
|
pollingTimeout = setTimeout(pollingLoop, pollInitDelay);
|
|
219
185
|
return () => clearTimeout(pollingTimeout);
|
|
220
|
-
}, [
|
|
186
|
+
}, [refreshRate, pollInitDelay, isPolling, bridge]);
|
|
221
187
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
wipe: () => dispatch({ type: "wipe" }),
|
|
225
|
-
poll: () => setTriggerLoad(true),
|
|
226
|
-
start: () => setIsPolling(true),
|
|
227
|
-
stop: () => setIsPolling(false),
|
|
228
|
-
pending,
|
|
229
|
-
error,
|
|
230
|
-
}),
|
|
231
|
-
[pending, error],
|
|
232
|
-
);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
233
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Root countervalues provider (polling + calculation).
|
|
193
|
+
*/
|
|
194
|
+
export function CountervaluesProvider({ children, bridge, ...rest }: Props): ReactElement {
|
|
234
195
|
return (
|
|
235
|
-
<
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
</CountervaluesPollingContext.Provider>
|
|
196
|
+
<CountervaluesContext.Provider value={bridge}>
|
|
197
|
+
<Effect {...rest} bridge={bridge} />
|
|
198
|
+
{children}
|
|
199
|
+
</CountervaluesContext.Provider>
|
|
240
200
|
);
|
|
241
201
|
}
|
|
242
202
|
|
|
243
|
-
type Action =
|
|
244
|
-
| { type: "success"; payload: CounterValuesState }
|
|
245
|
-
| { type: "error"; payload: Error }
|
|
246
|
-
| { type: "pending" }
|
|
247
|
-
| { type: "wipe" }
|
|
248
|
-
| { type: "setCounterValueState"; payload: CounterValuesState };
|
|
249
|
-
|
|
250
|
-
type FetchState = { state: CounterValuesState; pending: boolean; error?: Error };
|
|
251
|
-
const initialFetchState: FetchState = { state: initialState, pending: false };
|
|
252
|
-
|
|
253
|
-
function fetchReducer(state: FetchState, action: Action): FetchState {
|
|
254
|
-
switch (action.type) {
|
|
255
|
-
case "success":
|
|
256
|
-
return { state: action.payload, pending: false, error: undefined };
|
|
257
|
-
case "error":
|
|
258
|
-
return { ...state, pending: false, error: action.payload };
|
|
259
|
-
case "pending":
|
|
260
|
-
return { ...state, pending: true, error: undefined };
|
|
261
|
-
case "wipe":
|
|
262
|
-
return { state: initialState, pending: false, error: undefined };
|
|
263
|
-
case "setCounterValueState":
|
|
264
|
-
return { ...state, state: action.payload };
|
|
265
|
-
default:
|
|
266
|
-
return state;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/** Returns market-cap ids. */
|
|
271
|
-
export function useMarketcapIds(): string[] {
|
|
272
|
-
return useContext(CountervaluesMarketcapIdsContext);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
203
|
/** Returns the full countervalues state. */
|
|
276
204
|
export function useCountervaluesState(): CounterValuesState {
|
|
277
|
-
return
|
|
205
|
+
return useCountervaluesBridgeContext().useState();
|
|
278
206
|
}
|
|
279
207
|
|
|
280
|
-
|
|
208
|
+
/** Allows consumer to access the countervalues polling control object */
|
|
281
209
|
export function useCountervaluesPolling(): Polling {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
210
|
+
const bridge = useCountervaluesBridgeContext();
|
|
211
|
+
const pending = bridge.useStatePending();
|
|
212
|
+
const error = bridge.useStateError();
|
|
213
|
+
return useMemo(
|
|
214
|
+
() => ({
|
|
215
|
+
poll: () => bridge.setPollingTriggerLoad(true),
|
|
216
|
+
start: () => bridge.setPollingIsPolling(true),
|
|
217
|
+
stop: () => bridge.setPollingIsPolling(false),
|
|
218
|
+
wipe: () => bridge.wipe(),
|
|
219
|
+
pending,
|
|
220
|
+
error,
|
|
221
|
+
}),
|
|
222
|
+
[bridge, error, pending],
|
|
223
|
+
);
|
|
288
224
|
}
|
|
289
225
|
|
|
290
|
-
|
|
291
|
-
export function
|
|
292
|
-
|
|
293
|
-
return useMemo(() => exportCountervalues(state), [state]);
|
|
226
|
+
/** Allows consumer to access the user settings that was used to fetch the countervalues */
|
|
227
|
+
export function useCountervaluesUserSettings(): CountervaluesSettings {
|
|
228
|
+
return useCountervaluesBridgeContext().useUserSettings();
|
|
294
229
|
}
|
|
295
230
|
|
|
296
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Provides a way to calculate a countervalue from a value
|
|
233
|
+
* Seems like a major bottleneck, see if it actually needs the full state or we can select only the needed data
|
|
234
|
+
*/
|
|
297
235
|
export function useCalculate(query: {
|
|
298
236
|
value: number;
|
|
299
237
|
from: Currency;
|
|
@@ -303,10 +241,10 @@ export function useCalculate(query: {
|
|
|
303
241
|
reverse?: boolean;
|
|
304
242
|
}): number | null | undefined {
|
|
305
243
|
const state = useCountervaluesState();
|
|
306
|
-
return calculate(state, query);
|
|
244
|
+
return useMemo(() => calculate(state, query), [state, query]);
|
|
307
245
|
}
|
|
308
246
|
|
|
309
|
-
|
|
247
|
+
/** Provides a way to calculate a countervalue from a value using a callback */
|
|
310
248
|
export function useCalculateCountervalueCallback({
|
|
311
249
|
to,
|
|
312
250
|
}: {
|
|
@@ -366,8 +304,10 @@ export function useSendAmount({
|
|
|
366
304
|
return { fiatAmount, fiatUnit, calculateCryptoAmount };
|
|
367
305
|
}
|
|
368
306
|
|
|
369
|
-
|
|
370
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Infer the tracking pairs for the top coins that the portfolio needs to display itself
|
|
309
|
+
* if startDate is undefined, the feature is disabled
|
|
310
|
+
*/
|
|
371
311
|
export function useTrackingPairsForTopCoins(
|
|
372
312
|
marketcapIds: string[],
|
|
373
313
|
countervalue: Currency,
|