1
0
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:
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 */ /* @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;
}; };

View File

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

View File

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

View File

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

View File

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