mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-28 03:08:30 +00:00
changed "observeChanges" utility method
This commit is contained in:
parent
e3816be8d2
commit
0fbbdf7b71
@ -1,11 +1,15 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||||
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
import * as ACCOUNT from 'actions/constants/account';
|
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 * as reducerUtils from 'reducers/utils';
|
||||||
import { initialState } from 'reducers/SelectedAccountReducer';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AsyncAction,
|
PayloadAction,
|
||||||
Action,
|
Action,
|
||||||
GetState,
|
GetState,
|
||||||
Dispatch,
|
Dispatch,
|
||||||
@ -61,7 +65,7 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
|
|||||||
if (blockchain && !blockchain.connected) {
|
if (blockchain && !blockchain.connected) {
|
||||||
return {
|
return {
|
||||||
type: 'backend',
|
type: 'backend',
|
||||||
title: 'Backend is not connected',
|
title: `${network.name} backend is not connected`,
|
||||||
shouldRender: false,
|
shouldRender: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -142,25 +146,29 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const observe = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
// list of all actions which has influence on "selectedAccount" reducer
|
||||||
// ignore actions dispatched from this process
|
// other actions will be ignored
|
||||||
if (action.type === ACCOUNT.UPDATE_SELECTED_ACCOUNT) {
|
const actions = [
|
||||||
return;
|
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 state: State = getState();
|
||||||
const { location } = state.router;
|
const { location } = state.router;
|
||||||
if (!location.state.account) {
|
// displayed route is not an account route
|
||||||
// displayed route is not an account route
|
if (!location.state.account) return false;
|
||||||
if (state.selectedAccount.location.length > 1) {
|
|
||||||
// reset "selectedAccount" reducer to default
|
|
||||||
dispatch({
|
|
||||||
type: ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
|
||||||
payload: initialState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get new values for selected account
|
// get new values for selected account
|
||||||
const account = reducerUtils.getSelectedAccount(state);
|
const account = reducerUtils.getSelectedAccount(state);
|
||||||
@ -186,7 +194,11 @@ export const observe = (prevState: State, action: Action): AsyncAction => async
|
|||||||
newState.notification = status || null;
|
newState.notification = status || null;
|
||||||
newState.shouldRender = status ? status.shouldRender : true;
|
newState.shouldRender = status ? status.shouldRender : true;
|
||||||
// check if newState is different than previous state
|
// 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) {
|
if (stateChanged) {
|
||||||
// update values in reducer
|
// update values in reducer
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -194,4 +206,5 @@ export const observe = (prevState: State, action: Action): AsyncAction => async
|
|||||||
payload: newState,
|
payload: newState,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return stateChanged;
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import * as SEND from 'actions/constants/send';
|
import * as SEND from 'actions/constants/send';
|
||||||
import * as WEB3 from 'actions/constants/web3';
|
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,
|
type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR,
|
||||||
} | SendTxAction;
|
} | 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 => {
|
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();
|
const currentState = getState();
|
||||||
// do not proceed if it's not "send" url
|
// do not proceed if it's not "send" url
|
||||||
if (!currentState.router.location.state.send) return;
|
if (!currentState.router.location.state.send) return;
|
||||||
@ -75,16 +87,9 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
|
|
||||||
let shouldUpdate: boolean = false;
|
let shouldUpdate: boolean = false;
|
||||||
// check if "selectedAccount" reducer changed
|
// check if "selectedAccount" reducer changed
|
||||||
const selectedAccountChanged = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, ['account', 'tokens', 'pending']);
|
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
|
||||||
if (selectedAccountChanged) {
|
account: ['balance', 'nonce'],
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if "sendForm" reducer changed
|
// check if "sendForm" reducer changed
|
||||||
if (!shouldUpdate) {
|
if (!shouldUpdate) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||||
|
import { DEVICE } from 'trezor-connect';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
import * as reducerUtils from 'reducers/utils';
|
import * as reducerUtils from 'reducers/utils';
|
||||||
|
|
||||||
@ -8,7 +10,7 @@ import type {
|
|||||||
TrezorDevice,
|
TrezorDevice,
|
||||||
RouterLocationState,
|
RouterLocationState,
|
||||||
ThunkAction,
|
ThunkAction,
|
||||||
AsyncAction,
|
PayloadAction,
|
||||||
Action,
|
Action,
|
||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
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 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 device = reducerUtils.getSelectedDevice(state);
|
||||||
const selectedDeviceChanged = reducerUtils.observeChanges(state.wallet.selectedDevice, device);
|
const selectedDeviceChanged = reducerUtils.observeChanges(state.wallet.selectedDevice, device);
|
||||||
|
|
||||||
// handle devices state change (from trezor-connect events or location change)
|
// handle devices state change (from trezor-connect events or location change)
|
||||||
if (locationChanged || selectedDeviceChanged) {
|
if (locationChanged || selectedDeviceChanged) {
|
||||||
if (device && reducerUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
|
if (device && reducerUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
|
||||||
@ -103,5 +113,7 @@ export const observe = (prevState: State, action: Action): AsyncAction => async
|
|||||||
device,
|
device,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
@ -116,22 +116,58 @@ export const getWeb3 = (state: State): ?Web3Instance => {
|
|||||||
return state.web3.find(w3 => w3.network === locationState.network);
|
return state.web3.find(w3 => w3.network === locationState.network);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const observeChanges = (prev: ?(Object | Array<any>), current: ?(Object | Array<any>), fields?: Array<string>): boolean => {
|
export const observeChanges = (prev: ?Object, current: ?Object, filter?: {[k: string]: Array<string>}): boolean => {
|
||||||
if (prev !== current) {
|
// 1. both objects are the same (solves simple types like string, boolean and number)
|
||||||
// 1. one of the objects is null/undefined
|
if (prev === current) return false;
|
||||||
if (!prev || !current) return true;
|
// 2. one of the objects is null/undefined
|
||||||
// 2. object are Arrays and they have different length
|
if (!prev || !current) return true;
|
||||||
if (Array.isArray(prev) && Array.isArray(current)) return prev.length !== current.length;
|
|
||||||
// 3. no nested field to check
|
const prevType = Object.prototype.toString.call(current);
|
||||||
if (!Array.isArray(fields)) return true;
|
const currentType = Object.prototype.toString.call(current);
|
||||||
// 4. validate nested field
|
// 3. one of the objects has different type then other
|
||||||
if (prev instanceof Object && current instanceof Object) {
|
if (prevType !== currentType) return true;
|
||||||
for (let i = 0; i < fields.length; i++) {
|
|
||||||
const key = fields[i];
|
if (currentType === '[object Array]') {
|
||||||
if (Array.isArray(prev[key]) && Array.isArray(current[key]) && prev[key].length !== current[key].length) return true;
|
// 4. Array length is different
|
||||||
if (prev[key] !== current[key]) return true;
|
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;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,7 @@ import type {
|
|||||||
/**
|
/**
|
||||||
* Middleware
|
* 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();
|
const prevState = api.getState();
|
||||||
|
|
||||||
// Application live cycle starts HERE!
|
// Application live cycle starts HERE!
|
||||||
@ -88,14 +88,14 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
|||||||
api.dispatch(NotificationActions.clear(prevLocation.state, currentLocation.state));
|
api.dispatch(NotificationActions.clear(prevLocation.state, currentLocation.state));
|
||||||
}
|
}
|
||||||
|
|
||||||
// observe send form props changes
|
// observe common values in WallerReducer
|
||||||
api.dispatch(SendFormActionActions.observe(prevState, action));
|
if (!await api.dispatch(WalletActions.observe(prevState, action))) {
|
||||||
|
// if "selectedDevice" didn't change observe common values in SelectedAccountReducer
|
||||||
// update common values in WallerReducer
|
if (!await api.dispatch(SelectedAccountActions.observe(prevState, action))) {
|
||||||
api.dispatch(WalletActions.observe(prevState, action));
|
// if "selectedAccount" didn't change observe send form props changes
|
||||||
|
api.dispatch(SendFormActionActions.observe(prevState, action));
|
||||||
// update common values in SelectedAccountReducer
|
}
|
||||||
api.dispatch(SelectedAccountActions.observe(prevState, action));
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user