lint all files

pull/420/head
Vladimir Volek 5 years ago
parent b13cbb1365
commit 749224dc66

@ -3,10 +3,10 @@
"eslint-config-airbnb",
"prettier",
"prettier/babel",
"plugin:flowtype/recommended",
"prettier/flowtype",
"prettier/react",
"plugin:jest/recommended",
"plugin:flowtype/recommended",
"plugin:jest/recommended"
],
"globals": {

@ -21,7 +21,8 @@
"lint": "run-s lint:*",
"lint:js": "npx eslint ./src ./webpack",
"lint:css": "npx stylelint './src/**/*.js'",
"lint:prettier": "pretty-quick",
"lint:prettier": "npx pretty-quick",
"lint-fix": "npx eslint ./src ./webpack --fix",
"prettier:check": "eslint --print-config ./src | eslint-config-prettier-check",
"test": "run-s test:*",
"test:unit": "npx jest",
@ -142,4 +143,4 @@
"optionalDependencies": {
"fsevents": "1.2.7"
}
}
}

@ -4,13 +4,15 @@ import * as ACCOUNT from 'actions/constants/account';
import type { Action } from 'flowtype';
import type { Account, State } from 'reducers/AccountsReducer';
export type AccountAction = {
type: typeof ACCOUNT.FROM_STORAGE,
payload: State
} | {
type: typeof ACCOUNT.CREATE | typeof ACCOUNT.UPDATE,
payload: Account,
};
export type AccountAction =
| {
type: typeof ACCOUNT.FROM_STORAGE,
payload: State,
}
| {
type: typeof ACCOUNT.CREATE | typeof ACCOUNT.UPDATE,
payload: Account,
};
export const update = (account: Account): Action => ({
type: ACCOUNT.UPDATE,

@ -4,35 +4,35 @@ import * as BLOCKCHAIN from 'actions/constants/blockchain';
import * as EthereumBlockchainActions from 'actions/ethereum/BlockchainActions';
import * as RippleBlockchainActions from 'actions/ripple/BlockchainActions';
import type {
Dispatch,
GetState,
PromiseAction,
BlockchainFeeLevel,
} from 'flowtype';
import type { Dispatch, GetState, PromiseAction, BlockchainFeeLevel } from 'flowtype';
import type { BlockchainBlock, BlockchainNotification, BlockchainError } from 'trezor-connect';
export type BlockchainAction = {
type: typeof BLOCKCHAIN.READY,
} | {
type: typeof BLOCKCHAIN.UPDATE_FEE,
shortcut: string,
feeLevels: Array<BlockchainFeeLevel>,
} | {
type: typeof BLOCKCHAIN.START_SUBSCRIBE,
shortcut: string,
}
export type BlockchainAction =
| {
type: typeof BLOCKCHAIN.READY,
}
| {
type: typeof BLOCKCHAIN.UPDATE_FEE,
shortcut: string,
feeLevels: Array<BlockchainFeeLevel>,
}
| {
type: typeof BLOCKCHAIN.START_SUBSCRIBE,
shortcut: string,
};
// Conditionally subscribe to blockchain backend
// called after TrezorConnect.init successfully emits TRANSPORT.START event
// checks if there are discovery processes loaded from LocalStorage
// if so starts subscription to proper networks
export const init = (): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const init = (): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
if (getState().discovery.length > 0) {
// get unique networks
const networks: Array<string> = [];
getState().discovery.forEach((discovery) => {
getState().discovery.forEach(discovery => {
if (networks.indexOf(discovery.network) < 0) {
networks.push(discovery.network);
}
@ -50,7 +50,10 @@ export const init = (): PromiseAction<void> => async (dispatch: Dispatch, getSta
});
};
export const subscribe = (networkName: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const subscribe = (networkName: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === networkName);
if (!network) return;
@ -67,11 +70,14 @@ export const subscribe = (networkName: string): PromiseAction<void> => async (di
case 'ripple':
await dispatch(RippleBlockchainActions.subscribe(networkName));
break;
default: break;
default:
break;
}
};
export const onBlockMined = (payload: $ElementType<BlockchainBlock, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onBlockMined = (
payload: $ElementType<BlockchainBlock, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const shortcut = payload.coin.shortcut.toLowerCase();
if (getState().router.location.state.network !== shortcut) return;
@ -86,11 +92,14 @@ export const onBlockMined = (payload: $ElementType<BlockchainBlock, 'payload'>):
case 'ripple':
await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
break;
default: break;
default:
break;
}
};
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onNotification = (
payload: $ElementType<BlockchainNotification, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const shortcut = payload.coin.shortcut.toLowerCase();
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === shortcut);
@ -104,13 +113,16 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
case 'ripple':
await dispatch(RippleBlockchainActions.onNotification(payload));
break;
default: break;
default:
break;
}
};
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
// disconnect and remove Web3 websocket instance if exists
export const onError = (payload: $ElementType<BlockchainError, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onError = (
payload: $ElementType<BlockchainError, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const shortcut = payload.coin.shortcut.toLowerCase();
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === shortcut);
@ -124,6 +136,7 @@ export const onError = (payload: $ElementType<BlockchainError, 'payload'>): Prom
// this error is handled in BlockchainReducer
// await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
break;
default: break;
default:
break;
}
};
};

@ -20,63 +20,85 @@ import * as BlockchainActions from './BlockchainActions';
import * as EthereumDiscoveryActions from './ethereum/DiscoveryActions';
import * as RippleDiscoveryActions from './ripple/DiscoveryActions';
export type DiscoveryStartAction = EthereumDiscoveryActions.DiscoveryStartAction | RippleDiscoveryActions.DiscoveryStartAction;
export type DiscoveryStartAction =
| EthereumDiscoveryActions.DiscoveryStartAction
| RippleDiscoveryActions.DiscoveryStartAction;
export type DiscoveryWaitingAction = {
type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN | typeof DISCOVERY.FIRMWARE_NOT_SUPPORTED | typeof DISCOVERY.FIRMWARE_OUTDATED,
type:
| typeof DISCOVERY.WAITING_FOR_DEVICE
| typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN
| typeof DISCOVERY.FIRMWARE_NOT_SUPPORTED
| typeof DISCOVERY.FIRMWARE_OUTDATED,
device: TrezorDevice,
network: string,
}
};
export type DiscoveryCompleteAction = {
type: typeof DISCOVERY.COMPLETE,
device: TrezorDevice,
network: string,
}
export type DiscoveryAction = {
type: typeof DISCOVERY.FROM_STORAGE,
payload: State
} | {
type: typeof DISCOVERY.STOP,
device: TrezorDevice
} | DiscoveryStartAction
| DiscoveryWaitingAction
| DiscoveryCompleteAction;
};
export type DiscoveryAction =
| {
type: typeof DISCOVERY.FROM_STORAGE,
payload: State,
}
| {
type: typeof DISCOVERY.STOP,
device: TrezorDevice,
}
| DiscoveryStartAction
| DiscoveryWaitingAction
| DiscoveryCompleteAction;
// There are multiple async methods during discovery process (trezor-connect and blockchain actions)
// This method will check after each of async action if process was interrupted (for example by network change or device disconnect)
const isProcessInterrupted = (process?: Discovery): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
const isProcessInterrupted = (process?: Discovery): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
if (!process) {
return false;
}
const { deviceState, network } = process;
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === deviceState && d.network === network);
const discoveryProcess: ?Discovery = getState().discovery.find(
d => d.deviceState === deviceState && d.network === network
);
if (!discoveryProcess) return false;
return discoveryProcess.interrupted;
};
// Private action
// Called from "this.begin", "this.restore", "this.addAccount"
const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const selected = getState().wallet.selectedDevice;
if (!selected) {
// TODO: throw error
console.error('Start discovery: no selected device', device);
return;
} if (selected.path !== device.path) {
}
if (selected.path !== device.path) {
console.error('Start discovery: requested device is not selected', device, selected);
return;
} if (!selected.state) {
}
if (!selected.state) {
console.warn("Start discovery: Selected device wasn't authenticated yet...");
return;
} if (selected.connected && !selected.available) {
}
if (selected.connected && !selected.available) {
console.warn('Start discovery: Selected device is unavailable...');
return;
}
const { discovery } = getState();
const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network);
const discoveryProcess: ?Discovery = discovery.find(
d => d.deviceState === device.state && d.network === network
);
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
dispatch({
@ -105,7 +127,11 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean)
device,
network,
});
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) {
} else if (
discoveryProcess.interrupted ||
discoveryProcess.waitingForDevice ||
discoveryProcess.waitingForBlockchain
) {
// discovery cycle was interrupted
// start from beginning
dispatch(begin(device, network));
@ -117,7 +143,10 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean)
// first iteration
// generate public key for this account
// start discovery process
const begin = (device: TrezorDevice, networkName: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const begin = (device: TrezorDevice, networkName: string): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === networkName);
if (!network) return;
@ -165,7 +194,9 @@ const begin = (device: TrezorDevice, networkName: string): AsyncAction => async
// check for interruption
// corner case: DISCOVERY.START wasn't called yet, but Discovery exists in reducer created by DISCOVERY.WAITING_FOR_DEVICE action
// this is why we need to get process instance directly from reducer
const discoveryProcess = getState().discovery.find(d => d.deviceState === device.state && d.network === network);
const discoveryProcess = getState().discovery.find(
d => d.deviceState === device.state && d.network === network
);
if (dispatch(isProcessInterrupted(discoveryProcess))) return;
// send data to reducer
@ -175,7 +206,10 @@ const begin = (device: TrezorDevice, networkName: string): AsyncAction => async
dispatch(start(device, networkName));
};
const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
if (!network) return;
@ -185,13 +219,19 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
try {
switch (network.type) {
case 'ethereum':
account = await dispatch(EthereumDiscoveryActions.discoverAccount(device, discoveryProcess));
account = await dispatch(
EthereumDiscoveryActions.discoverAccount(device, discoveryProcess)
);
break;
case 'ripple':
account = await dispatch(RippleDiscoveryActions.discoverAccount(device, discoveryProcess));
account = await dispatch(
RippleDiscoveryActions.discoverAccount(device, discoveryProcess)
);
break;
default:
throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`);
throw new Error(
`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`
);
}
} catch (error) {
// handle not supported firmware error
@ -242,7 +282,11 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
if (dispatch(isProcessInterrupted(discoveryProcess))) return;
const accountIsEmpty = account.empty;
if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && accountIndex === 0)) {
if (
!accountIsEmpty ||
(accountIsEmpty && completed) ||
(accountIsEmpty && accountIndex === 0)
) {
dispatch({
type: ACCOUNT.CREATE,
payload: account,
@ -256,7 +300,9 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
}
};
const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
await TrezorConnect.getFeatures({
device: {
path: device.path,
@ -279,7 +325,9 @@ const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction
});
};
export const reconnect = (network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
export const reconnect = (network: string): PromiseAction<void> => async (
dispatch: Dispatch
): Promise<void> => {
await dispatch(BlockchainActions.subscribe(network));
dispatch(restore());
};
@ -296,10 +344,16 @@ export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetStat
if (!selected) return;
// find discovery process for requested network
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.network === urlParams.network);
const discoveryProcess: ?Discovery = getState().discovery.find(
d => d.deviceState === selected.state && d.network === urlParams.network
);
// if there was no process befor OR process was interrupted/waiting
const shouldStart = !discoveryProcess || (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain);
const shouldStart =
!discoveryProcess ||
(discoveryProcess.interrupted ||
discoveryProcess.waitingForDevice ||
discoveryProcess.waitingForBlockchain);
if (shouldStart) {
dispatch(start(selected, urlParams.network));
}
@ -310,7 +364,9 @@ export const stop = (): ThunkAction => (dispatch: Dispatch, getState: GetState):
if (!device) return;
// get all uncompleted discovery processes which assigned to selected device
const discoveryProcesses = getState().discovery.filter(d => d.deviceState === device.state && !d.completed);
const discoveryProcesses = getState().discovery.filter(
d => d.deviceState === device.state && !d.completed
);
if (discoveryProcesses.length > 0) {
dispatch({
type: DISCOVERY.STOP,

@ -1,6 +1,5 @@
/* @flow */
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as ACCOUNT from 'actions/constants/account';
import * as TOKEN from 'actions/constants/token';
@ -32,18 +31,21 @@ import type { Config, Network, TokensCollection } from 'reducers/LocalStorageRed
import Erc20AbiJSON from 'public/data/ERC20Abi.json';
import AppConfigJSON from 'public/data/appConfig.json';
export type StorageAction = {
type: typeof STORAGE.READY,
config: Config,
tokens: TokensCollection,
ERC20Abi: Array<TokensCollection>
} | {
type: typeof STORAGE.SAVE,
network: string,
} | {
type: typeof STORAGE.ERROR,
error: string,
};
export type StorageAction =
| {
type: typeof STORAGE.READY,
config: Config,
tokens: TokensCollection,
ERC20Abi: Array<TokensCollection>,
}
| {
type: typeof STORAGE.SAVE,
network: string,
}
| {
type: typeof STORAGE.ERROR,
error: string,
};
const TYPE: 'local' = 'local';
const { STORAGE_PATH } = storageUtils;
@ -60,16 +62,39 @@ const KEY_LANGUAGE: string = `${STORAGE_PATH}language`;
// or
// https://www.npmjs.com/package/redux-react-session
const findAccounts = (devices: Array<TrezorDevice>, accounts: Array<Account>): Array<Account> => devices.reduce((arr, dev) => arr.concat(accounts.filter(a => a.deviceState === dev.state)), []);
const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token> => accounts.reduce((arr, account) => arr.concat(getAccountTokens(tokens, account)), []);
const findDiscovery = (devices: Array<TrezorDevice>, discovery: Array<Discovery>): Array<Discovery> => devices.reduce((arr, dev) => arr.concat(discovery.filter(d => d.deviceState === dev.state && d.completed)), []);
const findPendingTxs = (accounts: Array<Account>, pending: Array<Transaction>): Array<Transaction> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.descriptor === account.descriptor && p.network === account.network)), []);
const findAccounts = (devices: Array<TrezorDevice>, accounts: Array<Account>): Array<Account> =>
devices.reduce((arr, dev) => arr.concat(accounts.filter(a => a.deviceState === dev.state)), []);
const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token> =>
accounts.reduce((arr, account) => arr.concat(getAccountTokens(tokens, account)), []);
const findDiscovery = (
devices: Array<TrezorDevice>,
discovery: Array<Discovery>
): Array<Discovery> =>
devices.reduce(
(arr, dev) => arr.concat(discovery.filter(d => d.deviceState === dev.state && d.completed)),
[]
);
const findPendingTxs = (
accounts: Array<Account>,
pending: Array<Transaction>
): Array<Transaction> =>
accounts.reduce(
(result, account) =>
result.concat(
pending.filter(
p => p.descriptor === account.descriptor && p.network === account.network
)
),
[]
);
export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const devices: Array<TrezorDevice> = getState().devices.filter(d => d.features && d.remember === true);
const devices: Array<TrezorDevice> = getState().devices.filter(
d => d.features && d.remember === true
);
const accounts: Array<Account> = findAccounts(devices, getState().accounts);
const tokens: Array<Token> = findTokens(accounts, getState().tokens);
const pending: Array<Transaction> = findPendingTxs(accounts, getState().pending);
@ -98,14 +123,12 @@ export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch)
// check if device was added/ removed
// const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue);
// const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features);
// if (newDevices.length !== myDevices.length) {
// const diff = myDevices.filter(d => newDevices.indexOf(d) < 0)
// console.warn("DEV LIST CHANGED!", newDevices.length, myDevices.length, diff)
// // check if difference is caused by local device which is not saved
// // or device which was saved in other tab
// }
// const diff = oldDevices.filter(d => newDevices.indexOf())
}
@ -151,19 +174,25 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> =>
const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json');
window.addEventListener('storage', (event) => {
window.addEventListener('storage', event => {
dispatch(update(event));
});
// load tokens
const tokens = await config.networks.reduce(async (promise: Promise<TokensCollection>, network: Network): Promise<TokensCollection> => {
const collection: TokensCollection = await promise;
if (network.tokens) {
const json = await httpRequest(network.tokens, 'json');
collection[network.shortcut] = json;
}
return collection;
}, Promise.resolve({}));
const tokens = await config.networks.reduce(
async (
promise: Promise<TokensCollection>,
network: Network
): Promise<TokensCollection> => {
const collection: TokensCollection = await promise;
if (network.tokens) {
const json = await httpRequest(network.tokens, 'json');
collection[network.shortcut] = json;
}
return collection;
},
Promise.resolve({})
);
dispatch({
type: STORAGE.READY,
@ -230,7 +259,9 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
}
if (buildUtils.isDev() || buildUtils.isBeta()) {
const betaModal = Object.keys(window.localStorage).find(key => key.indexOf(KEY_BETA_MODAL) >= 0);
const betaModal = Object.keys(window.localStorage).find(
key => key.indexOf(KEY_BETA_MODAL) >= 0
);
if (!betaModal) {
dispatch({
type: WALLET.SHOW_BETA_DISCLAIMER,

@ -2,19 +2,20 @@
import * as LOG from 'actions/constants/log';
import type {
Action, ThunkAction, GetState, Dispatch,
} from 'flowtype';
import type { Action, ThunkAction, GetState, Dispatch } from 'flowtype';
import type { LogEntry } from 'reducers/LogReducer';
export type LogAction = {
type: typeof LOG.OPEN,
} | {
type: typeof LOG.CLOSE,
} | {
type: typeof LOG.ADD,
payload: LogEntry
};
export type LogAction =
| {
type: typeof LOG.OPEN,
}
| {
type: typeof LOG.CLOSE,
}
| {
type: typeof LOG.ADD,
payload: LogEntry,
};
export const toggle = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
if (!getState().log.opened) {

@ -6,25 +6,25 @@ import type { Device } from 'trezor-connect';
import * as MODAL from 'actions/constants/modal';
import * as CONNECT from 'actions/constants/TrezorConnect';
import type {
ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice,
} from 'flowtype';
import type { ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice } from 'flowtype';
import type { State } from 'reducers/ModalReducer';
import type { parsedURI } from 'utils/cryptoUriParser';
import sendEthereumFormActions from './ethereum/SendFormActions';
import sendRippleFormActions from './ripple/SendFormActions';
export type ModalAction = {
type: typeof MODAL.CLOSE
} | {
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
id: string,
url: string,
} | {
type: typeof MODAL.OPEN_SCAN_QR,
};
export type ModalAction =
| {
type: typeof MODAL.CLOSE,
}
| {
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
id: string,
url: string,
}
| {
type: typeof MODAL.OPEN_SCAN_QR,
};
export const onPinSubmit = (value: string): Action => {
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
@ -33,7 +33,10 @@ export const onPinSubmit = (value: string): Action => {
};
};
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { modal } = getState();
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
@ -59,7 +62,9 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (di
});
};
export const onReceiveConfirmation = (confirmation: any): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
export const onReceiveConfirmation = (confirmation: any): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
await TrezorConnect.uiResponse({
type: UI.RECEIVE_CONFIRMATION,
payload: confirmation,
@ -89,7 +94,9 @@ export const onCancel = (): Action => ({
type: MODAL.CLOSE,
});
export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (
dispatch: Dispatch
): void => {
dispatch(onCancel());
dispatch({
@ -98,11 +105,17 @@ export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatc
});
};
export const onRememberRequest = (prevState: State): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const onRememberRequest = (prevState: State): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().modal;
// handle case where forget modal is already opened
// TODO: 2 modals at once (two devices disconnected in the same time)
if (prevState.context === MODAL.CONTEXT_DEVICE && prevState.windowType === CONNECT.REMEMBER_REQUEST) {
if (
prevState.context === MODAL.CONTEXT_DEVICE &&
prevState.windowType === CONNECT.REMEMBER_REQUEST
) {
// forget current (new)
if (state.context === MODAL.CONTEXT_DEVICE) {
dispatch({
@ -119,12 +132,20 @@ export const onRememberRequest = (prevState: State): ThunkAction => (dispatch: D
}
};
export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const onDeviceConnect = (device: Device): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
// interrupt process of remembering device (force forget)
// TODO: the same for disconnect more than 1 device at once
const { modal } = getState();
if (modal.context === MODAL.CONTEXT_DEVICE && modal.windowType === CONNECT.REMEMBER_REQUEST) {
if (device.features && modal.device && modal.device.features && modal.device.features.device_id === device.features.device_id) {
if (
device.features &&
modal.device &&
modal.device.features &&
modal.device.features.device_id === device.features.device_id
) {
dispatch({
type: MODAL.CLOSE,
});
@ -137,7 +158,10 @@ export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispa
}
};
export const onWalletTypeRequest = (hidden: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const onWalletTypeRequest = (hidden: boolean): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { modal } = getState();
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
dispatch({
@ -150,7 +174,9 @@ export const onWalletTypeRequest = (hidden: boolean): ThunkAction => (dispatch:
});
};
export const gotoExternalWallet = (id: string, url: string): ThunkAction => (dispatch: Dispatch): void => {
export const gotoExternalWallet = (id: string, url: string): ThunkAction => (
dispatch: Dispatch
): void => {
dispatch({
type: MODAL.OPEN_EXTERNAL_WALLET,
id,
@ -164,7 +190,9 @@ export const openQrModal = (): ThunkAction => (dispatch: Dispatch): void => {
});
};
export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction => (dispatch: Dispatch): void => {
export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction => (
dispatch: Dispatch
): void => {
const { address = '', amount } = parsedUri;
switch (networkType) {
case 'ethereum':
@ -180,7 +208,6 @@ export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction
}
};
export default {
onPinSubmit,
onPassphraseSubmit,
@ -194,4 +221,4 @@ export default {
gotoExternalWallet,
openQrModal,
onQrScan,
};
};

@ -2,41 +2,48 @@
import * as React from 'react';
import * as NOTIFICATION from 'actions/constants/notification';
import type {
Action, AsyncAction, GetState, Dispatch, RouterLocationState,
} from 'flowtype';
import type { Action, AsyncAction, GetState, Dispatch, RouterLocationState } from 'flowtype';
import type { CallbackAction } from 'reducers/NotificationReducer';
export type NotificationAction = {
type: typeof NOTIFICATION.ADD,
payload: {
+type: string,
+title: React.Node | string,
+message?: ?(React.Node | string),
+cancelable: boolean,
actions?: Array<CallbackAction>
}
} | {
type: typeof NOTIFICATION.CLOSE,
payload?: {
id?: string;
devicePath?: string
}
}
export type NotificationAction =
| {
type: typeof NOTIFICATION.ADD,
payload: {
+type: string,
+title: React.Node | string,
+message?: ?(React.Node | string),
+cancelable: boolean,
actions?: Array<CallbackAction>,
},
}
| {
type: typeof NOTIFICATION.CLOSE,
payload?: {
id?: string,
devicePath?: string,
},
};
export const close = (payload: any = {}): Action => ({
type: NOTIFICATION.CLOSE,
payload,
});
// called from RouterService
export const clear = (currentParams: RouterLocationState, requestedParams: RouterLocationState): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const clear = (
currentParams: RouterLocationState,
requestedParams: RouterLocationState
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
// if route has been changed from device view into something else (like other device, settings...)
// try to remove all Notifications which are linked to previous device (they are not cancelable by user)
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string');
entries.forEach((entry) => {
if (
currentParams.device !== requestedParams.device ||
currentParams.deviceInstance !== requestedParams.deviceInstance
) {
const entries = getState().notifications.filter(
entry => typeof entry.devicePath === 'string'
);
entries.forEach(entry => {
if (typeof entry.devicePath === 'string') {
dispatch({
type: NOTIFICATION.CLOSE,

@ -1,24 +1,28 @@
/* @flow */
import * as PENDING from 'actions/constants/pendingTx';
import type { Transaction } from 'flowtype';
import type { State } from 'reducers/PendingTxReducer';
export type PendingTxAction = {
type: typeof PENDING.FROM_STORAGE,
payload: State
} | {
type: typeof PENDING.ADD,
payload: Transaction
} | {
type: typeof PENDING.TX_RESOLVED,
hash: string,
} | {
type: typeof PENDING.TX_REJECTED,
hash: string,
} | {
type: typeof PENDING.TX_TOKEN_ERROR,
hash: string,
}
export type PendingTxAction =
| {
type: typeof PENDING.FROM_STORAGE,
payload: State,
}
| {
type: typeof PENDING.ADD,
payload: Transaction,
}
| {
type: typeof PENDING.TX_RESOLVED,
hash: string,
}
| {
type: typeof PENDING.TX_REJECTED,
hash: string,
}
| {
type: typeof PENDING.TX_TOKEN_ERROR,
hash: string,
};

@ -1,6 +1,5 @@
/* @flow */
import TrezorConnect from 'trezor-connect';
import * as RECEIVE from 'actions/constants/receive';
import * as NOTIFICATION from 'actions/constants/notification';
@ -8,25 +7,29 @@ import * as NOTIFICATION from 'actions/constants/notification';
import { initialState } from 'reducers/ReceiveReducer';
import type { State } from 'reducers/ReceiveReducer';
import type {
TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch,
} from 'flowtype';
export type ReceiveAction = {
type: typeof RECEIVE.INIT,
state: State
} | {
type: typeof RECEIVE.DISPOSE,
} | {
type: typeof RECEIVE.REQUEST_UNVERIFIED,
device: TrezorDevice
} | {
type: typeof RECEIVE.SHOW_ADDRESS
} | {
type: typeof RECEIVE.HIDE_ADDRESS
} | {
type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS
}
import type { TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch } from 'flowtype';
export type ReceiveAction =
| {
type: typeof RECEIVE.INIT,
state: State,
}
| {
type: typeof RECEIVE.DISPOSE,
}
| {
type: typeof RECEIVE.REQUEST_UNVERIFIED,
device: TrezorDevice,
}
| {
type: typeof RECEIVE.SHOW_ADDRESS,
}
| {
type: typeof RECEIVE.HIDE_ADDRESS,
}
| {
type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS,
};
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
const state: State = {
@ -48,7 +51,10 @@ export const showUnverifiedAddress = (): Action => ({
});
//export const showAddress = (address_n: string): AsyncAction => {
export const showAddress = (path: Array<number>): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const showAddress = (path: Array<number>): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
const { network } = getState().selectedAccount;
@ -81,7 +87,11 @@ export const showAddress = (path: Array<number>): AsyncAction => async (dispatch
response = await TrezorConnect.rippleGetAddress(params);
break;
default:
response = { payload: { error: `ReceiveActions.showAddress: Unknown network type: ${network.type}` } };
response = {
payload: {
error: `ReceiveActions.showAddress: Unknown network type: ${network.type}`,
},
};
break;
}
@ -123,4 +133,4 @@ export default {
dispose,
showAddress,
showUnverifiedAddress,
};
};

@ -18,9 +18,11 @@ import type {
import type { RouterAction } from 'connected-react-router';
/*
* Parse url string to RouterLocationState object (key/value)
*/
export const pathToParams = (path: string): PayloadAction<RouterLocationState> => (): RouterLocationState => {
* 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 = {};
@ -46,10 +48,13 @@ export const pathToParams = (path: string): PayloadAction<RouterLocationState> =
};
/*
* RouterLocationState validation
* Check if requested device or network exists in reducers
*/
export const paramsValidation = (params: RouterLocationState): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* 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')) {
@ -57,9 +62,18 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
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));
device = devices.find(
d =>
d.features &&
d.features.device_id === params.device &&
d.instance === parseInt(params.deviceInstance, 10)
);
} else {
device = devices.find(d => ((!d.features || d.mode === 'bootloader') && d.path === params.device) || (d.features && d.features.device_id === params.device));
device = devices.find(
d =>
((!d.features || d.mode === 'bootloader') && d.path === params.device) ||
(d.features && d.features.device_id === params.device)
);
}
if (!device) return false;
@ -87,12 +101,16 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
};
/*
* Composing url string from given RouterLocationState object
* Filters unrecognized fields and sorting in correct order
*/
export const paramsToPath = (params: RouterLocationState): PayloadAction<?string> => (): ?string => {
* 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));
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);
@ -111,7 +129,7 @@ export const paramsToPath = (params: RouterLocationState): PayloadAction<?string
// compose url string from pattern
let url: string = '';
patternToUse.forEach((field) => {
patternToUse.forEach(field => {
if (field === params[field]) {
// standalone (odd) fields
url += `/${field}`;
@ -127,7 +145,10 @@ export const paramsToPath = (params: RouterLocationState): PayloadAction<?string
return url;
};
export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dispatch: Dispatch, getState: GetState): string => {
export const getValidUrl = (action: RouterAction): PayloadAction<string> => (
dispatch: Dispatch,
getState: GetState
): string => {
const { location } = getState().router;
const { firstLocationChange } = getState().wallet;
// redirect to landing page (loading screen)
@ -151,12 +172,17 @@ export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dis
// 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; }
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 shouldBeLandingPage =
getState().devices.length < 1 ||
!getState().wallet.ready ||
getState().connect.error !== null;
const landingPageUrl = dispatch(isLandingPageUrl(requestedUrl));
if (shouldBeLandingPage) {
const landingPageRoute = dispatch(isLandingPageUrl(requestedUrl, getState().wallet.ready));
@ -185,13 +211,17 @@ export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dis
};
/*
* Compose url from requested device object and returns url
*/
const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> => (dispatch: Dispatch, getState: GetState): ?string => {
* Compose url from requested device object and returns url
*/
const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> => (
dispatch: Dispatch,
getState: GetState
): ?string => {
let url: ?string;
if (!device.features) {
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
} else if (device.mode === 'bootloader') { // device in bootloader doesn't have device_id
} else if (device.mode === 'bootloader') {
// device in bootloader doesn't have device_id
url = `/device/${device.path}/bootloader`;
} else if (device.mode === 'initialize') {
url = `/device/${device.features.device_id}/initialize`;
@ -207,7 +237,9 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
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 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}`;
@ -218,13 +250,16 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
};
/*
* 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 getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (dispatch: Dispatch, getState: GetState): ?string => {
* 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 getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (
dispatch: Dispatch,
getState: GetState
): ?string => {
const { devices } = getState();
let url: ?string;
if (devices.length > 0) {
@ -241,20 +276,24 @@ export const getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (dispatc
};
/*
* Utility used in "getDeviceUrl" and "getFirstAvailableDeviceUrl"
* 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;
});
* Utility used in "getDeviceUrl" and "getFirstAvailableDeviceUrl"
* 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;
});
/*
* Redirect to requested device
*/
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Redirect to requested device
*/
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
if (dispatch(setInitialUrl())) return;
const url: ?string = dispatch(getDeviceUrl(device));
@ -262,20 +301,30 @@ export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dis
const currentParams: RouterLocationState = getState().router.location.state;
const requestedParams = dispatch(pathToParams(url));
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
if (
currentParams.device !== requestedParams.device ||
currentParams.deviceInstance !== requestedParams.deviceInstance
) {
dispatch(goto(url));
}
};
/*
* Redirect to first device or landing page
*/
export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Redirect to first device or landing page
*/
export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const url = dispatch(getFirstAvailableDeviceUrl());
if (url) {
const currentParams = getState().router.location.state;
const requestedParams = dispatch(pathToParams(url));
if (gotoRoot || currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
if (
gotoRoot ||
currentParams.device !== requestedParams.device ||
currentParams.deviceInstance !== requestedParams.deviceInstance
) {
dispatch(goto(url));
}
} else {
@ -284,8 +333,8 @@ export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkActi
};
/*
* Internal method. redirect to given url
*/
* 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));
@ -293,15 +342,20 @@ const goto = (url: string): ThunkAction => (dispatch: Dispatch, getState: GetSta
};
/*
* Check if requested OR current url is landing page
*/
export const isLandingPageUrl = ($url?: string, checkRoutes: boolean = false): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* Check if requested OR current url is landing page
*/
export const isLandingPageUrl = (
$url?: string,
checkRoutes: boolean = false
): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
let url: ?string = $url;
if (typeof url !== 'string') {
url = getState().router.location.pathname;
}
if (checkRoutes) {
const isLandingRoute = routes.find(r => r.pattern === url && r.name.indexOf('landing') >= 0);
const isLandingRoute = routes.find(
r => r.pattern === url && r.name.indexOf('landing') >= 0
);
if (isLandingRoute) {
return true;
<