1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-24 09:18:09 +00:00

add RouterActions

This commit is contained in:
Szymon Lesisz 2018-09-20 18:24:28 +02:00
parent c5b6a40eff
commit 46a07b756c

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