mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-24 09:18:09 +00:00
commit
ddc190d6dc
@ -12,6 +12,7 @@
|
|||||||
"jest": true
|
"jest": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"no-use-before-define": 0,
|
||||||
"no-plusplus": 0,
|
"no-plusplus": 0,
|
||||||
"class-methods-use-this": 0,
|
"class-methods-use-this": 0,
|
||||||
"react/require-default-props": 0,
|
"react/require-default-props": 0,
|
||||||
|
@ -21,7 +21,7 @@ export type StorageAction = {
|
|||||||
type: typeof STORAGE.READY,
|
type: typeof STORAGE.READY,
|
||||||
config: Config,
|
config: Config,
|
||||||
tokens: TokensCollection,
|
tokens: TokensCollection,
|
||||||
ERC20Abi: Array<Object>
|
ERC20Abi: Array<TokensCollection>
|
||||||
} | {
|
} | {
|
||||||
type: typeof STORAGE.SAVE,
|
type: typeof STORAGE.SAVE,
|
||||||
network: string,
|
network: string,
|
||||||
@ -148,7 +148,6 @@ export function loadTokensFromJSON(): AsyncAction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: STORAGE.READY,
|
type: STORAGE.READY,
|
||||||
config,
|
config,
|
||||||
|
304
src/actions/RouterActions.js
Normal file
304
src/actions/RouterActions.js
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { push, LOCATION_CHANGE } from 'react-router-redux';
|
||||||
|
import { routes } from 'support/routes';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
RouterLocationState,
|
||||||
|
Device,
|
||||||
|
TrezorDevice,
|
||||||
|
ThunkAction,
|
||||||
|
PayloadAction,
|
||||||
|
Dispatch,
|
||||||
|
GetState,
|
||||||
|
} from 'flowtype';
|
||||||
|
import type { RouterAction } from 'react-router-redux';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse url string to RouterLocationState object (key/value)
|
||||||
|
*/
|
||||||
|
export const pathToParams = (path: string): PayloadAction<RouterLocationState> => (): RouterLocationState => {
|
||||||
|
// split url into parts
|
||||||
|
const parts: Array<string> = path.split('/').slice(1);
|
||||||
|
const params: RouterLocationState = {};
|
||||||
|
// return empty params
|
||||||
|
if (parts.length < 1 || path === '/') return params;
|
||||||
|
|
||||||
|
// map parts to params by key/value
|
||||||
|
// assuming that url is in format: "/key/value"
|
||||||
|
for (let i = 0, len = parts.length; i < len; i += 2) {
|
||||||
|
params[parts[i]] = parts[i + 1] || parts[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for special case: /device/device-id:instance-id
|
||||||
|
if (params.hasOwnProperty('device')) {
|
||||||
|
const isClonedDevice: Array<string> = params.device.split(':');
|
||||||
|
if (isClonedDevice.length > 1) {
|
||||||
|
const [device, instance] = isClonedDevice;
|
||||||
|
params.device = device;
|
||||||
|
params.deviceInstance = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RouterLocationState validation
|
||||||
|
* Check if requested device or network exists in reducers
|
||||||
|
*/
|
||||||
|
export const paramsValidation = (params: RouterLocationState): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
|
// validate requested device
|
||||||
|
if (params.hasOwnProperty('device')) {
|
||||||
|
const { devices } = getState();
|
||||||
|
|
||||||
|
let device: ?TrezorDevice;
|
||||||
|
if (params.hasOwnProperty('deviceInstance')) {
|
||||||
|
device = devices.find(d => d.features && d.features.device_id === params.device && d.instance === parseInt(params.deviceInstance, 10));
|
||||||
|
} else {
|
||||||
|
device = devices.find(d => d.path === params.device || (d.features && d.features.device_id === params.device));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate requested network
|
||||||
|
if (params.hasOwnProperty('network')) {
|
||||||
|
const { config } = getState().localStorage;
|
||||||
|
const coin = config.coins.find(c => c.network === params.network);
|
||||||
|
if (!coin) return false;
|
||||||
|
if (!params.account) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate requested account
|
||||||
|
// TODO: only if discovery on this network is completed
|
||||||
|
// if (params.hasOwnProperty('account')) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Composing url string from given RouterLocationState object
|
||||||
|
* Filters unrecognized fields and sorting in correct order
|
||||||
|
*/
|
||||||
|
export const paramsToPath = (params: RouterLocationState): PayloadAction<?string> => (): ?string => {
|
||||||
|
// get patterns (fields) from routes and sort them by complexity
|
||||||
|
const patterns: Array<Array<string>> = routes.map(r => r.fields).sort((a, b) => (a.length > b.length ? -1 : 1));
|
||||||
|
|
||||||
|
// find pattern
|
||||||
|
const keys: Array<string> = Object.keys(params);
|
||||||
|
let patternToUse: ?Array<string>;
|
||||||
|
let i: number;
|
||||||
|
for (i = 0; i < patterns.length; i++) {
|
||||||
|
const pattern = patterns[i];
|
||||||
|
const match: Array<string> = keys.filter(key => pattern.indexOf(key) >= 0);
|
||||||
|
if (match.length === pattern.length) {
|
||||||
|
patternToUse = pattern;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pattern not found, redirect back
|
||||||
|
if (!patternToUse) return null;
|
||||||
|
|
||||||
|
// compose url string from pattern
|
||||||
|
let url: string = '';
|
||||||
|
patternToUse.forEach((field) => {
|
||||||
|
if (field === params[field]) {
|
||||||
|
// standalone (odd) fields
|
||||||
|
url += `/${field}`;
|
||||||
|
} else {
|
||||||
|
url += `/${field}/${params[field]}`;
|
||||||
|
if (field === 'device') {
|
||||||
|
if (params.hasOwnProperty('deviceInstance')) {
|
||||||
|
url += `:${params.deviceInstance}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dispatch: Dispatch, getState: GetState): string => {
|
||||||
|
const { location } = getState().router;
|
||||||
|
|
||||||
|
// redirect to landing page (loading screen)
|
||||||
|
// and wait until application is ready
|
||||||
|
if (!location) return '/';
|
||||||
|
|
||||||
|
const requestedUrl = action.payload.pathname;
|
||||||
|
// Corner case: LOCATION_CHANGE was called but pathname didn't changed (redirect action from RouterService)
|
||||||
|
if (requestedUrl === location.pathname) return requestedUrl;
|
||||||
|
|
||||||
|
// Modal is opened
|
||||||
|
// redirect to previous url
|
||||||
|
if (getState().modal.opened) {
|
||||||
|
// Corner case: modal is opened and currentParams are still valid
|
||||||
|
// example 1 (valid blocking): url changed while passphrase modal opened but device is still connected (we want user to finish this action)
|
||||||
|
// example 2 (invalid blocking): url changes while passphrase modal opened because device disconnect
|
||||||
|
const currentParams = dispatch(pathToParams(location.pathname));
|
||||||
|
const currentParamsAreValid = dispatch(paramsValidation(currentParams));
|
||||||
|
if (currentParamsAreValid) { return location.pathname; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are no connected devices or application isn't ready or initialization error occurred
|
||||||
|
// redirect to landing page
|
||||||
|
const shouldBeLandingPage = getState().devices.length < 1 || !getState().wallet.ready || getState().connect.error !== null;
|
||||||
|
const landingPageUrl = dispatch(isLandingPageUrl(requestedUrl));
|
||||||
|
if (shouldBeLandingPage) {
|
||||||
|
return !landingPageUrl ? '/' : requestedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow displaying landing page
|
||||||
|
// redirect to previous url
|
||||||
|
if (!shouldBeLandingPage && landingPageUrl) {
|
||||||
|
return location.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular url change during application live cycle
|
||||||
|
const requestedParams = dispatch(pathToParams(requestedUrl));
|
||||||
|
const requestedParamsAreValid: boolean = dispatch(paramsValidation(requestedParams));
|
||||||
|
|
||||||
|
// Requested params are not valid
|
||||||
|
// Neither device or network doesn't exists
|
||||||
|
if (!requestedParamsAreValid) {
|
||||||
|
return location.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose valid url from requested params
|
||||||
|
const composedUrl = dispatch(paramsToPath(requestedParams));
|
||||||
|
return composedUrl || location.pathname;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility used in "selectDevice" and "selectFirstAvailableDevice"
|
||||||
|
* sorting device array by "ts" (timestamp) field
|
||||||
|
*/
|
||||||
|
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => devices.sort((a, b) => {
|
||||||
|
if (!a.ts || !b.ts) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return a.ts > b.ts ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compose url from given device object and redirect
|
||||||
|
*/
|
||||||
|
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
let url: ?string;
|
||||||
|
if (!device.features) {
|
||||||
|
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
|
||||||
|
} else if (device.features.bootloader_mode) {
|
||||||
|
url = `/device/${device.path}/bootloader`;
|
||||||
|
} else if (!device.features.initialized) {
|
||||||
|
url = `/device/${device.features.device_id}/initialize`;
|
||||||
|
} else if (typeof device.instance === 'number') {
|
||||||
|
url = `/device/${device.features.device_id}:${device.instance}`;
|
||||||
|
} else {
|
||||||
|
url = `/device/${device.features.device_id}`;
|
||||||
|
// make sure that device is not TrezorDevice type
|
||||||
|
if (!device.hasOwnProperty('ts')) {
|
||||||
|
// it is device from trezor-connect triggered by DEVICE.CONNECT event
|
||||||
|
// need to lookup if there are unavailable instances
|
||||||
|
const available: Array<TrezorDevice> = getState().devices.filter(d => d.path === device.path);
|
||||||
|
const latest: Array<TrezorDevice> = sortDevices(available);
|
||||||
|
if (latest.length > 0 && latest[0].instance) {
|
||||||
|
url += `:${latest[0].instance}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentParams: RouterLocationState = getState().router.location.state;
|
||||||
|
const requestedParams = dispatch(pathToParams(url));
|
||||||
|
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
|
||||||
|
dispatch(goto(url));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to find first available device using order:
|
||||||
|
* 1. First unacquired
|
||||||
|
* 2. First connected
|
||||||
|
* 3. Saved with latest timestamp
|
||||||
|
* OR redirect to landing page
|
||||||
|
*/
|
||||||
|
export const selectFirstAvailableDevice = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const { devices } = getState();
|
||||||
|
if (devices.length > 0) {
|
||||||
|
const unacquired = devices.find(d => !d.features);
|
||||||
|
if (unacquired) {
|
||||||
|
dispatch(selectDevice(unacquired));
|
||||||
|
} else {
|
||||||
|
const latest: Array<TrezorDevice> = sortDevices(devices);
|
||||||
|
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
|
||||||
|
dispatch(selectDevice(firstConnected || latest[0]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatch(gotoLandingPage());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal method. redirect to given url
|
||||||
|
*/
|
||||||
|
const goto = (url: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
if (getState().router.location.pathname !== url) {
|
||||||
|
dispatch(push(url));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if requested OR current url is landing page
|
||||||
|
*/
|
||||||
|
export const isLandingPageUrl = ($url?: string): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
|
let url: ?string = $url;
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
url = getState().router.location.pathname;
|
||||||
|
}
|
||||||
|
// TODO: add more landing page cases/urls to config.json (like /tools etc)
|
||||||
|
return (url === '/' || url === '/bridge');
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to redirect to landing page
|
||||||
|
*/
|
||||||
|
export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
|
const isLandingPage = dispatch(isLandingPageUrl());
|
||||||
|
if (!isLandingPage) {
|
||||||
|
dispatch(goto('/'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Go to given device settings page
|
||||||
|
*/
|
||||||
|
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
|
if (device.features) {
|
||||||
|
const devUrl: string = `${device.features.device_id}${device.instance ? `:${device.instance}` : ''}`;
|
||||||
|
dispatch(goto(`/device/${devUrl}/settings`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to redirect to initial url
|
||||||
|
*/
|
||||||
|
export const setInitialUrl = (): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
|
const { initialPathname } = getState().wallet;
|
||||||
|
if (typeof initialPathname === 'string' && !dispatch(isLandingPageUrl(initialPathname))) {
|
||||||
|
const valid = dispatch(getValidUrl({
|
||||||
|
type: LOCATION_CHANGE,
|
||||||
|
payload: {
|
||||||
|
pathname: initialPathname,
|
||||||
|
hash: '',
|
||||||
|
search: '',
|
||||||
|
state: {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
if (valid === initialPathname) {
|
||||||
|
dispatch(goto(valid));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
@ -7,6 +7,9 @@ import * as SEND from 'actions/constants/send';
|
|||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
|
||||||
|
import * as SendFormActions from 'actions/SendFormActions';
|
||||||
|
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
||||||
|
|
||||||
import * as stateUtils from 'reducers/utils';
|
import * as stateUtils from 'reducers/utils';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -16,8 +19,6 @@ import type {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
State,
|
State,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import * as SendFormActions from './SendFormActions';
|
|
||||||
|
|
||||||
|
|
||||||
export type SelectedAccountAction = {
|
export type SelectedAccountAction = {
|
||||||
type: typeof ACCOUNT.DISPOSE,
|
type: typeof ACCOUNT.DISPOSE,
|
||||||
@ -42,17 +43,16 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
|
|||||||
// SessionStorageActions.clear(location.pathname);
|
// SessionStorageActions.clear(location.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevState.sendForm !== state.sendForm) {
|
||||||
|
dispatch(SessionStorageActions.save());
|
||||||
|
}
|
||||||
|
|
||||||
// handle devices state change (from trezor-connect events or location change)
|
// handle devices state change (from trezor-connect events or location change)
|
||||||
if (locationChange
|
if (locationChange
|
||||||
|| prevState.accounts !== state.accounts
|
|| prevState.accounts !== state.accounts
|
||||||
|| prevState.discovery !== state.discovery
|
|| prevState.discovery !== state.discovery
|
||||||
|| prevState.tokens !== state.tokens
|
|| prevState.tokens !== state.tokens
|
||||||
|| prevState.pending !== state.pending) {
|
|| prevState.pending !== state.pending) {
|
||||||
if (locationChange) {
|
|
||||||
// dispose current account view
|
|
||||||
dispatch(dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = stateUtils.getSelectedAccount(state);
|
const account = stateUtils.getSelectedAccount(state);
|
||||||
const network = stateUtils.getSelectedNetwork(state);
|
const network = stateUtils.getSelectedNetwork(state);
|
||||||
const discovery = stateUtils.getDiscoveryProcess(state);
|
const discovery = stateUtils.getDiscoveryProcess(state);
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import TrezorConnect, {
|
import TrezorConnect, {
|
||||||
UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT
|
DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
|
||||||
import { getDuplicateInstanceNumber } from 'reducers/utils';
|
import { getDuplicateInstanceNumber } from 'reducers/utils';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import { push } from 'react-router-redux';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
DeviceMessage,
|
DeviceMessage,
|
||||||
@ -28,7 +26,6 @@ import type {
|
|||||||
AsyncAction,
|
AsyncAction,
|
||||||
Device,
|
Device,
|
||||||
TrezorDevice,
|
TrezorDevice,
|
||||||
RouterLocationState,
|
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import * as DiscoveryActions from './DiscoveryActions';
|
import * as DiscoveryActions from './DiscoveryActions';
|
||||||
|
|
||||||
@ -79,19 +76,11 @@ export type TrezorConnectAction = {
|
|||||||
type: typeof CONNECT.STOP_ACQUIRING,
|
type: typeof CONNECT.STOP_ACQUIRING,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => devices.sort((a, b) => {
|
|
||||||
if (!a.ts || !b.ts) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return a.ts > b.ts ? -1 : 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
// set listeners
|
// set listeners
|
||||||
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
|
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: DeviceMessageType = event.type; // assert flow type
|
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
device: event.payload,
|
device: event.payload,
|
||||||
@ -100,7 +89,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
|
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: UiMessageType = event.type; // assert flow type
|
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
payload: event.payload,
|
payload: event.payload,
|
||||||
@ -109,7 +98,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
|
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: TransportMessageType = event.type; // assert flow type
|
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
payload: event.payload,
|
payload: event.payload,
|
||||||
@ -118,21 +107,22 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainMessage): void => {
|
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: BlockchainMessageType = event.type; // assert flow type
|
const type: BlockchainMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
payload: event.payload,
|
payload: event.payload,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* global LOCAL */
|
||||||
// $FlowIssue LOCAL not declared
|
// $FlowIssue LOCAL not declared
|
||||||
window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/';
|
window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/'; // eslint-disable-line no-underscore-dangle
|
||||||
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/';
|
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/'; // eslint-disable-line no-underscore-dangle
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await TrezorConnect.init({
|
await TrezorConnect.init({
|
||||||
transportReconnect: true,
|
transportReconnect: true,
|
||||||
debug: true,
|
debug: false,
|
||||||
popup: false,
|
popup: false,
|
||||||
webusb: true,
|
webusb: true,
|
||||||
pendingTransportEvent: (getState().devices.length < 1),
|
pendingTransportEvent: (getState().devices.length < 1),
|
||||||
@ -145,67 +135,11 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// selection from Aside dropdown button
|
|
||||||
// after device_connect event
|
|
||||||
// or after acquiring device
|
|
||||||
// device type could be local TrezorDevice or Device (from trezor-connect device_connect event)
|
|
||||||
export const onSelectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
|
||||||
// || device.isUsedElsewhere
|
|
||||||
|
|
||||||
// switch to initial url and reset this value
|
|
||||||
|
|
||||||
if (!device.features) {
|
|
||||||
dispatch(push(`/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`));
|
|
||||||
} else if (device.features.bootloader_mode) {
|
|
||||||
dispatch(push(`/device/${device.path}/bootloader`));
|
|
||||||
} else if (!device.features.initialized) {
|
|
||||||
dispatch(push(`/device/${device.features.device_id}/initialize`));
|
|
||||||
} else if (typeof device.instance === 'number') {
|
|
||||||
dispatch(push(`/device/${device.features.device_id}:${device.instance}`));
|
|
||||||
} else {
|
|
||||||
const deviceId: string = device.features.device_id;
|
|
||||||
const urlParams: RouterLocationState = getState().router.location.state;
|
|
||||||
// let url: string = `/device/${ device.features.device_id }/network/ethereum/account/0`;
|
|
||||||
let url: string = `/device/${deviceId}`;
|
|
||||||
let instance: ?number;
|
|
||||||
// check if device is not TrezorDevice type
|
|
||||||
if (!device.hasOwnProperty('ts')) {
|
|
||||||
// its device from trezor-connect (called in initConnectedDevice triggered by device_connect event)
|
|
||||||
// need to lookup if there are unavailable instances
|
|
||||||
const available: Array<TrezorDevice> = getState().devices.filter(d => d.path === device.path);
|
|
||||||
const latest: Array<TrezorDevice> = sortDevices(available);
|
|
||||||
|
|
||||||
if (latest.length > 0 && latest[0].instance) {
|
|
||||||
url += `:${latest[0].instance}`;
|
|
||||||
instance = latest[0].instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check if current location is not set to this device
|
|
||||||
//dispatch( push(`/device/${ device.features.device_id }/network/etc/account/0`) );
|
|
||||||
|
|
||||||
if (urlParams.deviceInstance !== instance || urlParams.device !== deviceId) {
|
|
||||||
dispatch(push(url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initConnectedDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch/* , getState: GetState */): void => {
|
|
||||||
// const selected = getState().wallet.selectedDevice;
|
|
||||||
// if (!selected || (selected && selected.state)) {
|
|
||||||
dispatch(onSelectDevice(device));
|
|
||||||
// }
|
|
||||||
// if (device.unacquired && selected && selected.path !== device.path && !selected.connected) {
|
|
||||||
// dispatch( onSelectDevice(device) );
|
|
||||||
// } else if (!selected) {
|
|
||||||
// dispatch( onSelectDevice(device) );
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
|
|
||||||
// called after backend was initialized
|
// called after backend was initialized
|
||||||
// set listeners for connect/disconnect
|
// set listeners for connect/disconnect
|
||||||
export const postInit = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const postInit = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
const handleDeviceConnect = (device: Device) => {
|
const handleDeviceConnect = (device: Device) => {
|
||||||
dispatch(initConnectedDevice(device));
|
dispatch(RouterActions.selectDevice(device));
|
||||||
};
|
};
|
||||||
|
|
||||||
TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect);
|
TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect);
|
||||||
@ -214,58 +148,13 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
|
|||||||
TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
|
TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
|
||||||
TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
|
TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
|
||||||
|
|
||||||
const { devices } = getState();
|
// try to redirect to initial url
|
||||||
|
if (!dispatch(RouterActions.setInitialUrl())) {
|
||||||
const { initialPathname, initialParams } = getState().wallet;
|
// if initial redirection fails try to switch to first available device
|
||||||
|
dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
if (initialPathname) {
|
|
||||||
dispatch({
|
|
||||||
type: WALLET.SET_INITIAL_URL,
|
|
||||||
// pathname: null,
|
|
||||||
// params: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (devices.length > 0) {
|
|
||||||
const unacquired: ?TrezorDevice = devices.find(d => d.features);
|
|
||||||
if (unacquired) {
|
|
||||||
dispatch(onSelectDevice(unacquired));
|
|
||||||
} else {
|
|
||||||
const latest: Array<TrezorDevice> = sortDevices(devices);
|
|
||||||
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
|
|
||||||
dispatch(onSelectDevice(firstConnected || latest[0]));
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
if (initialParams) {
|
|
||||||
if (!initialParams.hasOwnProperty('network') && initialPathname !== getState().router.location.pathname) {
|
|
||||||
// dispatch( push(initialPathname) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const switchToFirstAvailableDevice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
|
||||||
const { devices } = getState();
|
|
||||||
if (devices.length > 0) {
|
|
||||||
// TODO: Priority:
|
|
||||||
// 1. First Unacquired
|
|
||||||
// 2. First connected
|
|
||||||
// 3. Saved with latest timestamp
|
|
||||||
const unacquired = devices.find(d => !d.features);
|
|
||||||
if (unacquired) {
|
|
||||||
dispatch(initConnectedDevice(unacquired));
|
|
||||||
} else {
|
|
||||||
const latest: Array<TrezorDevice> = sortDevices(devices);
|
|
||||||
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
|
|
||||||
dispatch(onSelectDevice(firstConnected || latest[0]));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatch(push('/'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const selected = getState().wallet.selectedDevice;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (selected
|
if (selected
|
||||||
@ -345,7 +234,7 @@ export const coinChanged = (network: ?string): ThunkAction => (dispatch: Dispatc
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function reload(): AsyncAction {
|
export function reload(): AsyncAction {
|
||||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
return async (): Promise<void> => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,13 +280,6 @@ export function acquire(): AsyncAction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
|
|
||||||
if (device.features) {
|
|
||||||
const devUrl: string = `${device.features.device_id}${device.instance ? `:${device.instance}` : ''}`;
|
|
||||||
dispatch(push(`/device/${devUrl}/settings`));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// called from Aside - device menu (forget single instance)
|
// called from Aside - device menu (forget single instance)
|
||||||
export const forget = (device: TrezorDevice): Action => ({
|
export const forget = (device: TrezorDevice): Action => ({
|
||||||
type: CONNECT.FORGET_REQUEST,
|
type: CONNECT.FORGET_REQUEST,
|
||||||
|
@ -6,9 +6,9 @@ import type {
|
|||||||
MiddlewareAPI as ReduxMiddlewareAPI,
|
MiddlewareAPI as ReduxMiddlewareAPI,
|
||||||
Middleware as ReduxMiddleware,
|
Middleware as ReduxMiddleware,
|
||||||
ThunkAction as ReduxThunkAction,
|
ThunkAction as ReduxThunkAction,
|
||||||
|
PayloadAction as ReduxPayloadAction,
|
||||||
AsyncAction as ReduxAsyncAction,
|
AsyncAction as ReduxAsyncAction,
|
||||||
PromiseAction as ReduxPromiseAction,
|
PromiseAction as ReduxPromiseAction,
|
||||||
ThunkDispatch as ReduxThunkDispatch,
|
|
||||||
PlainDispatch as ReduxPlainDispatch,
|
PlainDispatch as ReduxPlainDispatch,
|
||||||
} from 'redux';
|
} from 'redux';
|
||||||
|
|
||||||
@ -164,6 +164,7 @@ export type MiddlewareAPI = ReduxMiddlewareAPI<State, Action>;
|
|||||||
export type Middleware = ReduxMiddleware<State, Action>;
|
export type Middleware = ReduxMiddleware<State, Action>;
|
||||||
|
|
||||||
export type ThunkAction = ReduxThunkAction<State, Action>;
|
export type ThunkAction = ReduxThunkAction<State, Action>;
|
||||||
|
export type PayloadAction<R> = ReduxPayloadAction<State, Action, R>;
|
||||||
export type AsyncAction = ReduxAsyncAction<State, Action>;
|
export type AsyncAction = ReduxAsyncAction<State, Action>;
|
||||||
export type PromiseAction<R> = ReduxPromiseAction<State, Action, R>;
|
export type PromiseAction<R> = ReduxPromiseAction<State, Action, R>;
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
declare module 'redux' {
|
declare module 'redux' {
|
||||||
/*
|
/*
|
||||||
|
|
||||||
S = State
|
S = State
|
||||||
A = Action
|
A = Action
|
||||||
D = Dispatch
|
D = Dispatch
|
||||||
@ -16,28 +15,25 @@ declare module 'redux' {
|
|||||||
declare export type ThunkAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => void;
|
declare export type ThunkAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => void;
|
||||||
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
||||||
declare export type PromiseAction<S, A, R> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<R>;
|
declare export type PromiseAction<S, A, R> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<R>;
|
||||||
|
declare export type PayloadAction<S, A, R> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => R;
|
||||||
|
|
||||||
declare export type ThunkDispatch<S, A> = (action: ThunkAction<S, A>) => void;
|
declare export type ThunkDispatch<S, A> = (action: ThunkAction<S, A>) => void;
|
||||||
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
||||||
declare export type PromiseDispatch<S, A> = <R>(action: PromiseAction<S, A, R>) => Promise<R>;
|
declare export type PromiseDispatch<S, A> = <R>(action: PromiseAction<S, A, R>) => Promise<R>;
|
||||||
|
declare export type PayloadDispatch<S, A> = <R>(action: PayloadAction<S, A, R>) => R;
|
||||||
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
|
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
|
||||||
/* NEW: Dispatch is now a combination of these different dispatch types */
|
/* NEW: Dispatch is now a combination of these different dispatch types */
|
||||||
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A>;
|
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A> & PayloadDispatch<S, A>;
|
||||||
|
|
||||||
declare export type MiddlewareAPI<S, A> = {
|
declare export type MiddlewareAPI<S, A> = {
|
||||||
// dispatch: Dispatch<S, A>;
|
|
||||||
// dispatch: (action: A | ThunkAction<S, A>) => A | void;
|
|
||||||
// dispatch: PlainDispatch<A> | () => A;
|
|
||||||
// dispatch: ( A | ThunkAction<S, A> | void ) => void;
|
|
||||||
dispatch: ReduxDispatch<S, A>;
|
dispatch: ReduxDispatch<S, A>;
|
||||||
// dispatch: Dispatch<S, A>;
|
|
||||||
// dispatch: D;
|
|
||||||
getState(): S;
|
getState(): S;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare export type Middleware<S, A> =
|
declare export type Middleware<S, A> =
|
||||||
(api: MiddlewareAPI<S, A>) =>
|
(api: MiddlewareAPI<S, A>) =>
|
||||||
(next: PlainDispatch<A>) => PlainDispatch<A>;
|
(next: PlainDispatch<A>) =>
|
||||||
|
(PlainDispatch<A> | (action: A) => Promise<A>);
|
||||||
|
|
||||||
declare export type Store<S, A, D = ReduxDispatch<S, A>> = {
|
declare export type Store<S, A, D = ReduxDispatch<S, A>> = {
|
||||||
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
|
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
|
||||||
|
@ -66,7 +66,6 @@ export type CustomBackend = {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
error: ?string;
|
error: ?string;
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import * as ACCOUNT from 'actions/constants/account';
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -14,7 +12,6 @@ import type {
|
|||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
location?: string;
|
location?: string;
|
||||||
|
|
||||||
account: ?Account;
|
account: ?Account;
|
||||||
network: ?Coin;
|
network: ?Coin;
|
||||||
tokens: Array<Token>,
|
tokens: Array<Token>,
|
||||||
|
@ -12,17 +12,18 @@ import type { Action, RouterLocationState, TrezorDevice } from 'flowtype';
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
|
unloading: boolean;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
dropdownOpened: boolean;
|
dropdownOpened: boolean;
|
||||||
initialParams: ?RouterLocationState;
|
initialParams: ?RouterLocationState;
|
||||||
initialPathname: ?string;
|
initialPathname: ?string;
|
||||||
disconnectRequest: ?TrezorDevice;
|
disconnectRequest: ?TrezorDevice;
|
||||||
|
|
||||||
selectedDevice: ?TrezorDevice;
|
selectedDevice: ?TrezorDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
ready: false,
|
ready: false,
|
||||||
|
unloading: false,
|
||||||
online: navigator.onLine,
|
online: navigator.onLine,
|
||||||
dropdownOpened: false,
|
dropdownOpened: false,
|
||||||
initialParams: null,
|
initialParams: null,
|
||||||
@ -33,6 +34,12 @@ const initialState: State = {
|
|||||||
|
|
||||||
export default function wallet(state: State = initialState, action: Action): State {
|
export default function wallet(state: State = initialState, action: Action): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case WALLET.ON_BEFORE_UNLOAD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
unloading: true,
|
||||||
|
};
|
||||||
|
|
||||||
case WALLET.SET_INITIAL_URL:
|
case WALLET.SET_INITIAL_URL:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -1,156 +1,46 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import { LOCATION_CHANGE/* , replace */ } from 'react-router-redux';
|
import { LOCATION_CHANGE, replace } from 'react-router-redux';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
|
||||||
import * as NotificationActions from 'actions/NotificationActions';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Middleware,
|
Middleware,
|
||||||
MiddlewareAPI,
|
MiddlewareAPI,
|
||||||
MiddlewareDispatch,
|
MiddlewareDispatch,
|
||||||
Action,
|
Action,
|
||||||
RouterLocationState,
|
|
||||||
TrezorDevice,
|
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware used for init application and managing router path.
|
* Redux Middleware used for managing router path
|
||||||
|
* This middleware couldn't use async/await because LOCATION_CHANGE action is also synchronized with RouterReducer (react-router-redux)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const pathToParams = (path: string): RouterLocationState => {
|
|
||||||
const urlParts: Array<string> = path.split('/').slice(1);
|
|
||||||
const params: RouterLocationState = {};
|
|
||||||
if (urlParts.length < 1 || path === '/') return params;
|
|
||||||
|
|
||||||
for (let i = 0, len = urlParts.length; i < len; i += 2) {
|
|
||||||
params[urlParts[i]] = urlParts[i + 1] || urlParts[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.hasOwnProperty('device')) {
|
|
||||||
const isClonedDevice: Array<string> = params.device.split(':');
|
|
||||||
if (isClonedDevice.length > 1) {
|
|
||||||
params.device = isClonedDevice[0];
|
|
||||||
params.deviceInstance = isClonedDevice[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validation = (api: MiddlewareAPI, params: RouterLocationState): boolean => {
|
|
||||||
if (params.hasOwnProperty('device')) {
|
|
||||||
const { devices } = api.getState();
|
|
||||||
|
|
||||||
let device: ?TrezorDevice;
|
|
||||||
if (params.hasOwnProperty('deviceInstance')) {
|
|
||||||
device = devices.find(d => d.features && d.features.device_id === params.device && d.instance === parseInt(params.deviceInstance, 10));
|
|
||||||
} else {
|
|
||||||
device = devices.find(d => d.path === params.device || (d.features && d.features.device_id === params.device));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!device) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.hasOwnProperty('network')) {
|
|
||||||
const { config } = api.getState().localStorage;
|
|
||||||
const coin = config.coins.find(c => c.network === params.network);
|
|
||||||
if (!coin) return false;
|
|
||||||
if (!params.account) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (params.account) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
let __unloading: boolean = false;
|
|
||||||
|
|
||||||
const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||||
if (action.type === WALLET.ON_BEFORE_UNLOAD) {
|
// make sure that middleware should process this action
|
||||||
__unloading = true;
|
if (action.type !== LOCATION_CHANGE || api.getState().wallet.unloading) {
|
||||||
} else if (action.type === LOCATION_CHANGE && !__unloading) {
|
// pass action
|
||||||
const { location } = api.getState().router;
|
|
||||||
const { devices } = api.getState();
|
|
||||||
const { error } = api.getState().connect;
|
|
||||||
|
|
||||||
const requestedParams: RouterLocationState = pathToParams(action.payload.pathname);
|
|
||||||
const currentParams: RouterLocationState = pathToParams(location ? location.pathname : '/');
|
|
||||||
const postActions: Array<Action> = [];
|
|
||||||
|
|
||||||
let redirectPath: ?string;
|
|
||||||
// first event after application loads
|
|
||||||
if (!location) {
|
|
||||||
postActions.push({
|
|
||||||
type: WALLET.SET_INITIAL_URL,
|
|
||||||
pathname: action.payload.pathname,
|
|
||||||
state: requestedParams,
|
|
||||||
});
|
|
||||||
|
|
||||||
redirectPath = '/';
|
|
||||||
} else {
|
|
||||||
const isModalOpened: boolean = api.getState().modal.opened;
|
|
||||||
// there are no devices attached or initialization error occurs
|
|
||||||
const landingPage: boolean = devices.length < 1 || error !== null;
|
|
||||||
|
|
||||||
// modal is still opened and currentPath is still valid
|
|
||||||
// example 1 (valid blocking): url changes while passphrase modal opened but device is still connected (we want user to finish this action)
|
|
||||||
// example 2 (invalid blocking): url changes while passphrase modal opened because device disconnect
|
|
||||||
if (isModalOpened && action.payload.pathname !== location.pathname && validation(api, currentParams)) {
|
|
||||||
redirectPath = location.pathname;
|
|
||||||
console.warn('Modal still opened');
|
|
||||||
} else if (landingPage) {
|
|
||||||
// keep route on landing page
|
|
||||||
if (action.payload.pathname !== '/' && action.payload.pathname !== '/bridge') {
|
|
||||||
redirectPath = '/';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// PATH VALIDATION
|
|
||||||
// redirect from root view
|
|
||||||
if (action.payload.pathname === '/' || !validation(api, requestedParams)) {
|
|
||||||
// TODO redirect to first device
|
|
||||||
redirectPath = '/device/x';
|
|
||||||
} else if (requestedParams.device) {
|
|
||||||
if (requestedParams.network !== currentParams.network) {
|
|
||||||
postActions.push({
|
|
||||||
type: CONNECT.COIN_CHANGED,
|
|
||||||
payload: {
|
|
||||||
network: requestedParams.network,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirectPath) {
|
|
||||||
console.warn('Redirecting...', redirectPath);
|
|
||||||
// override action to keep routerReducer sync
|
|
||||||
const url: string = redirectPath;
|
|
||||||
action.payload.state = pathToParams(url);
|
|
||||||
action.payload.pathname = url;
|
|
||||||
// change url
|
|
||||||
// api.dispatch(replace(url));
|
|
||||||
} else {
|
|
||||||
action.payload.state = requestedParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve LOCATION_CHANGE action
|
|
||||||
next(action);
|
|
||||||
|
|
||||||
// resolve post actions
|
|
||||||
postActions.forEach((a) => {
|
|
||||||
api.dispatch(a);
|
|
||||||
});
|
|
||||||
|
|
||||||
api.dispatch(NotificationActions.clear(currentParams, requestedParams));
|
|
||||||
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass all actions through by default
|
|
||||||
return next(action);
|
return next(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compose valid url
|
||||||
|
const validUrl = api.dispatch(RouterActions.getValidUrl(action));
|
||||||
|
// override action state (to be stored in RouterReducer)
|
||||||
|
const override = action;
|
||||||
|
override.payload.state = api.dispatch(RouterActions.pathToParams(validUrl));
|
||||||
|
const redirect = action.payload.pathname !== validUrl;
|
||||||
|
if (redirect) {
|
||||||
|
// override action pathname
|
||||||
|
override.payload.pathname = validUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass action
|
||||||
|
next(override);
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
// replace invalid url
|
||||||
|
api.dispatch(replace(validUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return override;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RouterService;
|
export default RouterService;
|
@ -1,12 +1,12 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import { push } from 'react-router-redux';
|
|
||||||
|
|
||||||
import TrezorConnect, {
|
import {
|
||||||
TRANSPORT, DEVICE_EVENT, UI_EVENT, UI, DEVICE, BLOCKCHAIN
|
TRANSPORT, DEVICE, BLOCKCHAIN,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
import * as DiscoveryActions from 'actions/DiscoveryActions';
|
import * as DiscoveryActions from 'actions/DiscoveryActions';
|
||||||
import * as BlockchainActions from 'actions/BlockchainActions';
|
import * as BlockchainActions from 'actions/BlockchainActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import * as ModalActions from 'actions/ModalActions';
|
import * as ModalActions from 'actions/ModalActions';
|
||||||
import * as STORAGE from 'actions/constants/localStorage';
|
import * as STORAGE from 'actions/constants/localStorage';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
@ -31,7 +31,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
api.dispatch(TrezorConnectActions.init());
|
api.dispatch(TrezorConnectActions.init());
|
||||||
} else if (action.type === TRANSPORT.ERROR) {
|
} else if (action.type === TRANSPORT.ERROR) {
|
||||||
// TODO: check if modal is open
|
// TODO: check if modal is open
|
||||||
// api.dispatch( push('/') );
|
// api.dispatch( RouterActions.gotoLandingPage() );
|
||||||
} else if (action.type === TRANSPORT.START) {
|
} else if (action.type === TRANSPORT.START) {
|
||||||
api.dispatch(BlockchainActions.init());
|
api.dispatch(BlockchainActions.init());
|
||||||
} else if (action.type === BLOCKCHAIN_READY) {
|
} else if (action.type === BLOCKCHAIN_READY) {
|
||||||
@ -42,7 +42,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
api.dispatch(ModalActions.onRememberRequest(prevModalState));
|
api.dispatch(ModalActions.onRememberRequest(prevModalState));
|
||||||
} else if (action.type === CONNECT.FORGET) {
|
} else if (action.type === CONNECT.FORGET) {
|
||||||
//api.dispatch( TrezorConnectActions.forgetDevice(action.device) );
|
//api.dispatch( TrezorConnectActions.forgetDevice(action.device) );
|
||||||
api.dispatch(TrezorConnectActions.switchToFirstAvailableDevice());
|
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
} else if (action.type === CONNECT.FORGET_SINGLE) {
|
} else if (action.type === CONNECT.FORGET_SINGLE) {
|
||||||
if (api.getState().devices.length < 1 && action.device.connected) {
|
if (api.getState().devices.length < 1 && action.device.connected) {
|
||||||
// prompt disconnect device info in LandingPage
|
// prompt disconnect device info in LandingPage
|
||||||
@ -50,9 +50,9 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
type: CONNECT.DISCONNECT_REQUEST,
|
type: CONNECT.DISCONNECT_REQUEST,
|
||||||
device: action.device,
|
device: action.device,
|
||||||
});
|
});
|
||||||
api.dispatch(push('/'));
|
api.dispatch(RouterActions.gotoLandingPage());
|
||||||
} else {
|
} else {
|
||||||
api.dispatch(TrezorConnectActions.switchToFirstAvailableDevice());
|
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
}
|
}
|
||||||
} else if (action.type === DEVICE.CONNECT || action.type === DEVICE.CONNECT_UNACQUIRED) {
|
} else if (action.type === DEVICE.CONNECT || action.type === DEVICE.CONNECT_UNACQUIRED) {
|
||||||
api.dispatch(DiscoveryActions.restore());
|
api.dispatch(DiscoveryActions.restore());
|
||||||
@ -60,7 +60,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
} else if (action.type === CONNECT.AUTH_DEVICE) {
|
} else if (action.type === CONNECT.AUTH_DEVICE) {
|
||||||
api.dispatch(DiscoveryActions.check());
|
api.dispatch(DiscoveryActions.check());
|
||||||
} else if (action.type === CONNECT.DUPLICATE) {
|
} else if (action.type === CONNECT.DUPLICATE) {
|
||||||
api.dispatch(TrezorConnectActions.onSelectDevice(action.device));
|
api.dispatch(RouterActions.selectDevice(action.device));
|
||||||
} else if (action.type === CONNECT.COIN_CHANGED) {
|
} else if (action.type === CONNECT.COIN_CHANGED) {
|
||||||
api.dispatch(TrezorConnectActions.coinChanged(action.payload.network));
|
api.dispatch(TrezorConnectActions.coinChanged(action.payload.network));
|
||||||
} else if (action.type === BLOCKCHAIN.BLOCK) {
|
} else if (action.type === BLOCKCHAIN.BLOCK) {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import { DEVICE } from 'trezor-connect';
|
import { DEVICE } from 'trezor-connect';
|
||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
|
|
||||||
import * as WalletActions from 'actions/WalletActions';
|
import * as WalletActions from 'actions/WalletActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
|
import * as NotificationActions from 'actions/NotificationActions';
|
||||||
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
import * as SelectedAccountActions from 'actions/SelectedAccountActions';
|
import * as SelectedAccountActions from 'actions/SelectedAccountActions';
|
||||||
@ -22,24 +23,74 @@ import type {
|
|||||||
*/
|
*/
|
||||||
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||||
const prevState = api.getState();
|
const prevState = api.getState();
|
||||||
const locationChange: boolean = action.type === LOCATION_CHANGE;
|
|
||||||
|
|
||||||
// Application live cycle starts here
|
// Application live cycle starts HERE!
|
||||||
if (locationChange) {
|
// when first LOCATION_CHANGE is called router does not have "location" set yet
|
||||||
const { location } = api.getState().router;
|
if (action.type === LOCATION_CHANGE && !prevState.router.location) {
|
||||||
if (!location) {
|
// initialize wallet
|
||||||
api.dispatch(WalletActions.init());
|
api.dispatch(WalletActions.init());
|
||||||
// load data from config.json and local storage
|
// set initial url
|
||||||
api.dispatch(LocalStorageActions.loadData());
|
// TODO: validate if initial url is potentially correct
|
||||||
}
|
api.dispatch({
|
||||||
|
type: WALLET.SET_INITIAL_URL,
|
||||||
|
pathname: action.payload.pathname,
|
||||||
|
state: {},
|
||||||
|
});
|
||||||
|
// pass action and break process
|
||||||
|
return next(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass action
|
// pass action
|
||||||
next(action);
|
next(action);
|
||||||
|
|
||||||
if (action.type === DEVICE.CONNECT) {
|
switch (action.type) {
|
||||||
api.dispatch(WalletActions.clearUnavailableDevicesData(prevState, action.device));
|
case WALLET.SET_INITIAL_URL:
|
||||||
|
api.dispatch(LocalStorageActions.loadData());
|
||||||
|
break;
|
||||||
|
case WALLET.SET_SELECTED_DEVICE:
|
||||||
|
if (action.device) {
|
||||||
|
// try to authorize device
|
||||||
|
api.dispatch(TrezorConnectActions.getSelectedDeviceState());
|
||||||
|
} else {
|
||||||
|
// try select different device
|
||||||
|
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case DEVICE.CONNECT:
|
||||||
|
api.dispatch(WalletActions.clearUnavailableDevicesData(prevState, action.device));
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update common values ONLY if application is ready
|
||||||
|
if (!api.getState().wallet.ready) return action;
|
||||||
|
|
||||||
|
// double verification needed
|
||||||
|
// Corner case: LOCATION_CHANGE was called but pathname didn't changed (redirection from RouterService)
|
||||||
|
const prevLocation = prevState.router.location;
|
||||||
|
const currentLocation = api.getState().router.location;
|
||||||
|
if (action.type === LOCATION_CHANGE && prevLocation.pathname !== currentLocation.pathname) {
|
||||||
|
// watch for coin change
|
||||||
|
if (prevLocation.state.network !== currentLocation.state.network) {
|
||||||
|
api.dispatch({
|
||||||
|
type: CONNECT.COIN_CHANGED,
|
||||||
|
payload: {
|
||||||
|
network: currentLocation.state.network,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch for account change
|
||||||
|
if (prevLocation.state.network !== currentLocation.state.network || prevLocation.state.account !== currentLocation.state.account) {
|
||||||
|
api.dispatch(SelectedAccountActions.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear notifications
|
||||||
|
api.dispatch(NotificationActions.clear(prevLocation.state, currentLocation.state));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// update common values in WallerReducer
|
// update common values in WallerReducer
|
||||||
api.dispatch(WalletActions.updateSelectedValues(prevState, action));
|
api.dispatch(WalletActions.updateSelectedValues(prevState, action));
|
||||||
@ -47,15 +98,6 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
|||||||
// update common values in SelectedAccountReducer
|
// update common values in SelectedAccountReducer
|
||||||
api.dispatch(SelectedAccountActions.updateSelectedValues(prevState, action));
|
api.dispatch(SelectedAccountActions.updateSelectedValues(prevState, action));
|
||||||
|
|
||||||
// selected device changed
|
|
||||||
if (action.type === WALLET.SET_SELECTED_DEVICE) {
|
|
||||||
if (action.device) {
|
|
||||||
api.dispatch(TrezorConnectActions.getSelectedDeviceState());
|
|
||||||
} else {
|
|
||||||
api.dispatch(TrezorConnectActions.switchToFirstAvailableDevice());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
94
src/support/routes.js
Normal file
94
src/support/routes.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
export type Route = {
|
||||||
|
+name: string;
|
||||||
|
+pattern: string;
|
||||||
|
fields: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routes: Array<Route> = [
|
||||||
|
{
|
||||||
|
name: 'landing-home',
|
||||||
|
pattern: '/',
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landing-bridge',
|
||||||
|
pattern: '/bridge',
|
||||||
|
fields: ['bridge'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landing-import',
|
||||||
|
pattern: '/import',
|
||||||
|
fields: ['import'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-setting',
|
||||||
|
pattern: '/settings',
|
||||||
|
fields: ['settings'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-acquire',
|
||||||
|
pattern: '/device/:device/acquire',
|
||||||
|
fields: ['device', 'acquire'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-unreadable',
|
||||||
|
pattern: '/device/:device/unreadable',
|
||||||
|
fields: ['device', 'unreadable'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-bootloader',
|
||||||
|
pattern: '/device/:device/bootloader',
|
||||||
|
fields: ['device', 'bootloader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-initialize',
|
||||||
|
pattern: '/device/:device/initialize',
|
||||||
|
fields: ['device', 'initialize'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-device-settings',
|
||||||
|
pattern: '/device/:device/settings',
|
||||||
|
fields: ['device', 'settings'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-dashboard',
|
||||||
|
pattern: '/device/:device',
|
||||||
|
fields: ['device'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-summary',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account',
|
||||||
|
fields: ['device', 'network', 'account'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-send',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/send',
|
||||||
|
fields: ['device', 'network', 'account', 'send'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-send-override',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/send/override',
|
||||||
|
fields: ['device', 'network', 'account', 'send'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-receive',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/receive',
|
||||||
|
fields: ['device', 'network', 'account', 'receive'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-signverify',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/signverify',
|
||||||
|
fields: ['device', 'network', 'account', 'signverify'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getPattern = (name: string): string => {
|
||||||
|
const entry = routes.find(r => r.name === name);
|
||||||
|
if (!entry) {
|
||||||
|
console.error(`Route for ${name} not found`);
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
return entry.pattern;
|
||||||
|
};
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
|
|
||||||
@ -36,8 +37,8 @@ const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps>
|
|||||||
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
|
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
|
||||||
forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch),
|
forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch),
|
||||||
duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch),
|
duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch),
|
||||||
gotoDeviceSettings: bindActionCreators(TrezorConnectActions.gotoDeviceSettings, dispatch),
|
gotoDeviceSettings: bindActionCreators(RouterActions.gotoDeviceSettings, dispatch),
|
||||||
onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch),
|
onSelectDevice: bindActionCreators(RouterActions.selectDevice, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withRouter(
|
export default withRouter(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import { toggleDeviceDropdown } from 'actions/WalletActions';
|
import { toggleDeviceDropdown } from 'actions/WalletActions';
|
||||||
import type { State } from 'flowtype';
|
import type { State } from 'flowtype';
|
||||||
|
|
||||||
@ -22,8 +23,8 @@ export type DispatchProps = {
|
|||||||
acquireDevice: typeof TrezorConnectActions.acquire,
|
acquireDevice: typeof TrezorConnectActions.acquire,
|
||||||
forgetDevice: typeof TrezorConnectActions.forget,
|
forgetDevice: typeof TrezorConnectActions.forget,
|
||||||
duplicateDevice: typeof TrezorConnectActions.duplicateDevice,
|
duplicateDevice: typeof TrezorConnectActions.duplicateDevice,
|
||||||
gotoDeviceSettings: typeof TrezorConnectActions.gotoDeviceSettings,
|
gotoDeviceSettings: typeof RouterActions.gotoDeviceSettings,
|
||||||
onSelectDevice: typeof TrezorConnectActions.onSelectDevice,
|
onSelectDevice: typeof RouterActions.selectDevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = StateProps & DispatchProps;
|
export type Props = StateProps & DispatchProps;
|
@ -30,10 +30,11 @@ const SelectedAccount = (props: Props) => {
|
|||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
discovery,
|
discovery,
|
||||||
network
|
network,
|
||||||
} = accountState;
|
} = accountState;
|
||||||
|
|
||||||
if (!network) return; // TODO: this shouldn't happen. change accountState reducer?
|
// corner case: accountState didn't finish loading state after LOCATION_CHANGE action
|
||||||
|
if (!network) return (<Notification type="info" title="Loading account state..." />);
|
||||||
|
|
||||||
const blockchain = props.blockchain.find(b => b.name === network.network);
|
const blockchain = props.blockchain.find(b => b.name === network.network);
|
||||||
if (blockchain && !blockchain.connected) {
|
if (blockchain && !blockchain.connected) {
|
||||||
@ -43,12 +44,13 @@ const SelectedAccount = (props: Props) => {
|
|||||||
title="Backend not connected"
|
title="Backend not connected"
|
||||||
actions={
|
actions={
|
||||||
[{
|
[{
|
||||||
label: "Try again",
|
label: 'Try again',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await props.blockchainReconnect(network.network);
|
await props.blockchainReconnect(network.network);
|
||||||
}
|
},
|
||||||
}]
|
}]
|
||||||
} />
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import { connect } from 'react-redux';
|
|||||||
|
|
||||||
import { reconnect } from 'actions/DiscoveryActions';
|
import { reconnect } from 'actions/DiscoveryActions';
|
||||||
import SendFormActions from 'actions/SendFormActions';
|
import SendFormActions from 'actions/SendFormActions';
|
||||||
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from 'views/Wallet/components/SelectedAccount';
|
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from 'views/Wallet/components/SelectedAccount';
|
||||||
@ -24,7 +23,6 @@ export type StateProps = BaseStateProps & {
|
|||||||
|
|
||||||
export type DispatchProps = BaseDispatchProps & {
|
export type DispatchProps = BaseDispatchProps & {
|
||||||
sendFormActions: typeof SendFormActions,
|
sendFormActions: typeof SendFormActions,
|
||||||
saveSessionStorage: typeof SessionStorageActions.save
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
||||||
@ -43,7 +41,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||||
blockchainReconnect: bindActionCreators(reconnect, dispatch),
|
blockchainReconnect: bindActionCreators(reconnect, dispatch),
|
||||||
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
||||||
saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(AccountSend);
|
export default connect(mapStateToProps, mapDispatchToProps)(AccountSend);
|
@ -199,8 +199,6 @@ class AccountSend extends Component<Props, State> {
|
|||||||
componentWillReceiveProps(newProps: Props) {
|
componentWillReceiveProps(newProps: Props) {
|
||||||
calculate(this.props, newProps);
|
calculate(this.props, newProps);
|
||||||
validation(newProps);
|
validation(newProps);
|
||||||
|
|
||||||
this.props.saveSessionStorage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddressInputState(address: string, addressErrors: string, addressWarnings: string) {
|
getAddressInputState(address: string, addressErrors: string, addressWarnings: string) {
|
||||||
|
@ -6,6 +6,7 @@ import { ConnectedRouter } from 'react-router-redux';
|
|||||||
|
|
||||||
// general
|
// general
|
||||||
import ErrorBoundary from 'support/ErrorBoundary';
|
import ErrorBoundary from 'support/ErrorBoundary';
|
||||||
|
import { getPattern } from 'support/routes';
|
||||||
import LandingContainer from 'views/Landing/Container';
|
import LandingContainer from 'views/Landing/Container';
|
||||||
|
|
||||||
// wallet views
|
// wallet views
|
||||||
@ -29,25 +30,24 @@ const App = () => (
|
|||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={LandingContainer} />
|
<Route exact path={getPattern('landing-home')} component={LandingContainer} />
|
||||||
<Route exact path="/bridge" component={LandingContainer} />
|
<Route exact path={getPattern('landing-bridge')} component={LandingContainer} />
|
||||||
<Route exact path="/import" component={LandingContainer} />
|
<Route exact path={getPattern('landing-import')} component={LandingContainer} />
|
||||||
<Route>
|
<Route>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<WalletContainer>
|
<WalletContainer>
|
||||||
<Route exact path="/settings" component={WalletSettings} />
|
<Route exact path={getPattern('wallet-setting')} component={WalletSettings} />
|
||||||
<Route exact path="/device/:device/" component={WalletDashboard} />
|
<Route exact path={getPattern('wallet-dashboard')} component={WalletDashboard} />
|
||||||
<Route exact path="/device/:device/network/:network" component={WalletDashboard} />
|
<Route exact path={getPattern('wallet-acquire')} component={WalletAcquire} />
|
||||||
<Route exact path="/device/:device/acquire" component={WalletAcquire} />
|
<Route exact path={getPattern('wallet-unreadable')} component={WalletUnreadableDevice} />
|
||||||
<Route exact path="/device/:device/unreadable" component={WalletUnreadableDevice} />
|
<Route exact path={getPattern('wallet-bootloader')} component={WalletBootloader} />
|
||||||
<Route exact path="/device/:device/bootloader" component={WalletBootloader} />
|
<Route exact path={getPattern('wallet-initialize')} component={WalletInitialize} />
|
||||||
<Route exact path="/device/:device/initialize" component={WalletInitialize} />
|
<Route exact path={getPattern('wallet-device-settings')} component={WalletDeviceSettings} />
|
||||||
<Route exact path="/device/:device/settings" component={WalletDeviceSettings} />
|
<Route exact path={getPattern('wallet-account-summary')} component={AccountSummary} />
|
||||||
<Route exact path="/device/:device/network/:network/account/:account" component={AccountSummary} />
|
<Route path={getPattern('wallet-account-send')} component={AccountSend} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/send" component={AccountSend} />
|
<Route path={getPattern('wallet-account-send-override')} component={AccountSend} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/send/override" component={AccountSend} />
|
<Route path={getPattern('wallet-account-receive')} component={AccountReceive} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/receive" component={AccountReceive} />
|
<Route path={getPattern('wallet-account-signverify')} component={AccountSignVerify} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/signverify" component={AccountSignVerify} />
|
|
||||||
</WalletContainer>
|
</WalletContainer>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Route>
|
</Route>
|
||||||
|
Loading…
Reference in New Issue
Block a user