mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-28 03:08:30 +00:00
add RouterActions
This commit is contained in:
parent
c5b6a40eff
commit
46a07b756c
193
src/actions/RouterActions.js
Normal file
193
src/actions/RouterActions.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { push } from 'react-router-redux';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
RouterLocationState,
|
||||||
|
Device,
|
||||||
|
TrezorDevice,
|
||||||
|
ThunkAction,
|
||||||
|
PayloadAction,
|
||||||
|
Dispatch,
|
||||||
|
GetState,
|
||||||
|
} from 'flowtype';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse url string to RouterLocationState object (key/value)
|
||||||
|
*/
|
||||||
|
export const pathToParams = (path: string): PayloadAction<RouterLocationState> => (dispatch: Dispatch, getState: GetState): 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) {
|
||||||
|
params.device = isClonedDevice[0];
|
||||||
|
params.deviceInstance = isClonedDevice[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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: check if discovery on this network is completed
|
||||||
|
// if (params.hasOwnProperty('account')) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 requested device
|
||||||
|
*/
|
||||||
|
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 (from RouterReducer) url is landing page
|
||||||
|
*/
|
||||||
|
export const isLandingPageUrl = (url?: string): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
|
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, getState: GetState): void => {
|
||||||
|
// check if current url is not valid landing page url
|
||||||
|
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 => {
|
||||||
|
// TODO
|
||||||
|
return false;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user