mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-24 09:18:09 +00:00
changed "observeChanges" utility method
This commit is contained in:
parent
e3816be8d2
commit
0fbbdf7b71
@ -1,11 +1,15 @@
|
||||
/* @flow */
|
||||
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import * as WALLET from 'actions/constants/wallet';
|
||||
import * as ACCOUNT from 'actions/constants/account';
|
||||
import * as DISCOVERY from 'actions/constants/discovery';
|
||||
import * as TOKEN from 'actions/constants/token';
|
||||
import * as PENDING from 'actions/constants/pendingTx';
|
||||
|
||||
import * as reducerUtils from 'reducers/utils';
|
||||
import { initialState } from 'reducers/SelectedAccountReducer';
|
||||
|
||||
import type {
|
||||
AsyncAction,
|
||||
PayloadAction,
|
||||
Action,
|
||||
GetState,
|
||||
Dispatch,
|
||||
@ -61,7 +65,7 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
|
||||
if (blockchain && !blockchain.connected) {
|
||||
return {
|
||||
type: 'backend',
|
||||
title: 'Backend is not connected',
|
||||
title: `${network.name} backend is not connected`,
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
@ -142,25 +146,29 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
|
||||
return null;
|
||||
};
|
||||
|
||||
export const observe = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
// ignore actions dispatched from this process
|
||||
if (action.type === ACCOUNT.UPDATE_SELECTED_ACCOUNT) {
|
||||
return;
|
||||
}
|
||||
// list of all actions which has influence on "selectedAccount" reducer
|
||||
// other actions will be ignored
|
||||
const actions = [
|
||||
LOCATION_CHANGE,
|
||||
WALLET.SET_SELECTED_DEVICE,
|
||||
WALLET.UPDATE_SELECTED_DEVICE,
|
||||
...Object.values(ACCOUNT).filter(v => typeof v === 'string' && v !== ACCOUNT.UPDATE_SELECTED_ACCOUNT), // exported values got unwanted "__esModule: true" as first element
|
||||
...Object.values(DISCOVERY).filter(v => typeof v === 'string'),
|
||||
...Object.values(TOKEN).filter(v => typeof v === 'string'),
|
||||
...Object.values(PENDING).filter(v => typeof v === 'string'),
|
||||
];
|
||||
|
||||
/*
|
||||
* Called from WalletService
|
||||
*/
|
||||
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||
// ignore not listed actions
|
||||
if (actions.indexOf(action.type) < 0) return false;
|
||||
|
||||
const state: State = getState();
|
||||
const { location } = state.router;
|
||||
if (!location.state.account) {
|
||||
// displayed route is not an account route
|
||||
if (state.selectedAccount.location.length > 1) {
|
||||
// reset "selectedAccount" reducer to default
|
||||
dispatch({
|
||||
type: ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
||||
payload: initialState,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
// displayed route is not an account route
|
||||
if (!location.state.account) return false;
|
||||
|
||||
// get new values for selected account
|
||||
const account = reducerUtils.getSelectedAccount(state);
|
||||
@ -186,7 +194,11 @@ export const observe = (prevState: State, action: Action): AsyncAction => async
|
||||
newState.notification = status || null;
|
||||
newState.shouldRender = status ? status.shouldRender : true;
|
||||
// check if newState is different than previous state
|
||||
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, ['location', 'account', 'network', 'discovery', 'tokens', 'pending', 'notification', 'shouldRender']);
|
||||
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, {
|
||||
account: ['balance', 'nonce'],
|
||||
discovery: ['accountIndex', 'interrupted', 'completed', 'waitingForBlockchain', 'waitingForDevice'],
|
||||
});
|
||||
|
||||
if (stateChanged) {
|
||||
// update values in reducer
|
||||
dispatch({
|
||||
@ -194,4 +206,5 @@ export const observe = (prevState: State, action: Action): AsyncAction => async
|
||||
payload: newState,
|
||||
});
|
||||
}
|
||||
return stateChanged;
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as ACCOUNT from 'actions/constants/account';
|
||||
import * as NOTIFICATION from 'actions/constants/notification';
|
||||
import * as SEND from 'actions/constants/send';
|
||||
import * as WEB3 from 'actions/constants/web3';
|
||||
@ -45,10 +46,21 @@ export type SendFormAction = {
|
||||
type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR,
|
||||
} | SendTxAction;
|
||||
|
||||
// list of all actions which has influence on "sendForm" reducer
|
||||
// other actions will be ignored
|
||||
const actions = [
|
||||
ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
||||
WEB3.GAS_PRICE_UPDATED,
|
||||
...Object.values(SEND).filter(v => typeof v === 'string'),
|
||||
];
|
||||
|
||||
/*
|
||||
* Called from WalletService on EACH action
|
||||
* Called from WalletService
|
||||
*/
|
||||
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||
// ignore not listed actions
|
||||
if (actions.indexOf(action.type) < 0) return;
|
||||
|
||||
const currentState = getState();
|
||||
// do not proceed if it's not "send" url
|
||||
if (!currentState.router.location.state.send) return;
|
||||
@ -75,16 +87,9 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
||||
|
||||
let shouldUpdate: boolean = false;
|
||||
// check if "selectedAccount" reducer changed
|
||||
const selectedAccountChanged = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, ['account', 'tokens', 'pending']);
|
||||
if (selectedAccountChanged) {
|
||||
// double check
|
||||
// there are only few fields that we are interested in
|
||||
// check them to avoid unnecessary calculation and validation
|
||||
const accountChanged = reducerUtils.observeChanges(prevState.selectedAccount.account, currentState.selectedAccount.account, ['balance', 'nonce']);
|
||||
const tokensChanged = reducerUtils.observeChanges(prevState.selectedAccount.tokens, currentState.selectedAccount.tokens);
|
||||
const pendingChanged = reducerUtils.observeChanges(prevState.selectedAccount.pending, currentState.selectedAccount.pending);
|
||||
shouldUpdate = accountChanged || tokensChanged || pendingChanged;
|
||||
}
|
||||
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
|
||||
account: ['balance', 'nonce'],
|
||||
});
|
||||
|
||||
// check if "sendForm" reducer changed
|
||||
if (!shouldUpdate) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* @flow */
|
||||
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import { DEVICE } from 'trezor-connect';
|
||||
import * as WALLET from 'actions/constants/wallet';
|
||||
import * as reducerUtils from 'reducers/utils';
|
||||
|
||||
@ -8,7 +10,7 @@ import type {
|
||||
TrezorDevice,
|
||||
RouterLocationState,
|
||||
ThunkAction,
|
||||
AsyncAction,
|
||||
PayloadAction,
|
||||
Action,
|
||||
Dispatch,
|
||||
GetState,
|
||||
@ -78,18 +80,26 @@ export const clearUnavailableDevicesData = (prevState: State, device: Device): T
|
||||
}
|
||||
};
|
||||
|
||||
// list of all actions which has influence on "selectedDevice" field in "wallet" reducer
|
||||
// other actions will be ignored
|
||||
const actions = [
|
||||
LOCATION_CHANGE,
|
||||
...Object.values(DEVICE).filter(v => typeof v === 'string'),
|
||||
];
|
||||
|
||||
/*
|
||||
* Called from WalletService
|
||||
*/
|
||||
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||
// ignore not listed actions
|
||||
if (actions.indexOf(action.type) < 0) return false;
|
||||
|
||||
export const observe = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
// ignore actions dispatched from this process
|
||||
if (action.type === WALLET.SET_SELECTED_DEVICE || action.type === WALLET.UPDATE_SELECTED_DEVICE) {
|
||||
return;
|
||||
}
|
||||
const state: State = getState();
|
||||
|
||||
const locationChanged = reducerUtils.observeChanges(prevState.router.location, state.router.location, ['pathname']);
|
||||
const locationChanged = reducerUtils.observeChanges(prevState.router.location, state.router.location);
|
||||
const device = reducerUtils.getSelectedDevice(state);
|
||||
const selectedDeviceChanged = reducerUtils.observeChanges(state.wallet.selectedDevice, device);
|
||||
|
||||
|
||||
// handle devices state change (from trezor-connect events or location change)
|
||||
if (locationChanged || selectedDeviceChanged) {
|
||||
if (device && reducerUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
|
||||
@ -103,5 +113,7 @@ export const observe = (prevState: State, action: Action): AsyncAction => async
|
||||
device,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
@ -116,22 +116,58 @@ export const getWeb3 = (state: State): ?Web3Instance => {
|
||||
return state.web3.find(w3 => w3.network === locationState.network);
|
||||
};
|
||||
|
||||
export const observeChanges = (prev: ?(Object | Array<any>), current: ?(Object | Array<any>), fields?: Array<string>): boolean => {
|
||||
if (prev !== current) {
|
||||
// 1. one of the objects is null/undefined
|
||||
if (!prev || !current) return true;
|
||||
// 2. object are Arrays and they have different length
|
||||
if (Array.isArray(prev) && Array.isArray(current)) return prev.length !== current.length;
|
||||
// 3. no nested field to check
|
||||
if (!Array.isArray(fields)) return true;
|
||||
// 4. validate nested field
|
||||
if (prev instanceof Object && current instanceof Object) {
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const key = fields[i];
|
||||
if (Array.isArray(prev[key]) && Array.isArray(current[key]) && prev[key].length !== current[key].length) return true;
|
||||
if (prev[key] !== current[key]) return true;
|
||||
export const observeChanges = (prev: ?Object, current: ?Object, filter?: {[k: string]: Array<string>}): boolean => {
|
||||
// 1. both objects are the same (solves simple types like string, boolean and number)
|
||||
if (prev === current) return false;
|
||||
// 2. one of the objects is null/undefined
|
||||
if (!prev || !current) return true;
|
||||
|
||||
const prevType = Object.prototype.toString.call(current);
|
||||
const currentType = Object.prototype.toString.call(current);
|
||||
// 3. one of the objects has different type then other
|
||||
if (prevType !== currentType) return true;
|
||||
|
||||
if (currentType === '[object Array]') {
|
||||
// 4. Array length is different
|
||||
if (prev.length !== current.length) return true;
|
||||
// observe array recursive
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
if (observeChanges(prev[i], current[i], filter)) return true;
|
||||
}
|
||||
} else if (currentType === '[object Object]') {
|
||||
const prevKeys = Object.keys(prev);
|
||||
const currentKeys = Object.keys(prev);
|
||||
// 5. simple validation of keys length
|
||||
if (prevKeys.length !== currentKeys.length) return true;
|
||||
|
||||
// 6. "prev" has keys which "current" doesn't have
|
||||
const prevDifference = prevKeys.find(k => currentKeys.indexOf(k) < 0);
|
||||
if (prevDifference) return true;
|
||||
|
||||
// 7. "current" has keys which "prev" doesn't have
|
||||
const currentDifference = currentKeys.find(k => prevKeys.indexOf(k) < 0);
|
||||
if (currentDifference) return true;
|
||||
|
||||
// 8. observe every key recursive
|
||||
for (let i = 0; i < currentKeys.length; i++) {
|
||||
const key = currentKeys[i];
|
||||
if (filter && filter.hasOwnProperty(key) && prev[key] && current[key]) {
|
||||
const prevFiltered = {};
|
||||
const currentFiltered = {};
|
||||
for (let i2 = 0; i2 < filter[key].length; i2++) {
|
||||
const field = filter[key][i2];
|
||||
prevFiltered[field] = prev[key][field];
|
||||
currentFiltered[field] = current[key][field];
|
||||
}
|
||||
if (observeChanges(prevFiltered, currentFiltered)) return true;
|
||||
} else if (observeChanges(prev[key], current[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (prev !== current) {
|
||||
// solve simple types like string, boolean and number
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ import type {
|
||||
/**
|
||||
* Middleware
|
||||
*/
|
||||
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => async (action: Action): Promise<Action> => {
|
||||
const prevState = api.getState();
|
||||
|
||||
// Application live cycle starts HERE!
|
||||
@ -88,14 +88,14 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
||||
api.dispatch(NotificationActions.clear(prevLocation.state, currentLocation.state));
|
||||
}
|
||||
|
||||
// observe send form props changes
|
||||
api.dispatch(SendFormActionActions.observe(prevState, action));
|
||||
|
||||
// update common values in WallerReducer
|
||||
api.dispatch(WalletActions.observe(prevState, action));
|
||||
|
||||
// update common values in SelectedAccountReducer
|
||||
api.dispatch(SelectedAccountActions.observe(prevState, action));
|
||||
// observe common values in WallerReducer
|
||||
if (!await api.dispatch(WalletActions.observe(prevState, action))) {
|
||||
// if "selectedDevice" didn't change observe common values in SelectedAccountReducer
|
||||
if (!await api.dispatch(SelectedAccountActions.observe(prevState, action))) {
|
||||
// if "selectedAccount" didn't change observe send form props changes
|
||||
api.dispatch(SendFormActionActions.observe(prevState, action));
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user