1
0
mirror of https://github.com/trezor/trezor-wallet synced 2025-01-01 03:40:53 +00:00

rewritten actions

This commit is contained in:
Szymon Lesisz 2018-09-26 14:11:37 +02:00
parent 2d4f3e8232
commit 0c0f9e7ac8
5 changed files with 231 additions and 93 deletions

View File

@ -1,12 +1,8 @@
/* @flow */ /* @flow */
import { LOCATION_CHANGE } from 'react-router-redux';
import * as ACCOUNT from 'actions/constants/account'; import * as ACCOUNT from 'actions/constants/account';
import * as NOTIFICATION from 'actions/constants/notification'; import * as reducerUtils from 'reducers/utils';
import * as PENDING from 'actions/constants/pendingTx'; import { initialState } from 'reducers/SelectedAccountReducer';
import * as stateUtils from 'reducers/utils';
import type { import type {
AsyncAction, AsyncAction,
@ -16,60 +12,191 @@ import type {
State, State,
} from 'flowtype'; } from 'flowtype';
type SelectedAccountState = $ElementType<State, 'selectedAccount'>;
export type SelectedAccountAction = { export type SelectedAccountAction = {
type: typeof ACCOUNT.DISPOSE, type: typeof ACCOUNT.DISPOSE,
} | { } | {
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT, type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload: $ElementType<State, 'selectedAccount'> payload: SelectedAccountState,
}; };
type AccountStatus = {
type: string;
title: string;
message?: string;
visible: boolean;
}
export const dispose = (): Action => ({ export const dispose = (): Action => ({
type: ACCOUNT.DISPOSE, type: ACCOUNT.DISPOSE,
}); });
export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => { const getAccountStatus = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => {
const locationChange: boolean = action.type === LOCATION_CHANGE; const device = state.wallet.selectedDevice;
if (!device || !device.state) {
return {
type: 'info',
title: 'Loading device...',
visible: false,
};
}
const {
account,
discovery,
network,
} = selectedAccount;
// corner case: accountState didn't finish loading state after LOCATION_CHANGE action
if (!network) {
return {
type: 'info',
title: 'Loading account state...',
visible: false,
};
}
const blockchain = state.blockchain.find(b => b.name === network.network);
if (blockchain && !blockchain.connected) {
return {
type: 'backend',
title: 'Backend is not connected',
visible: false,
};
}
// account not found (yet). checking why...
if (!account) {
if (!discovery || discovery.waitingForDevice) {
if (device.connected) {
// case 1: device is connected but discovery not started yet (probably waiting for auth)
if (device.available) {
return {
type: 'info',
title: 'Loading accounts...',
visible: false,
};
}
// case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
return {
type: 'info',
title: `Device ${device.instanceLabel} is unavailable`,
message: 'Change passphrase settings to use this device',
visible: false,
};
}
// case 3: device is disconnected
return {
type: 'info',
title: `Device ${device.instanceLabel} is disconnected`,
message: 'Connect device to load accounts',
visible: false,
};
}
if (discovery.completed) {
// case 4: account not found and discovery is completed
return {
type: 'warning',
title: 'Account does not exist',
visible: false,
};
}
// case 6: discovery is not completed yet
return {
type: 'info',
title: 'Loading accounts...',
visible: false,
};
}
// Additional status: account does exists and it's visible but shouldn't be active
if (!device.connected) {
return {
type: 'info',
title: `Device ${device.instanceLabel} is disconnected`,
visible: true,
};
}
if (!device.available) {
return {
type: 'info',
title: `Device ${device.instanceLabel} is unavailable`,
message: 'Change passphrase settings to use this device',
visible: true,
};
}
// Additional status: account does exists, but waiting for discovery to complete
if (discovery && !discovery.completed) {
return {
type: 'info',
title: 'Loading accounts...',
visible: true,
};
}
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;
}
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
if (state.selectedAccount.location.length > 1) {
// reset "selectedAccount" reducer to default
dispatch({
type: ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload: initialState,
});
}
return;
}
// handle devices state change (from trezor-connect events or location change) // get new values for selected account
if (locationChange const account = reducerUtils.getSelectedAccount(state);
|| prevState.accounts !== state.accounts const network = reducerUtils.getSelectedNetwork(state);
|| prevState.discovery !== state.discovery const discovery = reducerUtils.getDiscoveryProcess(state);
|| prevState.tokens !== state.tokens const tokens = reducerUtils.getAccountTokens(state, account);
|| prevState.pending !== state.pending) { const pending = reducerUtils.getAccountPendingTx(state.pending, account);
const account = stateUtils.getSelectedAccount(state);
const network = stateUtils.getSelectedNetwork(state);
const discovery = stateUtils.getDiscoveryProcess(state);
const tokens = stateUtils.getAccountTokens(state, account);
const pending = stateUtils.getAccountPendingTx(state.pending, account);
const payload: $ElementType<State, 'selectedAccount'> = { // prepare new state for "selectedAccount" reducer
location: location.pathname, const newState: SelectedAccountState = {
location: state.router.location.pathname,
account, account,
network, network,
discovery, discovery,
tokens, tokens,
pending, pending,
notification: null,
visible: false,
}; };
let needUpdate: boolean = false; // get "selectedAccount" status from newState
Object.keys(payload).forEach((key) => { const status = getAccountStatus(state, newState);
if (Array.isArray(payload[key])) { newState.notification = status || null;
if (Array.isArray(state.selectedAccount[key]) && payload[key].length !== state.selectedAccount[key].length) { newState.visible = status ? status.visible : true;
needUpdate = true;
}
} else if (payload[key] !== state.selectedAccount[key]) {
needUpdate = true;
}
});
if (needUpdate) { // check if newState is different than previous state
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, ['location', 'account', 'network', 'discovery', 'tokens', 'pending', 'status', 'visible']);
if (stateChanged) {
// update values in reducer
dispatch({ dispatch({
type: ACCOUNT.UPDATE_SELECTED_ACCOUNT, type: ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload, payload: newState,
}); });
// TODO: move this to send account actions
/*
if (location.state.send) { if (location.state.send) {
const rejectedTxs = pending.filter(tx => tx.rejected); const rejectedTxs = pending.filter(tx => tx.rejected);
rejectedTxs.forEach((tx) => { rejectedTxs.forEach((tx) => {
@ -95,6 +222,6 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
}); });
}); });
} }
} */
} }
}; };

