1
0
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:
Szymon Lesisz 2018-10-01 11:59:31 +02:00
parent e3816be8d2
commit 0fbbdf7b71
5 changed files with 129 additions and 63 deletions

View File

@ -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;
};

View File

@ -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) {

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};