View File

@ -1,9 +1,7 @@
/* @flow */ /* @flow */
import { LOCATION_CHANGE } from 'react-router-redux';
import * as WALLET from 'actions/constants/wallet'; import * as WALLET from 'actions/constants/wallet';
import * as stateUtils from 'reducers/utils'; import * as reducerUtils from 'reducers/utils';
import type { import type {
Device, Device,
@ -81,15 +79,20 @@ export const clearUnavailableDevicesData = (prevState: State, device: Device): T
}; };
export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => { export const observe = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const locationChange: boolean = action.type === LOCATION_CHANGE; // 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 device = reducerUtils.getSelectedDevice(state);
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 (locationChange || prevState.devices !== state.devices) { if (locationChanged || selectedDeviceChanged) {
const device = stateUtils.getSelectedDevice(state); if (device && reducerUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
if (state.wallet.selectedDevice !== device) {
if (device && stateUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
dispatch({ dispatch({
type: WALLET.UPDATE_SELECTED_DEVICE, type: WALLET.UPDATE_SELECTED_DEVICE,
device, device,
@ -101,5 +104,4 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
}); });
} }
} }
}
}; };

View File

@ -11,12 +11,18 @@ import type {
} from 'flowtype'; } from 'flowtype';
export type State = { export type State = {
location?: string; location: string;
account: ?Account; account: ?Account;
network: ?Coin; network: ?Coin;
tokens: Array<Token>, tokens: Array<Token>,
pending: Array<PendingTx>, pending: Array<PendingTx>,
discovery: ?Discovery discovery: ?Discovery,
notification: ?{
type: string,
title: string,
message?: string,
},
visible: boolean,
}; };
export const initialState: State = { export const initialState: State = {
@ -26,6 +32,8 @@ export const initialState: State = {
tokens: [], tokens: [],
pending: [], pending: [],
discovery: null, discovery: null,
notification: null,
visible: false,
}; };
export default (state: State = initialState, action: Action): State => { export default (state: State = initialState, action: Action): State => {

View File

@ -128,6 +128,7 @@ export const observeChanges = (prev: ?(Object | Array<any>), current: ?(Object |
if (prev instanceof Object && current instanceof Object) { if (prev instanceof Object && current instanceof Object) {
for (let i = 0; i < fields.length; i++) { for (let i = 0; i < fields.length; i++) {
const key = fields[i]; const key = fields[i];
if (Array.isArray(prev[key]) && Array.isArray(current[key])) return prev[key].length !== current[key].length;
if (prev[key] !== current[key]) return true; if (prev[key] !== current[key]) return true;
} }
} }

View File

@ -92,10 +92,10 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
api.dispatch(SendFormActionActions.observe(prevState, action)); api.dispatch(SendFormActionActions.observe(prevState, action));
// update common values in WallerReducer // update common values in WallerReducer
api.dispatch(WalletActions.updateSelectedValues(prevState, action)); api.dispatch(WalletActions.observe(prevState, action));
// update common values in SelectedAccountReducer // update common values in SelectedAccountReducer
api.dispatch(SelectedAccountActions.updateSelectedValues(prevState, action)); api.dispatch(SelectedAccountActions.observe(prevState, action));
return action; return action;
}; };