Lint most of the files

pull/407/head
Vladimir Volek 5 years ago
parent 0e82ea847f
commit 82f8d8d36b

@ -5,6 +5,8 @@
"prettier/babel",
"prettier/flowtype",
"prettier/react",
"plugin:jest/recommended",
"plugin:flowtype/recommended",
"plugin:jest/recommended"
],
"globals": {

@ -7,6 +7,5 @@
"bracketSpacing": true,
"semi": true,
"useTabs": false,
"jsxBracketSameLine": false,
"singleAttributePerLine": true
"jsxBracketSameLine": false
}

@ -21,7 +21,7 @@
"lint": "run-s lint:*",
"lint:js": "npx eslint ./src ./webpack",
"lint:css": "npx stylelint './src/**/*.js'",
"lint:prettier": "prettylint --check ./src/**/*.js",
"lint:prettier": "prettier --write ./src/**/*.js",
"prettier:check": "eslint --print-config ./src | eslint-config-prettier-check",
"test": "run-s test:*",
"test:unit": "npx jest",

@ -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,84 @@ 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 +126,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 +142,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 +193,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 +205,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 +218,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 +281,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 +299,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 +324,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 +343,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 +363,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;
}
@ -310,8 +364,8 @@ export const isLandingPageUrl = ($url?: string, checkRoutes: boolean = false): P
};
/*
* Try to redirect to landing page
*/
* Try to redirect to landing page
*/
export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void => {
const isLandingPage = dispatch(isLandingPageUrl());
if (!isLandingPage) {
@ -320,61 +374,76 @@ export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void =>
};
/*
* Go to given device settings page
*/
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
* 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}` : ''}`;
const devUrl: string = `${device.features.device_id}${
device.instance ? `:${device.instance}` : ''
}`;
dispatch(goto(`/device/${devUrl}/settings`));
}
};
/*
* Go to UpdateBridge page
*/
* Go to UpdateBridge page
*/
export const gotoBridgeUpdate = (): ThunkAction => (dispatch: Dispatch): void => {
dispatch(goto('/bridge'));
};
/*
* Go to UpdateFirmware page
* Called from App notification
*/
export const gotoFirmwareUpdate = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Go to UpdateFirmware page
* Called from App notification
*/
export const gotoFirmwareUpdate = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { selectedDevice } = getState().wallet;
if (!selectedDevice || !selectedDevice.features) return;
const devUrl: string = `${selectedDevice.features.device_id}${selectedDevice.instance ? `:${selectedDevice.instance}` : ''}`;
const devUrl: string = `${selectedDevice.features.device_id}${
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
}`;
dispatch(goto(`/device/${devUrl}/firmware-update`));
};
/*
* Go to NoBackup page
*/
* Go to NoBackup page
*/
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const { selectedDevice } = getState().wallet;
if (!selectedDevice || !selectedDevice.features) return;
const devUrl: string = `${selectedDevice.features.device_id}${selectedDevice.instance ? `:${selectedDevice.instance}` : ''}`;
const devUrl: string = `${selectedDevice.features.device_id}${
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
}`;
dispatch(goto(`/device/${devUrl}/backup`));
};
/*
* Try to redirect to initial url
*/
export const setInitialUrl = (): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* 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, true))) {
const valid = dispatch(getValidUrl({
type: LOCATION_CHANGE,
payload: {
location: {
pathname: initialPathname,
hash: '',
search: '',
state: {},
const valid = dispatch(
getValidUrl({
type: LOCATION_CHANGE,
payload: {
location: {
pathname: initialPathname,
hash: '',
search: '',
state: {},
},
},
},
}));
})
);
if (valid === initialPathname) {
// reset initial url

@ -11,13 +11,7 @@ import * as reducerUtils from 'reducers/utils';
import { getVersion } from 'utils/device';
import { initialState } from 'reducers/SelectedAccountReducer';
import type {
PayloadAction,
Action,
GetState,
Dispatch,
State,
} from 'flowtype';
import type { PayloadAction, Action, GetState, Dispatch, State } from 'flowtype';
import type {
State as SelectedAccountState,
@ -26,12 +20,14 @@ import type {
ExceptionPage,
} from 'reducers/SelectedAccountReducer';
export type SelectedAccountAction = {
type: typeof ACCOUNT.DISPOSE,
} | {
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload: SelectedAccountState,
};
export type SelectedAccountAction =
| {
type: typeof ACCOUNT.DISPOSE,
}
| {
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload: SelectedAccountState,
};
export const dispose = (): Action => ({
type: ACCOUNT.DISPOSE,
@ -68,11 +64,7 @@ const getExceptionPage = (state: State, selectedAccount: SelectedAccountState):
// display loader instead of component body
const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?Loader => {
const device = state.wallet.selectedDevice;
const {
account,
discovery,
network,
} = selectedAccount;
const { account, discovery, network } = selectedAccount;
if (!device || !device.state) {
return {
@ -89,7 +81,6 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState):
};
}
if (account) return null;
// account not found (yet). checking why...
@ -135,7 +126,10 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState):
};
// display notification above the component, with or without component body
const getAccountNotification = (state: State, selectedAccount: SelectedAccountState): ?(Notification & { shouldRender: boolean }) => {
const getAccountNotification = (
state: State,
selectedAccount: SelectedAccountState
): ?(Notification & { shouldRender: boolean }) => {
const device = state.wallet.selectedDevice;
const { account, network, discovery } = selectedAccount;
if (!device || !network) return null;
@ -190,16 +184,21 @@ const actions = [
...Object.values(BLOCKCHAIN).filter(v => typeof v === 'string'),
WALLET.SET_SELECTED_DEVICE,
WALLET.UPDATE_SELECTED_DEVICE,
...Object.values(ACCOUNT).filter(v => typeof v === 'string' && v !== ACCOUNT.UPDATE_SELECTED_ACCOUNT && v !== ACCOUNT.DISPOSE), // exported values got unwanted "__esModule: true" as first element
...Object.values(ACCOUNT).filter(
v => typeof v === 'string' && v !== ACCOUNT.UPDATE_SELECTED_ACCOUNT && v !== ACCOUNT.DISPOSE
), // exported values got unwanted "__esModule: true" as first element
...Object.values(DISCOVERY).filter(v => typeof v === 'string'),
...Object.values(TOKEN).filter(v => typeof v === 'string'),
...Object.values(PENDING).filter(v => typeof v === 'string'),
];
/*
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
// ignore not listed actions
if (actions.indexOf(action.type) < 0) return false;
const state: State = getState();
@ -238,12 +237,22 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
newState.notification = notification;
}
newState.shouldRender = !(loader || exceptionPage || (notification && !notification.shouldRender));
newState.shouldRender = !(
loader ||
exceptionPage ||
(notification && !notification.shouldRender)
);
// check if newState is different than previous state
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, {
account: ['balance', 'nonce'],
discovery: ['accountIndex', 'interrupted', 'completed', 'waitingForBlockchain', 'waitingForDevice'],
discovery: [
'accountIndex',
'interrupted',
'completed',
'waitingForBlockchain',
'waitingForDevice',
],
});
if (stateChanged) {

@ -4,33 +4,30 @@ import * as SEND from 'actions/constants/send';
import * as WEB3 from 'actions/constants/web3';
import * as BLOCKCHAIN from 'actions/constants/blockchain';
import type {
Dispatch,
GetState,
State as ReducersState,
Action,
ThunkAction,
} from 'flowtype';
import type { Dispatch, GetState, State as ReducersState, Action, ThunkAction } from 'flowtype';
import type { State as EthereumState } from 'reducers/SendFormEthereumReducer';
import type { State as RippleState } from 'reducers/SendFormRippleReducer';
import * as EthereumSendFormActions from './ethereum/SendFormActions';
import * as RippleSendFormActions from './ripple/SendFormActions';
export type SendFormAction = {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ethereum',
state: EthereumState,
} | {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ripple',
state: RippleState,
} | {
type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR,
} | {
type: typeof SEND.TX_COMPLETE,
};
export type SendFormAction =
| {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ethereum',
state: EthereumState,
}
| {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ripple',
state: RippleState,
}
| {
type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR,
}
| {
type: typeof SEND.TX_COMPLETE,
};
// list of all actions which has influence on "sendForm" reducer
// other actions will be ignored
@ -42,9 +39,12 @@ const actions = [
];
/*
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
// ignore not listed actions
if (actions.indexOf(action.type) < 0) return;
@ -62,6 +62,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
case 'ripple':
dispatch(RippleSendFormActions.observe(prevState, action));
break;
default: break;
default:
break;
}
};

@ -4,12 +4,7 @@ import { findToken } from 'reducers/utils';
import type { State as EthereumSendFormState } from 'reducers/SendFormEthereumReducer';
import type { State as RippleSendFormState } from 'reducers/SendFormRippleReducer';
import type {
ThunkAction,
PayloadAction,
GetState,
Dispatch,
} from 'flowtype';
import type { ThunkAction, PayloadAction, GetState, Dispatch } from 'flowtype';
const TYPE: 'session' = 'session';
const { STORAGE_PATH } = storageUtils;
@ -20,7 +15,10 @@ const getTxDraftKey = (getState: GetState): string => {
return `${KEY_TX_DRAFT}${pathname}`;
};
export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const saveDraftTransaction = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state = getState().sendFormEthereum;
if (state.untouched) return;
@ -28,7 +26,10 @@ export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getS
storageUtils.set(TYPE, key, JSON.stringify(state));
};
export const loadEthereumDraftTransaction = (): PayloadAction<?EthereumSendFormState> => (dispatch: Dispatch, getState: GetState): ?EthereumSendFormState => {
export const loadEthereumDraftTransaction = (): PayloadAction<?EthereumSendFormState> => (
dispatch: Dispatch,
getState: GetState
): ?EthereumSendFormState => {
const key = getTxDraftKey(getState);
const value: ?string = storageUtils.get(TYPE, key);
if (!value) return null;
@ -53,7 +54,10 @@ export const loadEthereumDraftTransaction = (): PayloadAction<?EthereumSendFormS
return state;
};
export const loadRippleDraftTransaction = (): PayloadAction<?RippleSendFormState> => (dispatch: Dispatch, getState: GetState): ?RippleSendFormState => {
export const loadRippleDraftTransaction = (): PayloadAction<?RippleSendFormState> => (
dispatch: Dispatch,
getState: GetState
): ?RippleSendFormState => {
const key = getTxDraftKey(getState);
const value: ?string = storageUtils.get(TYPE, key);
if (!value) return null;

@ -1,41 +1,45 @@
/* @flow */
import TrezorConnect from 'trezor-connect';
import type {
GetState, Dispatch, ThunkAction, AsyncAction,
} from 'flowtype';
import type { GetState, Dispatch, ThunkAction, AsyncAction } from 'flowtype';
import { validateAddress } from 'utils/ethUtils';
import * as NOTIFICATION from 'actions/constants/notification';
import * as SIGN_VERIFY from './constants/signVerify';
export type SignVerifyAction = {
type: typeof SIGN_VERIFY.SIGN_SUCCESS,
signSignature: string
} | {
type: typeof SIGN_VERIFY.CLEAR_SIGN,
} | {
type: typeof SIGN_VERIFY.CLEAR_VERIFY,
} | {
type: typeof SIGN_VERIFY.INPUT_CHANGE,
inputName: string,
value: string
} | {
type: typeof SIGN_VERIFY.TOUCH,
inputName: string,
} | {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string
} | {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string
}
export type SignVerifyAction =
| {
type: typeof SIGN_VERIFY.SIGN_SUCCESS,
signSignature: string,
}
| {
type: typeof SIGN_VERIFY.CLEAR_SIGN,
}
| {
type: typeof SIGN_VERIFY.CLEAR_VERIFY,
}
| {
type: typeof SIGN_VERIFY.INPUT_CHANGE,
inputName: string,
value: string,
}
| {
type: typeof SIGN_VERIFY.TOUCH,
inputName: string,
}
| {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string,
}
| {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string,
};
const sign = (
path: Array<number>,
message: string,
hex: boolean = false,
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const sign = (path: Array<number>, message: string, hex: boolean = false): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
@ -73,7 +77,7 @@ const verify = (
address: string,
message: string,
signature: string,
hex: boolean = false,
hex: boolean = false
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
@ -125,7 +129,9 @@ const verify = (
}
};
const inputChange = (inputName: string, value: string): ThunkAction => (dispatch: Dispatch): void => {
const inputChange = (inputName: string, value: string): ThunkAction => (
dispatch: Dispatch
): void => {
dispatch({
type: SIGN_VERIFY.INPUT_CHANGE,
inputName,
@ -162,4 +168,4 @@ export default {
clearSign,
clearVerify,
inputChange,
};
};

@ -2,19 +2,20 @@
import * as SUMMARY from 'actions/constants/summary';
import { initialState } from 'reducers/SummaryReducer';
import type {
ThunkAction, Action, Dispatch,
} from 'flowtype';
import type { ThunkAction, Action, Dispatch } from 'flowtype';
import type { State } from 'reducers/SummaryReducer';
export type SummaryAction = {
type: typeof SUMMARY.INIT,
state: State
} | {
type: typeof SUMMARY.DISPOSE,
} | {
type: typeof SUMMARY.DETAILS_TOGGLE
}
export type SummaryAction =
| {
type: typeof SUMMARY.INIT,
state: State,
}
| {
type: typeof SUMMARY.DISPOSE,
}
| {
type: typeof SUMMARY.DETAILS_TOGGLE,
};
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
const state: State = {

@ -1,41 +1,47 @@
/* @flow */
import * as TOKEN from 'actions/constants/token';
import type {
GetState, AsyncAction, Action, Dispatch,
} from 'flowtype';
import type { GetState, AsyncAction, Action, Dispatch } from 'flowtype';
import type { State, Token } from 'reducers/TokensReducer';
import type { Account } from 'reducers/AccountsReducer';
import type { NetworkToken } from 'reducers/LocalStorageReducer';
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
export type TokenAction = {
type: typeof TOKEN.FROM_STORAGE,
payload: State
} | {
type: typeof TOKEN.ADD,
payload: Token
} | {
type: typeof TOKEN.REMOVE,
token: Token
} | {
type: typeof TOKEN.SET_BALANCE,
payload: State
}
export type TokenAction =
| {
type: typeof TOKEN.FROM_STORAGE,
payload: State,
}
| {
type: typeof TOKEN.ADD,
payload: Token,
}
| {
type: typeof TOKEN.REMOVE,
token: Token,
}
| {
type: typeof TOKEN.SET_BALANCE,
payload: State,
};
// action from component <reactSelect>
export const load = ($input: string, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<any> => {
export const load = ($input: string, network: string): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<any> => {
let input = $input;
if (input.length < 1) input = '0x';
const tokens = getState().localStorage.tokens[network];
const value = input.toLowerCase();
const result = tokens.filter(t => t.symbol.toLowerCase().indexOf(value) >= 0
|| t.address.toLowerCase().indexOf(value) >= 0
|| t.name.toLowerCase().indexOf(value) >= 0);
const result = tokens.filter(
t =>
t.symbol.toLowerCase().indexOf(value) >= 0 ||
t.address.toLowerCase().indexOf(value) >= 0 ||
t.name.toLowerCase().indexOf(value) >= 0
);
if (result.length > 0) {
// TODO: Temporary fix for async select
@ -52,23 +58,33 @@ export const load = ($input: string, network: string): AsyncAction => async (dis
return null;
};
export const setBalance = (tokenAddress: string, ethAddress: string, balance: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const setBalance = (
tokenAddress: string,
ethAddress: string,
balance: string
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const newState: Array<Token> = [...getState().tokens];
const token: ?Token = newState.find(t => t.address === tokenAddress && t.ethAddress === ethAddress);
const token: ?Token = newState.find(
t => t.address === tokenAddress && t.ethAddress === ethAddress
);
if (token) {
const others = newState.filter(t => t !== token);
dispatch({
type: TOKEN.SET_BALANCE,
payload: others.concat([{
...token,
loaded: true,
balance,
}]),
payload: others.concat([
{
...token,
loaded: true,
balance,
},
]),
});
}
};
export const add = (token: NetworkToken, account: Account): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
export const add = (token: NetworkToken, account: Account): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
const tkn: Token = {
loaded: false,
deviceState: account.deviceState,
@ -93,4 +109,4 @@ export const add = (token: NetworkToken, account: Account): AsyncAction => async
export const remove = (token: Token): Action => ({
type: TOKEN.REMOVE,
token,
});
});

@ -1,6 +1,10 @@
/* @flow */
import TrezorConnect, {
DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT,
DEVICE,
DEVICE_EVENT,
UI_EVENT,
TRANSPORT_EVENT,
BLOCKCHAIN_EVENT,
} from 'trezor-connect';
import { CONTEXT_NONE } from 'actions/constants/modal';
import urlConstants from 'constants/urls';
@ -31,96 +35,126 @@ import type {
TrezorDevice,
} from 'flowtype';
export type TrezorConnectAction = {
type: typeof CONNECT.INITIALIZATION_ERROR,
error: string
} | {
type: typeof CONNECT.NETWORK_CHANGED,
payload: {
network: string
}
} | {
type: typeof CONNECT.AUTH_DEVICE,
device: TrezorDevice,
state: string
} | {
type: typeof CONNECT.DUPLICATE,
device: TrezorDevice
} | {
type: typeof CONNECT.REMEMBER_REQUEST,
device: TrezorDevice,
instances: Array<TrezorDevice>
} | {
type: typeof CONNECT.DISCONNECT_REQUEST,
device: TrezorDevice
} | {
type: typeof CONNECT.FORGET_REQUEST,
device: TrezorDevice
} | {
type: typeof CONNECT.FORGET,
device: TrezorDevice
} | {
type: typeof CONNECT.FORGET_SINGLE | typeof CONNECT.FORGET_SILENT,
device: TrezorDevice
} | {
type: typeof CONNECT.REMEMBER,
device: TrezorDevice
} | {
type: typeof CONNECT.TRY_TO_DUPLICATE,
device: TrezorDevice
} | {
type: typeof CONNECT.DEVICE_FROM_STORAGE,
payload: Array<TrezorDevice>
} | {
type: typeof CONNECT.START_ACQUIRING | typeof CONNECT.STOP_ACQUIRING,
} | {
type: typeof CONNECT.REQUEST_WALLET_TYPE,
device: TrezorDevice
} | {
type: typeof CONNECT.RECEIVE_WALLET_TYPE | typeof CONNECT.UPDATE_WALLET_TYPE,
device: TrezorDevice,
hidden: boolean,
};
export type TrezorConnectAction =
| {
type: typeof CONNECT.INITIALIZATION_ERROR,
error: string,
}
| {
type: typeof CONNECT.NETWORK_CHANGED,
payload: {
network: string,
},
}
| {
type: typeof CONNECT.AUTH_DEVICE,
device: TrezorDevice,
state: string,
}
| {
type: typeof CONNECT.DUPLICATE,
device: TrezorDevice,
}
| {
type: typeof CONNECT.REMEMBER_REQUEST,
device: TrezorDevice,
instances: Array<TrezorDevice>,
}
| {
type: typeof CONNECT.DISCONNECT_REQUEST,
device: TrezorDevice,
}
| {
type: typeof CONNECT.FORGET_REQUEST,
device: TrezorDevice,
}
| {
type: typeof CONNECT.FORGET,
device: TrezorDevice,
}
| {
type: typeof CONNECT.FORGET_SINGLE | typeof CONNECT.FORGET_SILENT,
device: TrezorDevice,
}
| {
type: typeof CONNECT.REMEMBER,
device: TrezorDevice,
}
| {
type: typeof CONNECT.TRY_TO_DUPLICATE,
device: TrezorDevice,
}
| {
type: typeof CONNECT.DEVICE_FROM_STORAGE,
payload: Array<TrezorDevice>,
}
| {
type: typeof CONNECT.START_ACQUIRING | typeof CONNECT.STOP_ACQUIRING,
}
| {
type: typeof CONNECT.REQUEST_WALLET_TYPE,
device: TrezorDevice,
}
| {
type: typeof CONNECT.RECEIVE_WALLET_TYPE | typeof CONNECT.UPDATE_WALLET_TYPE,
device: TrezorDevice,
hidden: boolean,
};
declare var LOCAL: ?string;
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const init = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
// set listeners
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
// post event to reducers
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
device: event.payload,
});
});
TrezorConnect.on(
DEVICE_EVENT,
(event: DeviceMessage): void => {
// post event to reducers
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
device: event.payload,
});
}
);
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
// post event to reducers
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
});
TrezorConnect.on(
UI_EVENT,
(event: UiMessage): void => {
// post event to reducers
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
}
);
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
// post event to reducers
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
});
TrezorConnect.on(
TRANSPORT_EVENT,
(event: TransportMessage): void => {
// post event to reducers
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
}
);
// post event to reducers
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainEvent): void => {
dispatch(event);
});
TrezorConnect.on(
BLOCKCHAIN_EVENT,
(event: BlockchainEvent): void => {
dispatch(event);
}
);
if (buildUtils.isDev()) {
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://sisyfos.trezor.io/connect/'; // eslint-disable-line no-underscore-dangle
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://localhost:8088/'; // eslint-disable-line no-underscore-dangle
window.TrezorConnect = TrezorConnect;
}
@ -131,7 +165,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
debug: false,
popup: false,
webusb: true,
pendingTransportEvent: (getState().devices.length < 1),
pendingTransportEvent: getState().devices.length < 1,
manifest: {
email: 'info@trezor.io',
appUrl: urlConstants.NEXT_WALLET,
@ -165,10 +199,18 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch): void => {
}
};
export const requestWalletType = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const requestWalletType = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
const isDeviceReady =
selected.connected &&
selected.features &&
!selected.state &&
selected.mode === 'normal' &&
selected.firmware !== 'required';
if (!isDeviceReady) return;
if (selected.features && selected.features.passphrase_protection) {
@ -186,10 +228,18 @@ export const requestWalletType = (): AsyncAction => async (dispatch: Dispatch, g
}
};
export const authorizeDevice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const authorizeDevice = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
const isDeviceReady =
selected.connected &&
selected.features &&
!selected.state &&
selected.mode === 'normal' &&
selected.firmware !== 'required';
if (!isDeviceReady) return;
const response = await TrezorConnect.getDeviceState({
@ -233,11 +283,24 @@ export const authorizeDevice = (): AsyncAction => async (dispatch: Dispatch, get
}
};
export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const deviceDisconnect = (device: Device): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
if (device.features) {
const instances = getState().devices.filter(d => d.features && device.features && d.state && !d.remember && d.features.device_id === device.features.device_id);
const instances = getState().devices.filter(
d =>
d.features &&
device.features &&
d.state &&
!d.remember &&
d.features.device_id === device.features.device_id
);
if (instances.length > 0) {
const isSelected = deviceUtils.isSelectedDevice(getState().wallet.selectedDevice, device);
const isSelected = deviceUtils.isSelectedDevice(
getState().wallet.selectedDevice,
device
);
if (!isSelected && getState().modal.context !== CONTEXT_NONE) {
dispatch({
type: CONNECT.FORGET_SILENT,
@ -259,8 +322,7 @@ export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch
};
export function reload(): AsyncAction {
return async (): Promise<void> => {
};
return async (): Promise<void> => {};
}
export function acquire(): AsyncAction {
@ -314,7 +376,10 @@ export const forget = (device: TrezorDevice): Action => ({
device,
});
export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const instance: number = getDuplicateInstanceNumber(getState().devices, device);
const extended: Object = { instance };
dispatch({
@ -323,7 +388,9 @@ export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (
});
};
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
dispatch({
type: CONNECT.REQUEST_WALLET_TYPE,
device,

@ -7,27 +7,28 @@ import { toHex } from 'web3-utils'; // eslint-disable-line import/no-extraneous-
import { initWeb3 } from 'actions/Web3Actions';
import * as ethUtils from 'utils/ethUtils';
import type {
Dispatch,
PromiseAction,
} from 'flowtype';
import type { Dispatch, PromiseAction } from 'flowtype';
import type { EthereumTransaction } from 'trezor-connect';
import type { Token } from 'reducers/TokensReducer';
type EthereumTxRequest = {
network: string;
token: ?Token;
from: string;
to: string;
amount: string;
data: string;
gasLimit: string;
gasPrice: string;
nonce: number;
}
network: string,
token: ?Token,
from: string,
to: string,
amount: string,
data: string,
gasLimit: string,
gasPrice: string,
nonce: number,
};
export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<EthereumTransaction> => async (dispatch: Dispatch): Promise<EthereumTransaction> => {
export const prepareEthereumTx = (
tx: EthereumTxRequest
): PromiseAction<EthereumTransaction> => async (
dispatch: Dispatch
): Promise<EthereumTransaction> => {
const instance = await dispatch(initWeb3(tx.network));
const { token } = tx;
let data: string = ethUtils.sanitizeHex(tx.data);
@ -37,7 +38,9 @@ export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<Ethereum
// smart contract transaction
const contract = instance.erc20.clone();
contract.options.address = token.address;
const tokenAmount: string = new BigNumber(tx.amount).times(10 ** token.decimals).toString(10);
const tokenAmount: string = new BigNumber(tx.amount)
.times(10 ** token.decimals)
.toString(10);
data = instance.erc20.methods.transfer(to, tokenAmount).encodeABI();
value = '0x00';
to = token.address;
@ -57,7 +60,9 @@ export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<Ethereum
};
};
export const serializeEthereumTx = (tx: EthereumTransaction): PromiseAction<string> => async (): Promise<string> => {
export const serializeEthereumTx = (
tx: EthereumTransaction
): PromiseAction<string> => async (): Promise<string> => {
const ethTx = new EthereumjsTx(tx);
return `0x${ethTx.serialize().toString('hex')}`;
};
};

@ -19,34 +19,46 @@ import type {
State,
} from 'flowtype';
export type WalletAction = {
type: typeof WALLET.SET_INITIAL_URL,
state?: RouterLocationState,
pathname?: string
} | {
type: typeof WALLET.TOGGLE_DEVICE_DROPDOWN,
opened: boolean
} | {
type: typeof WALLET.ONLINE_STATUS,
online: boolean
} | {
type: typeof WALLET.SET_SELECTED_DEVICE,
device: ?TrezorDevice
} | {
type: typeof WALLET.UPDATE_SELECTED_DEVICE,
device: TrezorDevice
} | {
type: typeof WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
devices: Array<TrezorDevice>
} | {
type: typeof WALLET.SHOW_BETA_DISCLAIMER | typeof WALLET.HIDE_BETA_DISCLAIMER | typeof WALLET.SET_FIRST_LOCATION_CHANGE,
} | {
type: typeof WALLET.TOGGLE_SIDEBAR,
} | {
type: typeof WALLET.SET_LANGUAGE,
locale: string,
messages: { [string]: string },
}
export type WalletAction =
| {
type: typeof WALLET.SET_INITIAL_URL,
state?: RouterLocationState,
pathname?: string,
}
| {
type: typeof WALLET.TOGGLE_DEVICE_DROPDOWN,
opened: boolean,
}
| {
type: typeof WALLET.ONLINE_STATUS,
online: boolean,
}
| {
type: typeof WALLET.SET_SELECTED_DEVICE,
device: ?TrezorDevice,
}
| {
type: typeof WALLET.UPDATE_SELECTED_DEVICE,
device: TrezorDevice,
}
| {
type: typeof WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
devices: Array<TrezorDevice>,
}
| {
type:
| typeof WALLET.SHOW_BETA_DISCLAIMER
| typeof WALLET.HIDE_BETA_DISCLAIMER
| typeof WALLET.SET_FIRST_LOCATION_CHANGE,
}
| {
type: typeof WALLET.TOGGLE_SIDEBAR,
}
| {
type: typeof WALLET.SET_LANGUAGE,
locale: string,
messages: { [string]: string },
};
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
const updateOnlineStatus = () => {
@ -75,7 +87,7 @@ export const toggleSidebar = (): WalletAction => ({
export const fetchLocale = (locale: string): ThunkAction => (dispatch: Dispatch): void => {
fetch(`/l10n/${locale}.json`)
.then(response => response.json())
.then((messages) => {
.then(messages => {
dispatch({
type: WALLET.SET_LANGUAGE,
locale,
@ -89,12 +101,18 @@ export const fetchLocale = (locale: string): ThunkAction => (dispatch: Dispatch)
// all saved instances will be removed immediately inside DevicesReducer
// This method will clear leftovers associated with removed instances from reducers.
// (DiscoveryReducer, AccountReducer, TokensReducer)
export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (dispatch: Dispatch): void => {
export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (
dispatch: Dispatch
): void => {
if (!device.features) return;
const affectedDevices = prevState.devices.filter(d => d.features && device.features
&& d.features.device_id === device.features.device_id
&& d.features.passphrase_protection !== device.features.passphrase_protection);
const affectedDevices = prevState.devices.filter(
d =>
d.features &&
device.features &&
d.features.device_id === device.features.device_id &&
d.features.passphrase_protection !== device.features.passphrase_protection
);
if (affectedDevices.length > 0) {
dispatch({
@ -114,15 +132,21 @@ const actions = [
];
/*
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
// ignore not listed actions
if (actions.indexOf(action.type) < 0) return false;
const state: State = getState();
const locationChanged = reducerUtils.observeChanges(prevState.router.location, state.router.location);
const locationChanged = reducerUtils.observeChanges(
prevState.router.location,
state.router.location
);
const device = reducerUtils.getSelectedDevice(state);
const selectedDeviceChanged = reducerUtils.observeChanges(state.wallet.selectedDevice, device);
@ -142,4 +166,4 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
return true;
}
return false;
};
};

@ -9,12 +9,7 @@ import * as WEB3 from 'actions/constants/web3';
import * as PENDING from 'actions/constants/pendingTx';
import * as ethUtils from 'utils/ethUtils';
import type {
Dispatch,
GetState,
ThunkAction,
PromiseAction,
} from 'flowtype';
import type { Dispatch, GetState, ThunkAction, PromiseAction } from 'flowtype';
import type { EthereumAccount } from 'trezor-connect';
import type { Account } from 'reducers/AccountsReducer';
@ -27,100 +22,114 @@ import * as AccountsActions from './AccountsActions';
export type Web3UpdateBlockAction = {
type: typeof WEB3.BLOCK_UPDATED,
network: string,
blockHash: string
blockHash: string,
};
export type Web3UpdateGasPriceAction = {
type: typeof WEB3.GAS_PRICE_UPDATED,
network: string,
gasPrice: string
gasPrice: string,
};
export type Web3Action = {
type: typeof WEB3.READY,
} | {
type: typeof WEB3.START,
} | {
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
instance: Web3Instance
} | Web3UpdateBlockAction
| Web3UpdateGasPriceAction;
export const initWeb3 = (network: string, urlIndex: number = 0): PromiseAction<Web3Instance> => async (dispatch: Dispatch, getState: GetState): Promise<Web3Instance> => new Promise(async (resolve, reject) => {
// check if requested web was initialized before
const instance = getState().web3.find(w3 => w3.network === network);
if (instance && instance.web3.currentProvider.connected) {
resolve(instance);
return;
}
// requested web3 wasn't initialized or is disconnected
// initialize again
const { config, ERC20Abi } = getState().localStorage;
const networkData = config.networks.find(c => c.shortcut === network);
if (!networkData) {
// network not found
reject(new Error(`Network ${network} not found in application config.`));
return;
}
export type Web3Action =
| {
type: typeof WEB3.READY,
}
| {
type: typeof WEB3.START,
}
| {
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
instance: Web3Instance,
}
| Web3UpdateBlockAction
| Web3UpdateGasPriceAction;
export const initWeb3 = (
network: string,
urlIndex: number = 0
): PromiseAction<Web3Instance> => async (
dispatch: Dispatch,
getState: GetState
): Promise<Web3Instance> =>
new Promise(async (resolve, reject) => {
// check if requested web was initialized before
const instance = getState().web3.find(w3 => w3.network === network);
if (instance && instance.web3.currentProvider.connected) {
resolve(instance);
return;
}
// get first url
const url = networkData.web3[urlIndex];
if (!url) {
reject(new Error('Web3 backend is not responding'));
return;
}
// requested web3 wasn't initialized or is disconnected
// initialize again
const { config, ERC20Abi } = getState().localStorage;
const networkData = config.networks.find(c => c.shortcut === network);
if (!networkData) {
// network not found
reject(new Error(`Network ${network} not found in application config.`));
return;
}
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
// get first url
const url = networkData.web3[urlIndex];
if (!url) {
reject(new Error('Web3 backend is not responding'));
return;
}
const onConnect = async () => {
const latestBlock = await web3.eth.getBlockNumber();
const gasPrice = await web3.eth.getGasPrice();
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
const newInstance = {
network,
web3,
chainId: networkData.chainId,
erc20: new web3.eth.Contract(ERC20Abi),
latestBlock,
gasPrice,
};
const onConnect = async () => {
const latestBlock = await web3.eth.getBlockNumber();
const gasPrice = await web3.eth.getGasPrice();
dispatch({
type: WEB3.CREATE,
instance: newInstance,
});
const newInstance = {
network,
web3,
chainId: networkData.chainId,
erc20: new web3.eth.Contract(ERC20Abi),
latestBlock,
gasPrice,
};
resolve(newInstance);
};
dispatch({
type: WEB3.CREATE,
instance: newInstance,
});
const onEnd = async () => {
web3.currentProvider.reset();
const oldInstance = getState().web3.find(w3 => w3.network === network);
resolve(newInstance);
};
if (oldInstance && oldInstance.web3.currentProvider.connected) {
// backend disconnects
// dispatch({
// type: 'WEB3.DISCONNECT',
// network
// });
} else {
// backend initialization error for given url, try next one
try {
const otherWeb3 = await dispatch(initWeb3(network, urlIndex + 1));
resolve(otherWeb3);
} catch (error) {
reject(error);
const onEnd = async () => {
web3.currentProvider.reset();
const oldInstance = getState().web3.find(w3 => w3.network === network);
if (oldInstance && oldInstance.web3.currentProvider.connected) {
// backend disconnects
// dispatch({
// type: 'WEB3.DISCONNECT',
// network
// });
} else {
// backend initialization error for given url, try next one
try {
const otherWeb3 = await dispatch(initWeb3(network, urlIndex + 1));
resolve(otherWeb3);
} catch (error) {
reject(error);
}
}
}
};
};
web3.currentProvider.on('connect', onConnect);
web3.currentProvider.on('end', onEnd);
web3.currentProvider.on('error', onEnd);
});
web3.currentProvider.on('connect', onConnect);
web3.currentProvider.on('end', onEnd);
web3.currentProvider.on('error', onEnd);
});
export const discoverAccount = (descriptor: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
export const discoverAccount = (
descriptor: string,
network: string
): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const balance = await instance.web3.eth.getBalance(descriptor);
const nonce = await instance.web3.eth.getTransactionCount(descriptor);
@ -134,10 +143,13 @@ export const discoverAccount = (descriptor: string, network: string): PromiseAct
};
};
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const pending = getState().pending.filter(p => p.network === network);
pending.forEach(async (tx) => {
pending.forEach(async tx => {
const status = await instance.web3.eth.getTransaction(tx.hash);
if (!status) {
dispatch({
@ -192,39 +204,49 @@ export const getTxInput = (): PromiseAction<void> => async (dispatch: Dispatch):
};
*/
export const updateAccount = (account: Account, newAccount: EthereumAccount, network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
export const updateAccount = (
account: Account,
newAccount: EthereumAccount,
network: string
): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const balance = await instance.web3.eth.getBalance(account.descriptor);
const nonce = await instance.web3.eth.getTransactionCount(account.descriptor);
dispatch(AccountsActions.update({
networkType: 'ethereum',
...account,
...newAccount,
nonce,
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
}));
dispatch(
AccountsActions.update({
networkType: 'ethereum',
...account,
...newAccount,
nonce,
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
})
);
// update tokens for this account
dispatch(updateAccountTokens(account));
};
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const tokens = getState().tokens.filter(t => t.network === account.network && t.ethAddress === account.descriptor);
tokens.forEach(async (token) => {
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const tokens = getState().tokens.filter(
t => t.network === account.network && t.ethAddress === account.descriptor
);
tokens.forEach(async token => {
const balance = await dispatch(getTokenBalance(token));
// const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
if (balance !== token.balance) {
dispatch(TokenActions.setBalance(
token.address,
token.ethAddress,
balance,
));
dispatch(TokenActions.setBalance(token.address, token.ethAddress, balance));
}
});
};
export const getTokenInfo = (address: string, network: string): PromiseAction<NetworkToken> => async (dispatch: Dispatch): Promise<NetworkToken> => {
export const getTokenInfo = (
address: string,
network: string
): PromiseAction<NetworkToken> => async (dispatch: Dispatch): Promise<NetworkToken> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const contract = instance.erc20.clone();
contract.options.address = address;
@ -241,7 +263,9 @@ export const getTokenInfo = (address: string, network: string): PromiseAction<Ne
};
};
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
export const getTokenBalance = (token: Token): PromiseAction<string> => async (
dispatch: Dispatch
): Promise<string> => {
const instance = await dispatch(initWeb3(token.network));
const contract = instance.erc20.clone();
contract.options.address = token.address;
@ -250,7 +274,10 @@ export const getTokenBalance = (token: Token): PromiseAction<string> => async (d
return new BigNumber(balance).dividedBy(10 ** token.decimals).toString(10);
};
export const getCurrentGasPrice = (network: string): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
export const getCurrentGasPrice = (network: string): PromiseAction<string> => async (
dispatch: Dispatch,
getState: GetState
): Promise<string> => {
const instance = getState().web3.find(w3 => w3.network === network);
if (instance) {
return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei');
@ -258,7 +285,9 @@ export const getCurrentGasPrice = (network: string): PromiseAction<string> => as
return '0';
};
export const updateGasPrice = (network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
export const updateGasPrice = (network: string): PromiseAction<void> => async (
dispatch: Dispatch
): Promise<void> => {
try {
const instance = await dispatch(initWeb3(network));
const gasPrice = await instance.web3.eth.getGasPrice();
@ -275,23 +304,32 @@ export const updateGasPrice = (network: string): PromiseAction<void> => async (d
}
};
export const estimateGasLimit = (network: string, $options: EstimateGasOptions): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
export const estimateGasLimit = (
network: string,
$options: EstimateGasOptions
): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
const instance = await dispatch(initWeb3(network));
const data = ethUtils.sanitizeHex($options.data);
const options = {
...$options,
to: '0x0000000000000000000000000000000000000000',
data,
value: instance.web3.utils.toHex(EthereumjsUnits.convert($options.value || '0', 'ether', 'wei')),
gasPrice: instance.web3.utils.toHex(EthereumjsUnits.convert($options.gasPrice, 'gwei', 'wei')),
value: instance.web3.utils.toHex(
EthereumjsUnits.convert($options.value || '0', 'ether', 'wei')
),
gasPrice: instance.web3.utils.toHex(
EthereumjsUnits.convert($options.gasPrice, 'gwei', 'wei')
),
};
const limit = await instance.web3.eth.estimateGas(options);
return limit.toString();
};
export const disconnect = (network: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const disconnect = (network: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
// check if Web3 was already initialized
const instance = getState().web3.find(w3 => w3.network === network);
if (instance) {

@ -3,7 +3,6 @@ import styled, { css } from 'styled-components';
import PropTypes from 'prop-types';
import { FADE_IN } from 'config/animations';
const StyledBackdrop = styled.div`
width: 100%;
height: 100%;
@ -11,21 +10,17 @@ const StyledBackdrop = styled.div`
z-index: 100;
left: 0;
top: 0;
background-color: rgba(0,0,0,0.5);
background-color: rgba(0, 0, 0, 0.5);
${props => props.animated && css`
animation: ${FADE_IN} 0.3s;
`};
${props =>
props.animated &&
css`
animation: ${FADE_IN} 0.3s;
`};
`;
const Backdrop = ({
className,
show,
animated,
onClick,
}) => (
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null
);
const Backdrop = ({ className, show, animated, onClick }) =>
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null;
Backdrop.propTypes = {
show: PropTypes.bool,

@ -17,8 +17,8 @@ type Props = {
isWhite?: boolean,
isWebUsb?: boolean,
isTransparent?: boolean,
dataTest?: string
}
dataTest?: string,
};
const Wrapper = styled.button`
padding: ${props => (props.icon ? '4px 24px 4px 15px' : '11px 24px')};
@ -43,97 +43,105 @@ const Wrapper = styled.button`
box-shadow: 0 0px 6px 0 ${colors.INPUT_FOCUSED_SHADOW};
}
${props => props.isDisabled && css`
pointer-events: none;
color: ${colors.TEXT_SECONDARY};
background: ${colors.GRAY_LIGHT};
`}
${props => props.isWhite && css`
background: ${colors.WHITE};
color: ${colors.TEXT_SECONDARY};
border: 1px solid ${colors.DIVIDER};
&:focus {
border-color: ${colors.INPUT_FOCUSED_BORDER};
}
&:hover {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
&:active {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
`}
${props => props.isTransparent && css`
background: transparent;
border: 0px;
color: ${colors.TEXT_SECONDARY};
&:focus {
color: ${colors.TEXT_PRIMARY};
box-shadow: none;
}
&:hover,
&:active {
color: ${colors.TEXT_PRIMARY};
${props =>
props.isDisabled &&
css`
pointer-events: none;
color: ${colors.TEXT_SECONDARY};
background: ${colors.GRAY_LIGHT};
`}
${props =>
props.isWhite &&
css`
background: ${colors.WHITE};
color: ${colors.TEXT_SECONDARY};
border: 1px solid ${colors.DIVIDER};
&:focus {
border-color: ${colors.INPUT_FOCUSED_BORDER};
}
&:hover {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
&:active {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
`}
${props =>
props.isTransparent &&
css`
background: transparent;
}
`}
${props => props.isWebUsb && css`
position: relative;
padding: 12px 24px 12px 40px;
background: transparent;
color: ${colors.GREEN_PRIMARY};
border: 1px solid ${colors.GREEN_PRIMARY};
transition: ${TRANSITION.HOVER};
&:before,
&:after {
content: '';
position: absolute;
background: ${colors.GREEN_PRIMARY};
top: 0;
bottom: 0;
margin: auto;
transition: ${TRANSITION.HOVER};
}
border: 0px;
color: ${colors.TEXT_SECONDARY};
&:before {
width: 12px;
height: 2px;
left: 18px;
}
&:focus {
color: ${colors.TEXT_PRIMARY};
box-shadow: none;
}
&:after {
width: 2px;
height: 12px;
left: 23px;
}
&:hover,
&:active {
color: ${colors.TEXT_PRIMARY};
background: transparent;
}
`}
&:hover {
background: ${colors.GREEN_PRIMARY};
color: ${colors.WHITE};
${props =>
props.isWebUsb &&
css`
position: relative;
padding: 12px 24px 12px 40px;
background: transparent;
color: ${colors.GREEN_PRIMARY};
border: 1px solid ${colors.GREEN_PRIMARY};
transition: ${TRANSITION.HOVER};
&:before,
&:after {
background: ${colors.WHITE};
content: '';
position: absolute;
background: ${colors.GREEN_PRIMARY};
top: 0;
bottom: 0;
margin: auto;
transition: ${TRANSITION.HOVER};
}
&:before {
width: 12px;
height: 2px;
left: 18px;
}
&:after {
width: 2px;
height: 12px;
left: 23px;
}
&:hover {
background: ${colors.GREEN_PRIMARY};
color: ${colors.WHITE};
&:before,
&:after {
background: ${colors.WHITE};
}
}
iframe {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
}
iframe {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
`}
`}
`;
const Button = ({
@ -182,4 +190,4 @@ Button.propTypes = {
dataTest: PropTypes.string,
};
export default Button;
export default Button;

@ -12,7 +12,7 @@ type Props = {
onClick: (event: KeyboardEvent) => void,
isChecked: boolean,
children: React.Node,
}
};
const Wrapper = styled.div`
display: flex;
@ -26,8 +26,7 @@ const Wrapper = styled.div`
}
`;
const Tick = styled.div`
`;
const Tick = styled.div``;
const IconWrapper = styled.div`
display: flex;
@ -42,9 +41,11 @@ const IconWrapper = styled.div`
&:hover,
&:focus {
${props => !props.isChecked && css`
border: 1px solid ${colors.GREEN_PRIMARY};
`}
${props =>
!props.isChecked &&
css`
border: 1px solid ${colors.GREEN_PRIMARY};
`}
background: ${props => (props.isChecked ? colors.GREEN_PRIMARY : colors.WHITE)};
}
`;
@ -70,17 +71,9 @@ class Checkbox extends React.PureComponent<Props> {
}
render() {
const {
isChecked,
children,
onClick,
} = this.props;
const { isChecked, children, onClick } = this.props;
return (
<Wrapper
onClick={onClick}
onKeyUp={event => this.handleKeyboard(event)}
tabIndex={0}
>
<Wrapper onClick={onClick} onKeyUp={event => this.handleKeyboard(event)} tabIndex={0}>
<IconWrapper isChecked={isChecked}>
{isChecked && (
<Tick>
@ -91,8 +84,7 @@ class Checkbox extends React.PureComponent<Props> {
icon={icons.SUCCESS}
/>
</Tick>
)
}
)}
</IconWrapper>
<Label isChecked={isChecked}>{children}</Label>
</Wrapper>

@ -2,12 +2,7 @@
import React from 'react';
import styled, { css } from 'styled-components';
import PropTypes from 'prop-types';
import {
getStatusColor,
getStatusName,
getStatus,
getVersion,
} from 'utils/device';
import { getStatusColor, getStatusName, getStatus, getVersion } from 'utils/device';
import TrezorImage from 'components/images/TrezorImage';
import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
@ -27,19 +22,26 @@ const Wrapper = styled.div`
border-radius: 4px 0 0 0;
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
${props => (props.isOpen || !props.isSelected) && css`
box-shadow: none;
`}
${props =>
(props.isOpen || !props.isSelected) &&
css`
box-shadow: none;
`}
${props => props.disabled && css`
cursor: default;
`}
${props =>
props.disabled &&
css`
cursor: default;
`}
${props => props.isHoverable && !props.disabled && css`
&:hover {
background: ${colors.GRAY_LIGHT};
}
`}
${props =>
props.isHoverable &&
!props.disabled &&
css`
&:hover {
background: ${colors.GRAY_LIGHT};
}
`}
`;
const LabelWrapper = styled.div`
@ -88,7 +90,6 @@ const Dot = styled.div`
height: 10px;
`;
const DeviceHeader = ({
isOpen,
icon,
@ -120,9 +121,7 @@ const DeviceHeader = ({
<Name>{device.instanceLabel}</Name>
<Status title={getStatusName(status)}>{getStatusName(status)}</Status>
</LabelWrapper>
<IconWrapper>
{icon && !disabled && isAccessible && icon}
</IconWrapper>
<IconWrapper>{icon && !disabled && isAccessible && icon}</IconWrapper>
</Wrapper>
);
};

@ -9,7 +9,6 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import colors from 'config/colors';
import { FONT_SIZE } from 'config/variables';
import * as LogActions from 'actions/LogActions';
@ -19,7 +18,7 @@ type Props = {
opened: boolean,
isLanding: boolean,
toggle: () => any,
}
};
const Wrapper = styled.div`
width: 100%;
@ -62,17 +61,27 @@ const Footer = ({ opened, toggle, isLanding }: Props) => (
<Wrapper>
<Left>
<Copy>&copy; {getYear(new Date())}</Copy>
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink>
<StyledLink href="http://satoshilabs.com" isGreen>
SatoshiLabs
</StyledLink>
<StyledLink href="https://trezor.io/static/pdf/tos.pdf" isGreen>
<FormattedMessage {...l10nMessages.TR_TERMS} />
</StyledLink>
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink>
<StyledLink onClick={toggle} isGreen>
{opened ? 'Hide Log' : 'Show Log'}
</StyledLink>
</Left>
{!isLanding && (
<Right>
<FormattedMessage
{...l10nMessages.TR_EXCHANGE_RATES_BY}
values={{ service: <Link href="https://www.coingecko.com" isGreen>Coingecko</Link> }}
values={{
service: (
<Link href="https://www.coingecko.com" isGreen>
Coingecko
</Link>
),
}}
/>
</Right>
)}
@ -93,4 +102,7 @@ const mapDispatchToProps = dispatch => ({
toggle: bindActionCreators(LogActions.toggle, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Footer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Footer);

@ -14,4 +14,4 @@ const definedMessages: Messages = defineMessages({
},
});
export default definedMessages;
export default definedMessages;

@ -11,26 +11,31 @@ import LanguagePicker from './index';
type StateProps = {
language: string,
}
};
type DispatchProps = {
fetchLocale: typeof WalletActions.fetchLocale,
};
type OwnProps = {
}
type OwnProps = {};
export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (
state: State
): StateProps => ({
language: state.wallet.language,
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (
dispatch: Dispatch
): DispatchProps => ({
fetchLocale: bindActionCreators(WalletActions.fetchLocale, dispatch),
});
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(LanguagePicker),
connect(
mapStateToProps,
mapDispatchToProps
)(LanguagePicker)
);

@ -81,8 +81,7 @@ const styles = {
}),
};
const buildOption = (langCode) => {
const buildOption = langCode => {
const lang = LANGUAGE.find(l => l.code === langCode);
return { value: lang.code, label: lang.name };
};
@ -100,9 +99,7 @@ const LanguagePicker = ({ language, fetchLocale }: Props) => (
isClearable={false}
onChange={option => fetchLocale(option.value)}
value={buildOption(language)}
options={
LANGUAGE.map(lang => buildOption(lang.code))
}
options={LANGUAGE.map(lang => buildOption(lang.code))}
/>
</SelectWrapper>
);

@ -53,16 +53,14 @@ const MenuToggler = styled.div`
cursor: pointer;
user-select: none;
padding: 10px 0px;
transition: all .1s ease-in;
transition: all 0.1s ease-in;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: flex;
}
`;
const TogglerText = styled.div`
`;
const TogglerText = styled.div``;
const TREZOR = styled.div``;
const T = styled.div``;
@ -75,11 +73,11 @@ const Logo = styled.div`
${T} {
display: none;
width: 20px;
}
}
${TREZOR} {
width: 100px;
}
}
svg {
fill: ${colors.WHITE};
@ -95,11 +93,11 @@ const Logo = styled.div`
/* hides full width trezor logo, shows only trezor icon */
${TREZOR} {
display: none;
}
}
${T} {
display: inherit;
}
}
}
`;
@ -131,7 +129,7 @@ const Projects = styled.div`
const A = styled.a`
color: ${colors.WHITE};
margin-left: 24px;
transition: all .1s ease-in;
transition: all 0.1s ease-in;
white-space: nowrap;
&:visited {
@ -153,33 +151,24 @@ type Props = {
sidebarEnabled?: boolean,
sidebarOpened?: ?boolean,
toggleSidebar?: toggleSidebarType,
};
const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
<Wrapper data-test="Main__page__navigation">
<LayoutWrapper>
<Left>
{ sidebarEnabled && (
{sidebarEnabled && (
<MenuToggler onClick={toggleSidebar}>
{sidebarOpened ? (
<>
<Icon
size={24}
color={colors.WHITE}
icon={icons.CLOSE}
/>
<Icon size={24} color={colors.WHITE} icon={icons.CLOSE} />
<TogglerText>
<FormattedMessage {...l10nMessages.TR_MENU_CLOSE} />
</TogglerText>
</>
) : (
<>
<Icon
color={colors.WHITE}
size={24}
icon={icons.MENU}
/>
<Icon color={colors.WHITE} size={24} icon={icons.MENU} />
<TogglerText>
<FormattedMessage {...l10nMessages.TR_MENU} />
</TogglerText>
@ -191,7 +180,15 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
<Logo>
<NavLink to="/">
<TREZOR>
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 163.7 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet">
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 163.7 41.9"
width="100%"
height="100%"
preserveAspectRatio="xMinYMin meet"
>
<polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1" />
<path d="M158.8,26.9c2.1-0.8,4.3-2.9,4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1,7.5h6.7L158.8,26.9z M154.7,22.5 h-4V18h4c1.5,0,2.5,0.9,2.5,2.2C157.2,21.6,156.2,22.5,154.7,22.5z" />
<path d="M130.8,12.5c-6.8,0-11.6,4.9-11.6,11.5s4.9,11.5,11.6,11.5s11.7-4.9,11.7-11.5S137.6,12.5,130.8,12.5z M130.8,30.3 c-3.4,0-5.7-2.6-5.7-6.3c0-3.8,2.3-6.3,5.7-6.3c3.4,0,5.8,2.6,5.8,6.3C136.6,27.7,134.2,30.3,130.8,30.3z" />
@ -202,7 +199,15 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
</svg>
</TREZOR>
<T>
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 20 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet">
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 20 41.9"
width="100%"
height="100%"
preserveAspectRatio="xMinYMin meet"
>
<path d="M24.6,9.7C24.6,4.4,20,0,14.4,0S4.2,4.4,4.2,9.7v3.1H0v22.3h0l14.4,6.7l14.4-6.7h0V12.9h-4.2V9.7z M9.4,9.7 c0-2.5,2.2-4.5,5-4.5s5,2,5,4.5v3.1H9.4V9.7z M23,31.5l-8.6,4l-8.6-4V18.1H23V31.5z" />
</svg>
</T>
@ -210,10 +215,18 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
</Logo>
<MenuLinks>
<Projects>
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_TREZOR} /></A>
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_WIKI} /></A>
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_BLOG} /></A>
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_SUPPORT} /></A>
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_TREZOR} />
</A>
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_WIKI} />
</A>
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_BLOG} />
</A>
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_SUPPORT} />
</A>
</Projects>
<LanguagePicker />
</MenuLinks>
@ -221,4 +234,4 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
</Wrapper>
);
export default Header;
export default Header;

@ -35,4 +35,4 @@ const definedMessages: Messages = defineMessages({
},
});
export default definedMessages;
export default definedMessages;

@ -22,8 +22,9 @@ const H2 = styled.h2`
font-size: ${FONT_SIZE.H2};
padding-bottom: 10px;
${props => props.claim
&& css`
${props =>
props.claim &&
css`
font-size: ${FONT_SIZE.HUGE};
padding-bottom: 24px;
`};
@ -41,6 +42,4 @@ const H4 = styled.h4`
padding-bottom: 10px;
`;
export {
H1, H2, H3, H4,
};
export { H1, H2, H3, H4 };

@ -17,7 +17,7 @@ type Props = {
onMouseLeave?: () => void,
onFocus?: () => void,
onClick?: () => void,
}
};
const chooseIconAnimationType = (canAnimate, isActive) => {
if (canAnimate) {
@ -49,11 +49,12 @@ const rotate180down = keyframes`
`;
const SvgWrapper = styled.svg`
animation: ${props => chooseIconAnimationType(props.canAnimate, props.isActive)} 0.2s linear 1 forwards;
animation: ${props => chooseIconAnimationType(props.canAnimate, props.isActive)} 0.2s linear 1
forwards;
:hover {
path {
fill: ${props => props.hoverColor}
fill: ${props => props.hoverColor};
}
}
`;
@ -93,12 +94,7 @@ const Icon = ({
onClick={onClick}
>
{icon.map(path => (
<Path
key={path}
isActive={isActive}
color={color}
d={path}
/>
<Path key={path} isActive={isActive} color={color} d={path} />
))}
</SvgWrapper>
);
@ -117,4 +113,4 @@ Icon.propTypes = {
onClick: PropTypes.func,
};
export default Icon;
export default Icon;

@ -11,25 +11,33 @@ const A = styled.a`
transition: ${TRANSITION.HOVER};
font-size: ${FONT_SIZE.SMALL};
${props => props.isGreen && css`
text-decoration: underline;
text-decoration-color: ${colors.GREEN_PRIMARY};
`}
${props => props.isGray && css`
text-decoration: underline;
text-decoration-color: ${colors.TEXT_SECONDARY};
`}
${props =>
props.isGreen &&
css`
text-decoration: underline;
text-decoration-color: ${colors.GREEN_PRIMARY};
`}
${props =>
props.isGray &&
css`
text-decoration: underline;
text-decoration-color: ${colors.TEXT_SECONDARY};
`}
&,
&:visited,
&:active,
&:hover {
${props => props.isGreen && css`
color: ${colors.GREEN_PRIMARY};
`}
${props => props.isGray && css`
color: ${colors.TEXT_SECONDARY};
`}
${props =>
props.isGreen &&
css`
color: ${colors.GREEN_PRIMARY};
`}
${props =>
props.isGray &&
css`
color: ${colors.TEXT_SECONDARY};
`}
}
&:hover {
@ -38,13 +46,17 @@ const A = styled.a`
`;
const StyledNavLink = styled(NavLink)`
${props => props.isGreen && css`
color: ${colors.GREEN_PRIMARY};
`}
${props =>
props.isGreen &&
css`
color: ${colors.GREEN_PRIMARY};
`}
${props => props.isGray && css`
color: ${colors.TEXT_SECONDARY};
`}
${props =>
props.isGray &&
css`
color: ${colors.TEXT_SECONDARY};
`}
`;
class Link extends PureComponent {
@ -52,8 +64,7 @@ class Link extends PureComponent {
const shouldRenderRouterLink = this.props.to;
let LinkComponent;
if (shouldRenderRouterLink) {
LinkComponent = (
<StyledNavLink {...this.props}>{this.props.children}</StyledNavLink>);
LinkComponent = <StyledNavLink {...this.props}>{this.props.children}</StyledNavLink>;
} else {
LinkComponent = (
<A
@ -61,7 +72,8 @@ class Link extends PureComponent {
target={this.props.target || '_blank'}
rel="noreferrer noopener"
{...this.props}
>{this.props.children}
>
{this.props.children}
</A>
);
}
@ -86,4 +98,4 @@ Link.propTypes = {
isGray: PropTypes.bool,
};
export default Link;
export default Link;

@ -24,17 +24,22 @@ const SvgWrapper = styled.svg`
`;
const CircleWrapper = styled.circle`
${props => props.isRoute && css`
stroke: ${props.transparentRoute ? 'transparent' : colors.GRAY_LIGHT};
`}
${props =>
props.isRoute &&
css`
stroke: ${props.transparentRoute ? 'transparent' : colors.GRAY_LIGHT};
`}
${props => props.isPath && css`
stroke-width: ${props.transparentRoute ? '2px' : '1px'};
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: ${DASH} 1.5s ease-in-out infinite, ${props.animationColor || GREEN_COLOR} 6s ease-in-out infinite;
stroke-linecap: round;
`};
${props =>
props.isPath &&
css`
stroke-width: ${props.transparentRoute ? '2px' : '1px'};
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: ${DASH} 1.5s ease-in-out infinite,
${props.animationColor || GREEN_COLOR} 6s ease-in-out infinite;
stroke-linecap: round;
`};
`;
const StyledParagraph = styled(Paragraph)`
@ -43,10 +48,18 @@ const StyledParagraph = styled(Paragraph)`
`;
const Loader = ({
className, text, isWhiteText = false, isSmallText, size = 100, animationColor, transparentRoute,
className,
text,
isWhiteText = false,
isSmallText,
size = 100,
animationColor,
transparentRoute,
}) => (
<Wrapper className={className} size={size}>
<StyledParagraph isSmallText={isSmallText} isWhiteText={isWhiteText}>{text}</StyledParagraph>
<StyledParagraph isSmallText={isSmallText} isWhiteText={isWhiteText}>
{text}
</StyledParagraph>
<SvgWrapper viewBox="25 25 50 50">
<CircleWrapper
animationColor={animationColor}

@ -10,7 +10,6 @@ import Icon from 'components/Icon';
import P from 'components/Paragraph';
import { FormattedMessage } from 'react-intl';
import * as LogActions from 'actions/LogActions';
import icons from 'config/icons';
import type { State, Dispatch } from 'flowtype';
@ -18,8 +17,8 @@ import l10nMessages from './index.messages';
type Props = {
log: $ElementType<State, 'log'>,
toggle: typeof LogActions.toggle
}
toggle: typeof LogActions.toggle,
};
const Wrapper = styled.div`
position: relative;
@ -86,5 +85,5 @@ export default connect(
}),
(dispatch: Dispatch) => ({
toggle: bindActionCreators(LogActions.toggle, dispatch),
}),
)(Log);
})
)(Log);

@ -5,7 +5,8 @@ import type { Messages } from 'flowtype/npm/react-intl';
const definedMessages: Messages = defineMessages({
TR_ATTENTION_COLON_THE_LOG_CONTAINS: {
id: 'TR_ATTENTION_COLON_THE_LOG_CONTAINS',
defaultMessage: 'Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.',
defaultMessage:
'Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.',
},
TR_LOG: {
id: 'TR_LOG',
@ -14,4 +15,4 @@ const definedMessages: Messages = defineMessages({
},
});
export default definedMessages;
export default definedMessages;

@ -42,11 +42,7 @@ class CoinLogo extends PureComponent {
return logo;
}
return (
<Wrapper className={className}>
{logo}
</Wrapper>
);
return <Wrapper className={className}>{logo}</Wrapper>;
}
}

@ -13,12 +13,12 @@ type Props = {
color?: string,
hoverColor?: string,
onClick?: any,
}
};
const SvgWrapper = styled.svg`
:hover {
path {
fill: ${props => props.hoverColor}
fill: ${props => props.hoverColor};
}
}
`;
@ -54,11 +54,7 @@ const DeviceIcon = ({
viewBox="0 0 1024 1024"
onClick={onClick}
>
<Path
key={majorVersion}
color={color}
d={getDeviceIcon(majorVersion)}
/>
<Path key={majorVersion} color={color} d={getDeviceIcon(majorVersion)} />
</SvgWrapper>
);
};
@ -71,4 +67,4 @@ DeviceIcon.propTypes = {
onClick: PropTypes.func,
};
export default DeviceIcon;
export default DeviceIcon;

@ -5,8 +5,8 @@ import styled from 'styled-components';
import PropTypes from 'prop-types';
type Props = {
model: string;
}
model: string,
};
const Wrapper = styled.div``;

@ -7,7 +7,7 @@ import ICONS from 'config/icons';
const SvgWrapper = styled.svg`
:hover {
path {
fill: ${props => props.hoverColor}
fill: ${props => props.hoverColor};
}
}
`;
@ -46,4 +46,4 @@ Icon.propTypes = {
onClick: PropTypes.func,
};
export default Icon;
export default Icon;

@ -4,13 +4,7 @@ import styled, { css } from 'styled-components';
import colors from 'config/colors';
import ICONS from 'config/icons';
import Icon from 'components/Icon';
import {
FONT_SIZE,
FONT_FAMILY,
FONT_WEIGHT,
LINE_HEIGHT,
TRANSITION,
} from 'config/variables';
import { FONT_SIZE, FONT_FAMILY, FONT_WEIGHT, LINE_HEIGHT, TRANSITION } from 'config/variables';
const Wrapper = styled.div`
width: 100%;
@ -48,10 +42,12 @@ const StyledInput = styled.input`
border-radius: 2px;
${props => props.hasAddon && css`
border-top-right-radius: 0;
border-bottom-right-radius: 0;
`}
${props =>
props.hasAddon &&
css`
border-top-right-radius: 0;
border-bottom-right-radius: 0;
`}
border: 1px solid ${colors.DIVIDER};
border-color: ${props => props.borderColor};
@ -75,14 +71,16 @@ const StyledInput = styled.input`
color: ${colors.TEXT_SECONDARY};
}
${props => props.trezorAction && css`
z-index: 10001;
position: relative; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
`};
${props =>
props.trezorAction &&
css`
z-index: 10001;
position: relative; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
`};
`;
const StyledIcon = styled(Icon)`
@ -99,18 +97,21 @@ const BottomText = styled.span`
`;
const Overlay = styled.div`
${props => props.isPartiallyHidden && css`
bottom: 0;
border: 1px solid ${colors.DIVIDER};
border-radius: 2px;
position: absolute;
width: 100%;
height: 100%;
background-image: linear-gradient(to right,
rgba(0,0,0, 0) 0%,
rgba(249,249,249, 1) 220px
);
`}
${props =>
props.isPartiallyHidden &&
css`
bottom: 0;
border: 1px solid ${colors.DIVIDER};
border-radius: 2px;
position: absolute;
width: 100%;
height: 100%;
background-image: linear-gradient(
to right,
rgba(0, 0, 0, 0) 0%,
rgba(249, 249, 249, 1) 220px
);
`}
`;
const TrezorAction = styled.div`
@ -168,12 +169,8 @@ class Input extends PureComponent {
render() {
return (
<Wrapper
className={this.props.className}
>
{this.props.topLabel && (
<TopLabel>{this.props.topLabel}</TopLabel>
)}
<Wrapper className={this.props.className}>
{this.props.topLabel && <TopLabel>{this.props.topLabel}</TopLabel>}
<InputWrapper>
<InputIconWrapper>
{this.props.state && (
@ -208,15 +205,14 @@ class Input extends PureComponent {
data-lpignore="true"
/>
<TrezorAction action={this.props.trezorAction}>
<ArrowUp />{this.props.trezorAction}
<ArrowUp />
{this.props.trezorAction}
</TrezorAction>
</InputIconWrapper>
{this.props.sideAddons && this.props.sideAddons.map(sideAddon => sideAddon)}
</InputWrapper>
{this.props.bottomText && (
<BottomText
color={this.getColor(this.props.state)}
>
<BottomText color={this.getColor(this.props.state)}>
{this.props.bottomText}
</BottomText>
)}

@ -98,4 +98,4 @@ export const SLIDE_LEFT = keyframes`
100% {
transform: translateX(-100%);
}
`;
`;

@ -38,4 +38,4 @@ export default {
INPUT_FOCUSED_BORDER: '#A9A9A9',
INPUT_FOCUSED_SHADOW: '#d6d7d7',
};
};

@ -396,4 +396,4 @@ export default {
]
}
*/
*/

@ -49,7 +49,8 @@ export const FONT_WEIGHT = {
};
export const FONT_FAMILY = {
DEFAULT: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif',
DEFAULT:
'-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif',
MONOSPACE: '"Roboto Mono", Menlo, Monaco, Consolas, "Courier New", monospace',
};
@ -70,7 +71,9 @@ const TRANSITION_TIME = {
};
export const TRANSITION = {
HOVER: `background-color ${TRANSITION_TIME.BASE} ease-in-out, color ${TRANSITION_TIME.BASE} ease-in-out, border-color ${TRANSITION_TIME.BASE} ease-in-out`,
HOVER: `background-color ${TRANSITION_TIME.BASE} ease-in-out, color ${
TRANSITION_TIME.BASE
} ease-in-out, border-color ${TRANSITION_TIME.BASE} ease-in-out`,
};
export const LINE_HEIGHT = {
@ -97,4 +100,4 @@ export const LANGUAGE = [
{ code: 'uk', name: 'Український', en: 'Ukrainian' },
{ code: 'zh', name: '中文(简体)', en: 'Chinese Simplified' },
{ code: 'zh_TW', name: '中文(台灣)', en: 'Chinese Traditional' },
];
];

@ -67,4 +67,4 @@ export default [
url: 'https://adalite.io/app',
external: true,
},
];
];

@ -5,4 +5,4 @@ export default {
info: 2,
success: 3,
},
};
};

@ -2,4 +2,4 @@ export default {
NEXT_WALLET: 'https://beta-wallet.trezor.io/next',
OLD_WALLET: 'https://wallet.trezor.io',
OLD_WALLET_BETA: 'https://beta-wallet.trezor.io',
};
};

@ -1,6 +1,5 @@
/* @flow */
declare module CSSModule {
declare var exports: { [key: string]: string };
}
}

@ -62,14 +62,13 @@ export type AcquiredDevice = $Exact<{
state: ?string,
useEmptyPassphrase: boolean,
remember: boolean; // device should be remembered
connected: boolean; // device is connected
available: boolean; // device cannot be used because of features.passphrase_protection is different then expected
instance?: number;
instanceLabel: string;
instanceName: ?string;
ts: number;
remember: boolean, // device should be remembered
connected: boolean, // device is connected
available: boolean, // device cannot be used because of features.passphrase_protection is different then expected
instance?: number,
instanceLabel: string,
instanceName: ?string,
ts: number,
}>;
export type UnknownDevice = $Exact<{
@ -80,13 +79,13 @@ export type UnknownDevice = $Exact<{
state: ?string,
useEmptyPassphrase: boolean,
remember: boolean; // device should be remembered
connected: boolean; // device is connected
available: boolean; // device cannot be used because of features.passphrase_protection is different then expected
instance?: number;
instanceLabel: string;
instanceName: ?string;
ts: number;
remember: boolean, // device should be remembered
connected: boolean, // device is connected
available: boolean, // device cannot be used because of features.passphrase_protection is different then expected
instance?: number,
instanceLabel: string,
instanceName: ?string,
ts: number,
}>;
export type { Device } from 'trezor-connect';
@ -104,12 +103,12 @@ export type RouterLocationState = LocationState;
type DeviceEventAction = {
type: DeviceMessageType,
device: Device,
}
};
type TransportEventAction = {
type: TransportMessageType,
payload: any,
}
};
type UiEventAction = {
type: UiMessageType,
@ -118,22 +117,21 @@ type UiEventAction = {
// device: Device;
// code?: string;
// },
}
};
// TODO: join this message with uiMessage
type IFrameHandshake = {
type: 'iframe_handshake',
payload: any
}
payload: any,
};
export type Action =
RouterAction
| RouterAction
| IFrameHandshake
| TransportEventAction
| DeviceEventAction
| UiEventAction
| BlockchainEvent
| SelectedAccountAction
| AccountAction
| BlockchainAction

@ -1,6 +1,5 @@
/* @flow */
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as WALLET from 'actions/constants/wallet';
import * as ACCOUNT from 'actions/constants/account';
@ -24,25 +23,38 @@ type AccountCommon = {
transactions: number, // deprecated
};
export type Account = AccountCommon & {
networkType: 'ethereum',
nonce: number,
} | AccountCommon & {
networkType: 'ripple',
sequence: number,
reserve: string,
} | AccountCommon & {
networkType: 'bitcoin',
addressIndex: number,
};
export type Account =
| (AccountCommon & {
networkType: 'ethereum',
nonce: number,
})
| (AccountCommon & {
networkType: 'ripple',
sequence: number,
reserve: string,
})
| (AccountCommon & {
networkType: 'bitcoin',
addressIndex: number,
});
export type State = Array<Account>;
const initialState: State = [];
export const findAccount = (state: State, index: number, deviceState: string, network: string): ?Account => state.find(a => a.deviceState === deviceState && a.index === index && a.network === network);
export const findDeviceAccounts = (state: State, device: TrezorDevice, network: string): Array<Account> => {
export const findAccount = (
state: State,
index: number,
deviceState: string,
network: string
): ?Account =>
state.find(a => a.deviceState === deviceState && a.index === index && a.network === network);
export const findDeviceAccounts = (
state: State,
device: TrezorDevice,
network: string
): Array<Account> => {
if (network) {
return state.filter(addr => addr.deviceState === device.state && addr.network === network);
}
@ -52,7 +64,12 @@ export const findDeviceAccounts = (state: State, device: TrezorDevice, network:
const createAccount = (state: State, account: Account): State => {
// TODO check with device_id
// check if account was created before
const exist: ?Account = state.find(a => a.descriptor === account.descriptor && a.network === account.network && a.deviceState === account.deviceState);
const exist: ?Account = state.find(
a =>
a.descriptor === account.descriptor &&
a.network === account.network &&
a.deviceState === account.deviceState
);
if (exist) {
return state;
}
@ -61,18 +78,24 @@ const createAccount = (state: State, account: Account): State => {
return newState;
};
const removeAccounts = (state: State, device: TrezorDevice): State => state.filter(account => account.deviceState !== device.state);
const removeAccounts = (state: State, device: TrezorDevice): State =>
state.filter(account => account.deviceState !== device.state);
const clear = (state: State, devices: Array<TrezorDevice>): State => {
let newState: State = [...state];
devices.forEach((d) => {
devices.forEach(d => {
newState = removeAccounts(newState, d);
});
return newState;
};
const updateAccount = (state: State, account: Account): State => {
const index: number = state.findIndex(a => a.descriptor === account.descriptor && a.network === account.network && a.deviceState === account.deviceState);
const index: number = state.findIndex(
a =>
a.descriptor === account.descriptor &&
a.network === account.network &&
a.deviceState === account.deviceState
);
const newState: State = [...state];
newState[index] = account;
return newState;
@ -92,8 +115,8 @@ export default (state: State = initialState, action: Action): State => {
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:
return clear(state, action.devices);
//case CONNECT.FORGET_SINGLE :
// return forgetAccounts(state, action);
//case CONNECT.FORGET_SINGLE :
// return forgetAccounts(state, action);
case ACCOUNT.UPDATE:
return updateAccount(state, action.payload);
@ -104,4 +127,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -28,20 +28,24 @@ const onStartSubscribe = (state: State, shortcut: string): State => {
const network = state.find(b => b.shortcut === shortcut);
if (network) {
const others = state.filter(b => b !== network);
return others.concat([{
...network,
connecting: true,
}]);
return others.concat([
{
...network,
connecting: true,
},
]);
}
return state.concat([{
shortcut,
connected: false,
connecting: true,
block: 0,
feeTimestamp: 0,
feeLevels: [],
}]);
return state.concat([
{
shortcut,
connected: false,
connecting: true,
block: 0,
feeTimestamp: 0,
feeLevels: [],
},
]);
};
const onConnect = (state: State, action: BlockchainConnect): State => {
@ -50,22 +54,26 @@ const onConnect = (state: State, action: BlockchainConnect): State => {
const { info } = action.payload;
if (network) {
const others = state.filter(b => b !== network);
return others.concat([{
...network,
block: info.block,
connected: true,
connecting: false,
}]);
return others.concat([
{
...network,
block: info.block,
connected: true,
connecting: false,
},
]);
}
return state.concat([{
shortcut,
connected: true,
connecting: false,
block: info.block,
feeTimestamp: 0,
feeLevels: [],
}]);
return state.concat([
{
shortcut,
connected: true,
connecting: false,
block: info.block,
feeTimestamp: 0,
feeLevels: [],
},
]);
};
const onError = (state: State, action: BlockchainError): State => {
@ -73,21 +81,25 @@ const onError = (state: State, action: BlockchainError): State => {
const network = state.find(b => b.shortcut === shortcut);
if (network) {
const others = state.filter(b => b !== network);
return others.concat([{
...network,
connected: false,
connecting: false,
}]);
return others.concat([
{
...network,
connected: false,
connecting: false,
},
]);
}
return state.concat([{
shortcut,
connected: false,
connecting: false,
block: 0,
feeTimestamp: 0,
feeLevels: [],
}]);
return state.concat([
{
shortcut,
connected: false,
connecting: false,
block: 0,
feeTimestamp: 0,
feeLevels: [],
},
]);
};
const onBlock = (state: State, action: BlockchainBlock): State => {
@ -95,10 +107,12 @@ const onBlock = (state: State, action: BlockchainBlock): State => {
const network = state.find(b => b.shortcut === shortcut);
if (network) {
const others = state.filter(b => b !== network);
return others.concat([{
...network,
block: action.payload.block,
}]);
return others.concat([
{
...network,
block: action.payload.block,
},
]);
}
return state;
@ -109,14 +123,15 @@ const updateFee = (state: State, shortcut: string, feeLevels: Array<BlockchainFe
if (!network) return state;
const others = state.filter(b => b !== network);
return others.concat([{
...network,
feeTimestamp: new Date().getTime(),
feeLevels,
}]);
return others.concat([
{
...network,
feeTimestamp: new Date().getTime(),
feeLevels,
},
]);
};
export default (state: State = initialState, action: Action): State => {
switch (action.type) {
case BLOCKCHAIN_ACTION.START_SUBSCRIBE:
@ -133,4 +148,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -35,15 +35,22 @@ const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): T
remember: typeof upcoming.remember === 'boolean' ? upcoming.remember : current.remember,
instance: current.instance,
instanceLabel,
instanceName: typeof upcoming.instanceName === 'string' ? upcoming.instanceName : current.instanceName,
instanceName:
typeof upcoming.instanceName === 'string'
? upcoming.instanceName
: current.instanceName,
state: current.state,
ts: typeof upcoming.ts === 'number' ? upcoming.ts : current.ts,
useEmptyPassphrase: typeof upcoming.useEmptyPassphrase === 'boolean' ? upcoming.useEmptyPassphrase : current.useEmptyPassphrase,
useEmptyPassphrase:
typeof upcoming.useEmptyPassphrase === 'boolean'
? upcoming.useEmptyPassphrase
: current.useEmptyPassphrase,
};
if (upcoming.type === 'acquired') {
return { ...upcoming, ...extended };
} if (upcoming.type === 'unacquired' && current.features && current.state) {
}
if (upcoming.type === 'unacquired' && current.features && current.state) {
// corner-case: trying to merge unacquired device with acquired
// make sure that sensitive fields will not be changed and device will remain acquired
return {
@ -72,11 +79,13 @@ const addDevice = (state: State, device: Device): State => {
}
otherDevices = state.filter(d => affectedDevices.indexOf(d) === -1);
} else {
affectedDevices = state.filter(d => d.features
&& device.features
&& d.features.device_id === device.features.device_id);
affectedDevices = state.filter(
d => d.features && device.features && d.features.device_id === device.features.device_id
);
const unacquiredDevices = state.filter(d => d.path.length > 0 && d.path === device.path);
otherDevices = state.filter(d => affectedDevices.indexOf(d) < 0 && unacquiredDevices.indexOf(d) < 0);
otherDevices = state.filter(
d => affectedDevices.indexOf(d) < 0 && unacquiredDevices.indexOf(d) < 0
);
}
const extended = {
@ -92,15 +101,17 @@ const addDevice = (state: State, device: Device): State => {
useEmptyPassphrase: true,
};
const newDevice: TrezorDevice = device.type === 'acquired' ? {
...device,
...extended,
} : {
...device,
features: null,
...extended,
};
const newDevice: TrezorDevice =
device.type === 'acquired'
? {
...device,
...extended,
}
: {
...device,
features: null,
...extended,
};
if (affectedDevices.length > 0) {
// check if freshly added device has different "passphrase_protection" settings
@ -130,11 +141,17 @@ const addDevice = (state: State, device: Device): State => {
// changedDevices.push(newDevice);
// }
const changedDevices: Array<TrezorDevice> = affectedDevices.filter(d => d.features && device.features
&& d.features.passphrase_protection === device.features.passphrase_protection).map((d) => {
const extended2: Object = { connected: true, available: true };
return mergeDevices(d, { ...device, ...extended2 });
});
const changedDevices: Array<TrezorDevice> = affectedDevices
.filter(
d =>
d.features &&
device.features &&
d.features.passphrase_protection === device.features.passphrase_protection
)
.map(d => {
const extended2: Object = { connected: true, available: true };
return mergeDevices(d, { ...device, ...extended2 });
});
if (changedDevices.length !== affectedDevices.length) {
changedDevices.push(newDevice);
}
@ -144,7 +161,6 @@ const addDevice = (state: State, device: Device): State => {
return otherDevices.concat([newDevice]);
};
const duplicate = (state: State, device: TrezorDevice): State => {
if (!device.features) return state;
@ -171,14 +187,22 @@ const changeDevice = (state: State, device: Device | TrezorDevice, extended: Obj
// find devices with the same device_id and passphrase_protection settings
// or devices with the same path (TODO: should be that way?)
const affectedDevices: Array<TrezorDevice> = state.filter(d => (d.features && device.features && d.features.device_id === device.features.device_id && d.features.passphrase_protection === device.features.passphrase_protection)
|| (d.features && d.path.length > 0 && d.path === device.path));
const affectedDevices: Array<TrezorDevice> = state.filter(
d =>
(d.features &&
device.features &&
d.features.device_id === device.features.device_id &&
d.features.passphrase_protection === device.features.passphrase_protection) ||
(d.features && d.path.length > 0 && d.path === device.path)
);
const otherDevices: Array<TrezorDevice> = state.filter(d => affectedDevices.indexOf(d) === -1);
if (affectedDevices.length > 0) {
// merge incoming device with State
const changedDevices = affectedDevices.map(d => mergeDevices(d, { ...device, ...extended }));
const changedDevices = affectedDevices.map(d =>
mergeDevices(d, { ...device, ...extended })
);
return otherDevices.concat(changedDevices);
}
@ -186,7 +210,9 @@ const changeDevice = (state: State, device: Device | TrezorDevice, extended: Obj
};
const authDevice = (state: State, device: TrezorDevice, deviceState: string): State => {
const affectedDevice: ?TrezorDevice = state.find(d => d.path === device.path && d.instance === device.instance);
const affectedDevice: ?TrezorDevice = state.find(
d => d.path === device.path && d.instance === device.instance
);
// device could already have own state from trezor-connect, do not override it
if (affectedDevice && !affectedDevice.state && affectedDevice.type === 'acquired') {
const otherDevices: Array<TrezorDevice> = state.filter(d => d !== affectedDevice);
@ -195,28 +221,35 @@ const authDevice = (state: State, device: TrezorDevice, deviceState: string): St
return state;
};
// Transform JSON form local storage into State
const devicesFromStorage = ($devices: Array<TrezorDevice>): State => $devices.map((device: TrezorDevice) => {
const extended = {
connected: false,
available: false,
path: '',
};
const devicesFromStorage = ($devices: Array<TrezorDevice>): State =>
$devices.map((device: TrezorDevice) => {
const extended = {
connected: false,
available: false,
path: '',
};
return device.type === 'acquired' ? {
...device,
...extended,
} : {
...device,
features: null,
...extended,
};
});
return device.type === 'acquired'
? {
...device,
...extended,
}
: {
...device,
features: null,
...extended,
};
});
// Remove all device reference from State
const forgetDevice = (state: State, device: TrezorDevice): State => state.filter(d => d.remember || (d.features && device.features && d.features.device_id !== device.features.device_id) || (!d.features && d.path !== device.path));
const forgetDevice = (state: State, device: TrezorDevice): State =>
state.filter(
d =>
d.remember ||
(d.features && device.features && d.features.device_id !== device.features.device_id) ||
(!d.features && d.path !== device.path)
);
// Remove single device reference from State
const forgetSingleDevice = (state: State, device: TrezorDevice): State => {
@ -227,19 +260,28 @@ const forgetSingleDevice = (state: State, device: TrezorDevice): State => {
};
const disconnectDevice = (state: State, device: Device): State => {
const affectedDevices: State = state.filter(d => d.path === device.path || (d.features && device.features && d.features.device_id === device.features.device_id));
const affectedDevices: State = state.filter(
d =>
d.path === device.path ||
(d.features && device.features && d.features.device_id === device.features.device_id)
);
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
if (affectedDevices.length > 0) {
const acquiredDevices = affectedDevices.filter(d => d.features && d.state).map((d) => { // eslint-disable-line arrow-body-style
return d.type === 'acquired' ? {
...d,
connected: false,
available: false,
status: 'available',
path: '',
} : d;
});
const acquiredDevices = affectedDevices
.filter(d => d.features && d.state)
.map(d => {
// eslint-disable-line arrow-body-style
return d.type === 'acquired'
? {
...d,
connected: false,
available: false,
status: 'available',
path: '',
}
: d;
});
return otherDevices.concat(acquiredDevices);
}
@ -250,29 +292,39 @@ const onSelectedDevice = (state: State, device: ?TrezorDevice): State => {
if (!device) return state;
const otherDevices: Array<TrezorDevice> = state.filter(d => d !== device);
const extended = device.type === 'acquired' ? {
...device,
ts: new Date().getTime(),
} : {
...device,
features: null,
ts: new Date().getTime(),
};
const extended =
device.type === 'acquired'
? {
...device,
ts: new Date().getTime(),
}
: {
...device,
features: null,
ts: new Date().getTime(),
};
return otherDevices.concat([extended]);
};
const onChangeWalletType = (state: State, device: TrezorDevice, hidden: boolean): State => {
const affectedDevices: State = state.filter(d => d.path === device.path || (d.features && device.features && d.features.device_id === device.features.device_id));
const affectedDevices: State = state.filter(
d =>
d.path === device.path ||
(d.features && device.features && d.features.device_id === device.features.device_id)
);
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
if (affectedDevices.length > 0) {
const changedDevices = affectedDevices.map((d) => { // eslint-disable-line arrow-body-style
return d.type === 'acquired' ? {
...d,
remember: false,
state: null,
useEmptyPassphrase: !hidden,
ts: new Date().getTime(),
} : d;
const changedDevices = affectedDevices.map(d => {
// eslint-disable-line arrow-body-style
return d.type === 'acquired'
? {
...d,
remember: false,
state: null,
useEmptyPassphrase: !hidden,
ts: new Date().getTime(),
}
: d;
});
return otherDevices.concat(changedDevices);
}
@ -307,10 +359,10 @@ export default function devices(state: State = initialState, action: Action): St
case DEVICE.CHANGED:
// return changeDevice(state, { ...action.device, connected: true, available: true });
return changeDevice(state, action.device, { connected: true, available: true });
// TODO: check if available will propagate to unavailable
// TODO: check if available will propagate to unavailable
case DEVICE.DISCONNECT:
// case DEVICE.DISCONNECT_UNACQUIRED:
// case DEVICE.DISCONNECT_UNACQUIRED:
return disconnectDevice(state, action.device);
case WALLET.SET_SELECTED_DEVICE:
@ -323,4 +375,4 @@ export default function devices(state: State = initialState, action: Action): St
default:
return state;
}
}
}

@ -1,6 +1,5 @@
/* @flow */
import HDKey from 'hdkey';
import * as DISCOVERY from 'actions/constants/discovery';
@ -18,20 +17,20 @@ import type {
import type { Account } from './AccountsReducer';
export type Discovery = {
network: string;
basePath: Array<number>;
deviceState: string;
accountIndex: number;
interrupted: boolean;
completed: boolean;
waitingForDevice: boolean;
waitingForBlockchain: boolean;
fwNotSupported: boolean;
fwOutdated: boolean;
publicKey: string; // used in ethereum only
chainCode: string; // used in ethereum only
hdKey: HDKey; // used in ethereum only
network: string,
basePath: Array<number>,
deviceState: string,
accountIndex: number,
interrupted: boolean,
completed: boolean,
waitingForDevice: boolean,
waitingForBlockchain: boolean,
fwNotSupported: boolean,
fwOutdated: boolean,
publicKey: string, // used in ethereum only
chainCode: string, // used in ethereum only
hdKey: HDKey, // used in ethereum only
};
export type State = Array<Discovery>;
@ -53,7 +52,8 @@ const defaultDiscovery: Discovery = {
hdKey: null,
};
const findIndex = (state: State, network: string, deviceState: string): number => state.findIndex(d => d.network === network && d.deviceState === deviceState);
const findIndex = (state: State, network: string, deviceState: string): number =>
state.findIndex(d => d.network === network && d.deviceState === deviceState);
const start = (state: State, action: DiscoveryStartAction): State => {
const deviceState: string = action.device.state || '0';
@ -99,11 +99,12 @@ const accountCreate = (state: State, account: Account): State => {
return newState;
};
const forgetDiscovery = (state: State, device: TrezorDevice): State => state.filter(d => d.deviceState !== device.state);
const forgetDiscovery = (state: State, device: TrezorDevice): State =>
state.filter(d => d.deviceState !== device.state);
const clear = (state: State, devices: Array<TrezorDevice>): State => {
let newState: State = [...state];
devices.forEach((d) => {
devices.forEach(d => {
newState = forgetDiscovery(newState, d);
});
return newState;
@ -163,7 +164,9 @@ const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): Sta
};
const notSupported = (state: State, action: DiscoveryWaitingAction): State => {
const affectedProcesses = state.filter(d => d.deviceState === action.device.state && d.network === action.network);
const affectedProcesses = state.filter(
d => d.deviceState === action.device.state && d.network === action.network
);
const otherProcesses = state.filter(d => affectedProcesses.indexOf(d) === -1);
const changedProcesses = affectedProcesses.map(d => ({
@ -194,7 +197,7 @@ export default function discovery(state: State = initialState, action: Action):
case DISCOVERY.FIRMWARE_OUTDATED:
return notSupported(state, action);
case DISCOVERY.FROM_STORAGE:
return action.payload.map((d) => {
return action.payload.map(d => {
if (d.publicKey.length < 1) return d;
// recreate ethereum discovery HDKey
// deprecated: will be removed after switching to blockbook
@ -220,4 +223,4 @@ export default function discovery(state: State = initialState, action: Action):
default:
return state;
}
}
}

@ -6,8 +6,8 @@ import type { Action } from 'flowtype';
import type { FiatRateAction } from 'services/CoingeckoService';
export type Fiat = {
+network: string;
value: string;
+network: string,
value: string,
};
export const initialState: Array<Fiat> = [];
@ -17,10 +17,12 @@ const update = (state: Array<Fiat>, action: FiatRateAction): Array<Fiat> => {
const otherRates = state.filter(d => d !== affected);
const { network, rate } = action;
return otherRates.concat([{
network,
value: rate.toFixed(2),
}]);
return otherRates.concat([
{
network,
value: rate.toFixed(2),
},
]);
};
export default (state: Array<Fiat> = initialState, action: Action): Array<Fiat> => {
@ -31,4 +33,4 @@ export default (state: Array<Fiat> = initialState, action: Action): Array<Fiat>
default:
return state;
}
};
};

@ -1,6 +1,5 @@
/* @flow */
import * as STORAGE from 'actions/constants/localStorage';
import type { Action } from 'flowtype';
@ -14,43 +13,43 @@ type NetworkFeeLevel = {
};
export type Network = {
type: string;
name: string;
testnet?: boolean;
shortcut: string;
symbol: string;
bip44: string;
defaultGasLimit: number;
defaultGasLimitTokens: number;
defaultGasPrice: number;
chainId: number; // ETH specific
type: string,
name: string,
testnet?: boolean,
shortcut: string,
symbol: string,
bip44: string,
defaultGasLimit: number,
defaultGasLimitTokens: number,
defaultGasPrice: number,
chainId: number, // ETH specific
explorer: {
tx: string;
address: string;
};
tokens: string;
decimals: number;
tx: string,
address: string,
},
tokens: string,
decimals: number,
fee: {
defaultFee: string;
minFee: string;
maxFee: string;
defaultGasLimit: string; // ETH specific
defaultGasLimitTokens: string; // ETH specific
levels: Array<NetworkFeeLevel>;
defaultFee: string,
minFee: string,
maxFee: string,
defaultGasLimit: string, // ETH specific
defaultGasLimitTokens: string, // ETH specific
levels: Array<NetworkFeeLevel>,
},
backends: Array<{
name: string;
urls: Array<string>;
}>;
web3: Array<string>;
}
name: string,
urls: Array<string>,
}>,
web3: Array<string>,
};
export type NetworkToken = {
address: string,
name: string,
symbol: string,
decimals: number
}
decimals: number,
};
export type TokensCollection = { [network: string]: Array<NetworkToken> };
@ -71,22 +70,22 @@ export type TokensCollection = { [network: string]: Array<NetworkToken> };
// }
export type FiatValueTicker = {
network: string;
url: string;
}
network: string,
url: string,
};
export type Config = {
networks: Array<Network>;
fiatValueTickers: Array<FiatValueTicker>;
}
networks: Array<Network>,
fiatValueTickers: Array<FiatValueTicker>,
};
export type State = {
initialized: boolean;
error: ?string;
config: Config;
ERC20Abi: Array<Object>;
tokens: TokensCollection;
}
initialized: boolean,
error: ?string,
config: Config,
ERC20Abi: Array<Object>,
tokens: TokensCollection,
};
const initialState: State = {
initialized: false,
@ -117,7 +116,6 @@ export default function localStorage(state: State = initialState, action: Action
error: action.error,
};
default:
return state;
}

@ -4,22 +4,21 @@ import * as LOG from 'actions/constants/log';
import type { Action } from 'flowtype';
export type LogEntry = {
time: number;
type: string;
message: any;
}
time: number,
type: string,
message: any,
};
export type State = {
opened: boolean;
entries: Array<LogEntry>;
}
opened: boolean,
entries: Array<LogEntry>,
};
export const initialState: State = {
opened: false,
entries: [],
};
export default (state: State = initialState, action: Action): State => {
switch (action.type) {
case LOG.OPEN:
@ -43,4 +42,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -1,6 +1,5 @@
/* @flow */
import { UI, DEVICE } from 'trezor-connect';
import * as RECEIVE from 'actions/constants/receive';
import * as MODAL from 'actions/constants/modal';
@ -8,22 +7,27 @@ import * as CONNECT from 'actions/constants/TrezorConnect';
import type { Action, TrezorDevice } from 'flowtype';
export type State = {
context: typeof MODAL.CONTEXT_NONE;
} | {
context: typeof MODAL.CONTEXT_DEVICE,
device: TrezorDevice;
instances?: Array<TrezorDevice>;
windowType?: string;
} | {
context: typeof MODAL.CONTEXT_EXTERNAL_WALLET,
windowType?: string;
} | {
context: typeof MODAL.CONTEXT_SCAN_QR,
} | {
context: typeof MODAL.CONTEXT_CONFIRMATION,
windowType: string;
};
export type State =
| {
context: typeof MODAL.CONTEXT_NONE,
}
| {
context: typeof MODAL.CONTEXT_DEVICE,
device: TrezorDevice,
instances?: Array<TrezorDevice>,
windowType?: string,
}
| {
context: typeof MODAL.CONTEXT_EXTERNAL_WALLET,
windowType?: string,
}
| {
context: typeof MODAL.CONTEXT_SCAN_QR,
}
| {
context: typeof MODAL.CONTEXT_CONFIRMATION,
windowType: string,
};
const initialState: State = {
context: MODAL.CONTEXT_NONE,
@ -61,12 +65,14 @@ export default function modal(state: State = initialState, action: Action): Stat
// device with context assigned to modal was disconnected
// close modal
case DEVICE.DISCONNECT:
if (state.context === MODAL.CONTEXT_DEVICE && action.device.path === state.device.path) {
if (
state.context === MODAL.CONTEXT_DEVICE &&
action.device.path === state.device.path
) {
return initialState;
}
return state;
case UI.REQUEST_PIN:
case UI.INVALID_PIN:
case UI.REQUEST_PASSPHRASE:
@ -110,4 +116,4 @@ export default function modal(state: State = initialState, action: Action): Stat
default:
return state;
}
}
}

@ -8,20 +8,20 @@ import { DEVICE } from 'trezor-connect';
import type { Action } from 'flowtype';
export type CallbackAction = {
label: string;
callback: Function;
}
label: string,
callback: Function,
};
export type NotificationEntry = {
+key: string; // React.Key
+id: ?string;
+devicePath: ?string;
+type: string;
+title: React.Node | string;
+message: ?(React.Node | string);
+cancelable: boolean;
+actions: Array<CallbackAction>;
}
+key: string, // React.Key
+id: ?string,
+devicePath: ?string,
+type: string,
+title: React.Node | string,
+message: ?(React.Node | string),
+cancelable: boolean,
+actions: Array<CallbackAction>,
};
export type State = Array<NotificationEntry>;
@ -56,7 +56,8 @@ const addNotification = (state: State, payload: any): State => {
const closeNotification = (state: State, payload: any): State => {
if (payload && typeof payload.id === 'string') {
return state.filter(entry => entry.id !== payload.id);
} if (payload && typeof payload.devicePath === 'string') {
}
if (payload && typeof payload.devicePath === 'string') {
return state.filter(entry => entry.devicePath !== payload.devicePath);
}
return state.filter(entry => !entry.cancelable);

@ -14,16 +14,18 @@ const add = (state: State, payload: Transaction): State => {
return newState;
};
const removeByDeviceState = (state: State, deviceState: ?string): State => state.filter(tx => tx.deviceState !== deviceState);
const removeByDeviceState = (state: State, deviceState: ?string): State =>
state.filter(tx => tx.deviceState !== deviceState);
const removeByHash = (state: State, hash: string): State => state.filter(tx => tx.hash !== hash);
const reject = (state: State, hash: string): State => state.map((tx) => {
if (tx.hash === hash && !tx.rejected) {
return { ...tx, rejected: true };
}
return tx;
});
const reject = (state: State, hash: string): State =>
state.map(tx => {
if (tx.hash === hash && !tx.rejected) {
return { ...tx, rejected: true };
}
return tx;
});
export default function pending(state: State = initialState, action: Action): State {
switch (action.type) {
@ -46,4 +48,4 @@ export default function pending(state: State = initialState, action: Action): St
default:
return state;
}
}
}

@ -1,15 +1,14 @@
/* @flow */
import { UI } from 'trezor-connect';
import * as RECEIVE from 'actions/constants/receive';
import * as ACCOUNT from 'actions/constants/account';
import type { Action } from 'flowtype';
export type State = {
addressVerified: boolean;
addressUnverified: boolean;
}
addressVerified: boolean,
addressUnverified: boolean,
};
export const initialState: State = {
addressVerified: false,
@ -52,4 +51,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -1,33 +1,26 @@
/* @flow */
import * as ACCOUNT from 'actions/constants/account';
import type {
Action,
Account,
Network,
Token,
Transaction,
Discovery,
} from 'flowtype';
import type { Action, Account, Network, Token, Transaction, Discovery } from 'flowtype';
export type Loader = {
type: string,
title: string,
message?: string,
}
};
export type Notification = {
type: string,
title: string,
message?: string,
}
};
export type ExceptionPage = {
type: ?string,
title: ?string,
message: ?string,
shortcut: string,
}
};
export type State = {
location: string,
@ -64,4 +57,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -6,41 +6,40 @@ import * as ACCOUNT from 'actions/constants/account';
import type { Action } from 'flowtype';
export type FeeLevel = {
label: string;
gasPrice: string;
value: string;
}
label: string,
gasPrice: string,
value: string,
};
export type State = {
+networkName: string;
+networkSymbol: string;
currency: string;
+networkName: string,
+networkSymbol: string,
currency: string,
// form fields
advanced: boolean;
untouched: boolean; // set to true when user made any changes in form
touched: {[k: string]: boolean};
address: string;
amount: string;
setMax: boolean;
feeLevels: Array<FeeLevel>;
selectedFeeLevel: FeeLevel;
recommendedGasPrice: string;
gasPriceNeedsUpdate: boolean;
gasLimit: string;
calculatingGasLimit: boolean;
gasPrice: string;
data: string;
nonce: string;
total: string;
errors: {[k: string]: string};
warnings: {[k: string]: string};
infos: {[k: string]: string};
sending: boolean;
}
advanced: boolean,
untouched: boolean, // set to true when user made any changes in form
touched: { [k: string]: boolean },
address: string,
amount: string,
setMax: boolean,
feeLevels: Array<FeeLevel>,
selectedFeeLevel: FeeLevel,
recommendedGasPrice: string,
gasPriceNeedsUpdate: boolean,
gasLimit: string,
calculatingGasLimit: boolean,
gasPrice: string,
data: string,
nonce: string,
total: string,
errors: { [k: string]: string },
warnings: { [k: string]: string },
infos: { [k: string]: string },
sending: boolean,
};
export const initialState: State = {
networkName: '',
@ -106,4 +105,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -6,38 +6,37 @@ import * as ACCOUNT from 'actions/constants/account';
import type { Action } from 'flowtype';
export type FeeLevel = {
label: string;
fee: string;
value: string;
}
label: string,
fee: string,
value: string,
};
export type State = {
+networkName: string;
+networkSymbol: string;
+networkName: string,
+networkSymbol: string,
// form fields
advanced: boolean;
untouched: boolean; // set to true when user made any changes in form
touched: {[k: string]: boolean};
address: string;
amount: string;
minAmount: string;
setMax: boolean;
feeLevels: Array<FeeLevel>;
selectedFeeLevel: FeeLevel;
fee: string;
feeNeedsUpdate: boolean;
sequence: string;
destinationTag: string;
total: string;
errors: {[k: string]: string};
warnings: {[k: string]: string};
infos: {[k: string]: string};
sending: boolean;
}
advanced: boolean,
untouched: boolean, // set to true when user made any changes in form
touched: { [k: string]: boolean },
address: string,
amount: string,
minAmount: string,
setMax: boolean,
feeLevels: Array<FeeLevel>,
selectedFeeLevel: FeeLevel,
fee: string,
feeNeedsUpdate: boolean,
sequence: string,
destinationTag: string,
total: string,
errors: { [k: string]: string },
warnings: { [k: string]: string },
infos: { [k: string]: string },
sending: boolean,
};
export const initialState: State = {
networkName: '',
@ -101,4 +100,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -17,8 +17,8 @@ export type State = {
verifyMessage: string,
verifySignature: string,
touched: Array<string>,
errors: Array<Error>
}
errors: Array<Error>,
};
export const initialState: State = {
signAddress: '',
@ -92,4 +92,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -1,15 +1,14 @@
/* @flow */
import * as ACCOUNT from 'actions/constants/account';
import * as SUMMARY from 'actions/constants/summary';
import type { Action } from 'flowtype';
import type { NetworkToken } from './LocalStorageReducer';
export type State = {
details: boolean;
selectedToken: ?NetworkToken;
}
details: boolean,
selectedToken: ?NetworkToken,
};
export const initialState: State = {
details: true,
@ -36,4 +35,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -1,6 +1,5 @@
/* @flow */
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as WALLET from 'actions/constants/wallet';
import * as TOKEN from 'actions/constants/token';
@ -8,16 +7,16 @@ import * as TOKEN from 'actions/constants/token';
import type { Action, TrezorDevice } from 'flowtype';
export type Token = {
loaded: boolean;
+deviceState: string;
+network: string;
+name: string;
+symbol: string;
+address: string;
+ethAddress: string; // foreign key
+decimals: number;
balance: string;
}
loaded: boolean,
+deviceState: string,
+network: string,
+name: string,
+symbol: string,
+address: string,
+ethAddress: string, // foreign key
+decimals: number,
balance: string,
};
export type State = Array<Token>;
@ -29,17 +28,26 @@ const create = (state: State, token: Token): State => {
return newState;
};
const forget = (state: State, device: TrezorDevice): State => state.filter(t => t.deviceState !== device.state);
const forget = (state: State, device: TrezorDevice): State =>
state.filter(t => t.deviceState !== device.state);
const clear = (state: State, devices: Array<TrezorDevice>): State => {
let newState: State = [...state];
devices.forEach((d) => {
devices.forEach(d => {
newState = forget(newState, d);
});
return newState;
};
const remove = (state: State, token: Token): State => state.filter(t => !(t.ethAddress === token.ethAddress && t.address === token.address && t.deviceState === token.deviceState));
const remove = (state: State, token: Token): State =>
state.filter(
t =>
!(
t.ethAddress === token.ethAddress &&
t.address === token.address &&
t.deviceState === token.deviceState
)
);
export default (state: State = initialState, action: Action): State => {
switch (action.type) {
@ -65,4 +73,4 @@ export default (state: State = initialState, action: Action): State => {
default:
return state;
}
};
};

@ -5,29 +5,31 @@ import * as CONNECT from 'actions/constants/TrezorConnect';
import type { Action } from 'flowtype';
export type SelectedDevice = {
id: string; // could be device path if unacquired or features.device_id
instance: ?number;
}
id: string, // could be device path if unacquired or features.device_id
instance: ?number,
};
export type LatestBridge = {
version: Array<number>;
directory: string;
packages: Array<{ name: string; url: string; signature?: string; preferred: boolean; }>;
changelog: Array<string>;
}
version: Array<number>,
directory: string,
packages: Array<{ name: string, url: string, signature?: string, preferred: boolean }>,
changelog: Array<string>,
};
export type State = {
initialized: boolean;
error: ?string;
transport: {
type: string;
version: string;
outdated: boolean;
bridge: LatestBridge;
} | {
type: null,
bridge: LatestBridge;
};
initialized: boolean,
error: ?string,
transport:
| {
type: string,
version: string,
outdated: boolean,
bridge: LatestBridge,
}
| {
type: null,
bridge: LatestBridge,
},
// browserState: {
// name: string;
// osname: string;
@ -35,9 +37,9 @@ export type State = {
// outdated: boolean;
// mobile: boolean;
// } | {};
browserState: any;
acquiringDevice: boolean;
}
browserState: any,
acquiringDevice: boolean,
};
const initialState: State = {
initialized: false,
@ -55,7 +57,6 @@ const initialState: State = {
acquiringDevice: false,
};
export default function connect(state: State = initialState, action: Action): State {
switch (action.type) {
// trezor-connect iframe didn't loaded properly
@ -106,4 +107,4 @@ export default function connect(state: State = initialState, action: Action): St
default:
return state;
}
}
}

@ -1,6 +1,5 @@
/* @flow */
import { LOCATION_CHANGE } from 'connected-react-router';
import { DEVICE, TRANSPORT } from 'trezor-connect';
import * as MODAL from 'actions/constants/modal';
@ -11,19 +10,19 @@ import * as ACCOUNT from 'actions/constants/account';
import type { Action, RouterLocationState, TrezorDevice } from 'flowtype';
type State = {
ready: boolean;
online: boolean;
language: string;
messages: { [string]: string };
dropdownOpened: boolean;
showBetaDisclaimer: boolean;
showSidebar: ?boolean;
initialParams: ?RouterLocationState;
initialPathname: ?string;
firstLocationChange: boolean;
disconnectRequest: ?TrezorDevice;
selectedDevice: ?TrezorDevice;
}
ready: boolean,
online: boolean,
language: string,
messages: { [string]: string },
dropdownOpened: boolean,
showBetaDisclaimer: boolean,
showSidebar: ?boolean,
initialParams: ?RouterLocationState,
initialPathname: ?string,
firstLocationChange: boolean,
disconnectRequest: ?TrezorDevice,
selectedDevice: ?TrezorDevice,
};
const initialState: State = {
ready: false,

@ -1,25 +1,21 @@
/* @flow */
import Web3 from 'web3';
import type { Contract } from 'web3';
import * as WEB3 from 'actions/constants/web3';
import type { Action } from 'flowtype';
import type {
Web3UpdateBlockAction,
Web3UpdateGasPriceAction,
} from 'actions/Web3Actions';
import type { Web3UpdateBlockAction, Web3UpdateGasPriceAction } from 'actions/Web3Actions';
export type Web3Instance = {
network: string;
web3: Web3;
chainId: number;
latestBlock: any;
gasPrice: string;
erc20: Contract;
}
network: string,
web3: Web3,
chainId: number,
latestBlock: any,
gasPrice: string,
erc20: Contract,
};
export type State = Array<Web3Instance>;
@ -70,4 +66,4 @@ export default function web3(state: State = initialState, action: Action): State
default:
return state;
}
}
}

@ -45,18 +45,23 @@ const reducers = {
devices,
blockchain,
signVerify,
router: () => ({
location: {
pathname: '', hash: '', search: '', state: {},
},
}: State),
router: () =>
({
location: {
pathname: '',
hash: '',
search: '',
state: {},
},
}: State),
};
export type Reducers = typeof reducers;
type $ExtractFunctionReturn = <V>(v: (...args: any) => V) => V;
export type ReducersState = $ObjMap<Reducers, $ExtractFunctionReturn>;
export default (history: any) => combineReducers({
...reducers,
router: connectRouter(history),
});
export default (history: any) =>
combineReducers({
...reducers,
router: connectRouter(history),
});

@ -17,18 +17,26 @@ import type {
export const RATE_UPDATE: 'rate__update' = 'rate__update';
export type FiatRateAction = {
type: typeof RATE_UPDATE;
network: string;
rate: number;
}
type: typeof RATE_UPDATE,
network: string,
rate: number,
};
const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const loadRateAction = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { config } = getState().localStorage;
if (!config) return;
try {
config.fiatValueTickers.forEach(async (ticker) => {
const response = await httpRequest(`${ticker.url}?tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`, 'json');
config.fiatValueTickers.forEach(async ticker => {
const response = await httpRequest(
`${
ticker.url
}?tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`,
'json'
);
if (response) {
dispatch({
type: RATE_UPDATE,
@ -47,7 +55,9 @@ const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: G
/**
* Middleware
*/
const CoingeckoService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
const CoingeckoService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (
action: Action
): Action => {
next(action);
if (action.type === READY) {
@ -57,4 +67,4 @@ const CoingeckoService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDi
return action;
};
export default CoingeckoService;
export default CoingeckoService;

@ -9,15 +9,11 @@ import * as DISCOVERY from 'actions/constants/discovery';
import * as PENDING from 'actions/constants/pendingTx';
import * as WALLET from 'actions/constants/wallet';
import type { Middleware, MiddlewareAPI, MiddlewareDispatch, Action } from 'flowtype';
import type {
Middleware,
MiddlewareAPI,
MiddlewareDispatch,
Action,
} from 'flowtype';
const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (
action: Action
): Action => {
// pass action
next(action);
@ -74,4 +70,4 @@ const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: Middlewar
return action;
};
export default LocalStorageService;
export default LocalStorageService;

@ -3,12 +3,7 @@ import * as LogActions from 'actions/LogActions';
import { TRANSPORT, DEVICE } from 'trezor-connect';
import * as DISCOVERY from 'actions/constants/discovery';
import type {
Middleware,
MiddlewareAPI,
MiddlewareDispatch,
Action,
} from 'flowtype';
import type { Middleware, MiddlewareAPI, MiddlewareDispatch, Action } from 'flowtype';
const actions: Array<string> = [
TRANSPORT.START,
@ -20,14 +15,21 @@ const actions: Array<string> = [
/**
* Middleware
*/
const LogService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
const LogService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (
action: Action
): Action => {
next(action);
if (actions.indexOf(action.type) < 0) return action;
switch (action.type) {
case TRANSPORT.START:
api.dispatch(LogActions.add('Transport', { type: action.payload.type, version: action.payload.version }));
api.dispatch(
LogActions.add('Transport', {
type: action.payload.type,
version: action.payload.version,
})
);
break;
case DEVICE.CONNECT:
api.dispatch(LogActions.add('Device connected', action.device));
@ -38,7 +40,8 @@ const LogService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch
case DISCOVERY.START:
api.dispatch(LogActions.add('Discovery started', action));
break;
default: break;
default:
break;
}
// if (action.type === TRANSPORT.START) {
@ -50,4 +53,4 @@ const LogService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch
return action;
};
export default LogService;
export default LogService;

@ -2,19 +2,16 @@
import { LOCATION_CHANGE, replace } from 'connected-react-router';
import * as RouterActions from 'actions/RouterActions';
import type {
Middleware,
MiddlewareAPI,
MiddlewareDispatch,
Action,
} from 'flowtype';
import type { Middleware, MiddlewareAPI, MiddlewareDispatch, Action } from 'flowtype';
/**
* 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 RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (
action: Action
): Action => {
// make sure that middleware should process this action
if (action.type !== LOCATION_CHANGE) {
// pass action
@ -43,4 +40,4 @@ const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
return override;
};
export default RouterService;
export default RouterService;

@ -1,8 +1,6 @@
/* @flow */
import {
TRANSPORT, DEVICE, BLOCKCHAIN,
} from 'trezor-connect';
import { TRANSPORT, DEVICE, BLOCKCHAIN } from 'trezor-connect';
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
import * as BlockchainActions from 'actions/BlockchainActions';
import * as RouterActions from 'actions/RouterActions';
@ -11,15 +9,11 @@ import * as STORAGE from 'actions/constants/localStorage';
import * as CONNECT from 'actions/constants/TrezorConnect';
import { READY as BLOCKCHAIN_READY } from 'actions/constants/blockchain';
import type {
Middleware,
MiddlewareAPI,
MiddlewareDispatch,
State,
Action,
} from 'flowtype';
import type { Middleware, MiddlewareAPI, MiddlewareDispatch, State, Action } from 'flowtype';
const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (
action: Action
): Action => {
// const prevState: $ElementType<State, 'connect'> = api.getState().connect;
const prevModalState: $ElementType<State, 'modal'> = api.getState().modal;
// const prevRouterState: $ElementType<State, 'router'> = api.getState().router;
@ -68,4 +62,4 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
return action;
};
export default TrezorConnectService;
export default TrezorConnectService;

@ -13,17 +13,14 @@ import * as SendFormActions from 'actions/SendFormActions';
import * as DiscoveryActions from 'actions/DiscoveryActions';
import * as RouterActions from 'actions/RouterActions';
import type {
Middleware,
MiddlewareAPI,
MiddlewareDispatch,
Action,
} from 'flowtype';
import type { Middleware, MiddlewareAPI, MiddlewareDispatch, Action } from 'flowtype';
/**
* Middleware
*/
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => async (action: Action): Promise<Action> => {
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => async (
action: Action
): Promise<Action> => {
const prevState = api.getState();
// Application live cycle starts HERE!
// when first LOCATION_CHANGE is called router does not have "location" set yet
@ -88,7 +85,10 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
}
// watch for account change
if (prevLocation.state.network !== currentLocation.state.network || prevLocation.state.account !== currentLocation.state.account) {
if (
prevLocation.state.network !== currentLocation.state.network ||
prevLocation.state.account !== currentLocation.state.account
) {
api.dispatch(SelectedAccountActions.dispose());
}
@ -97,9 +97,9 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
}
// observe common values in WallerReducer
if (!await api.dispatch(WalletActions.observe(prevState, action))) {
if (!(await api.dispatch(WalletActions.observe(prevState, action)))) {
// if "selectedDevice" didn't change observe common values in SelectedAccountReducer
if (!await api.dispatch(SelectedAccountActions.observe(prevState, action))) {
if (!(await api.dispatch(SelectedAccountActions.observe(prevState, action)))) {
// if "selectedAccount" didn't change observe send form props changes
api.dispatch(SendFormActions.observe(prevState, action));
}
@ -119,7 +119,6 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
}
}
// even if "selectedDevice" didn't change because it was updated on DEVICE.CHANGED before DEVICE.CONNECT action
// try to restore discovery
if (action.type === DEVICE.CONNECT) {
@ -131,4 +130,4 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
return action;
};
export default WalletService;
export default WalletService;

@ -12,4 +12,4 @@ export default [
LocalStorageService,
TrezorConnectService,
CoingeckoService,
];
];

@ -24,20 +24,39 @@ import ru from 'react-intl/locale-data/ru';
import uk from 'react-intl/locale-data/uk';
import zh from 'react-intl/locale-data/zh';
addLocaleData([...en, ...cs, ...bn, ...de, ...el, ...es, ...fr, ...id, ...it, ...ja, ...nl, ...pl, ...pt, ...ru, ...uk, ...zh]);
addLocaleData([
...en,
...cs,
...bn,
...de,
...el,
...es,
...fr,
...id,
...it,
...ja,
...nl,
...pl,
...pt,
...ru,
...uk,
...zh,
]);
type OwnProps = {
children: React.Node
}
children: React.Node,
};
type StateProps = {
locale: string,
messages: { [string]: string }
}
messages: { [string]: string },
};
type Props = StateProps & OwnProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (
state: State
): StateProps => ({
locale: state.wallet.language,
messages: state.wallet.messages,
});
@ -52,4 +71,7 @@ const ReactIntlProvider = ({ children, locale, messages }: Props) => (
</IntlProvider>
);
export default connect(mapStateToProps, null)(ReactIntlProvider);
export default connect(
mapStateToProps,
null
)(ReactIntlProvider);

@ -24,4 +24,4 @@ ErrorBoundary.propTypes = {
children: PropTypes.node,
};
export default ErrorBoundary;
export default ErrorBoundary;

@ -16,10 +16,12 @@ class ImagesPreloader extends Component {
const images = this.importAll(require.context('../images', false, /\.(png|jpe?g)$/));
return (
<Wrapper>
{images.map(image => <Img key={image} src={image} />)}
{images.map(image => (
<Img key={image} src={image} />
))}
</Wrapper>
);
}
}
export default ImagesPreloader;
export default ImagesPreloader;

@ -1,10 +1,10 @@
/* @flow */
export type Route = {
+name: string;
+pattern: string;
fields: Array<string>;
}
+name: string,
+pattern: string,
fields: Array<string>,
};
export const routes: Array<Route> = [
{
@ -111,4 +111,4 @@ export const getPattern = (name: string): string => {
return '/';
}
return entry.pattern;
};
};

@ -1 +1 @@
global.fetch = require('jest-fetch-mock');
global.fetch = require('jest-fetch-mock');

@ -40,4 +40,4 @@ const animationStyles = css`
}
`;
export default animationStyles;
export default animationStyles;

@ -3,5 +3,6 @@
// process.env.BUILD is set by package.json
// process.env.NODE_ENV is set by webpack.mode
export const isDev = (): boolean => process.env.BUILD === 'development' || process.env.NODE_ENV === 'development';
export const isBeta = (): boolean => process.env.BUILD === 'beta';
export const isDev = (): boolean =>
process.env.BUILD === 'development' || process.env.NODE_ENV === 'development';
export const isBeta = (): boolean => process.env.BUILD === 'beta';

@ -13,7 +13,7 @@ export type parsedURI = {
const parseUri = (uri: string): ?parsedURI => {
const str = stripPrefix(uri);
const query: Array<string> = str.split('?');
const values: Object = (query.length > 1) ? parseQuery(query[1]) : {};
const values: Object = query.length > 1 ? parseQuery(query[1]) : {};
values.address = query[0];
return values;
@ -29,15 +29,15 @@ const stripPrefix = (str: string): string => {
};
// Parse URL query string (like 'foo=bar&baz=1337) into an object
const parseQuery = (str: string): {} => str.split('&')
.map(val => val.split('='))
.reduce((vals, pair) => {
if (pair.length > 1) {
vals[pair[0]] = pair[1];
}
return vals;
}, {});
const parseQuery = (str: string): {} =>
str
.split('&')
.map(val => val.split('='))
.reduce((vals, pair) => {
if (pair.length > 1) {
vals[pair[0]] = pair[1];
}
return vals;
}, {});
export {
parseUri,
};
export { parseUri };

@ -3,10 +3,7 @@
import colors from 'config/colors';
import type { Device } from 'trezor-connect';
import type {
TrezorDevice,
State,
} from 'flowtype';
import type { TrezorDevice, State } from 'flowtype';
type Transport = $ElementType<$ElementType<State, 'connect'>, 'transport'>;
@ -38,7 +35,8 @@ export const getStatus = (device: TrezorDevice): string => {
}
return 'connected';
}
if (!device.available) { // deprecated
if (!device.available) {
// deprecated
return 'unavailable';
}
if (device.type === 'unacquired') {
@ -79,9 +77,13 @@ export const getStatusName = (deviceStatus: string): string => {
}
};
export const isWebUSB = (transport: Transport) => !!((transport.type && transport.type === 'webusb'));
export const isWebUSB = (transport: Transport) => !!(transport.type && transport.type === 'webusb');
export const isDisabled = (selectedDevice: TrezorDevice, devices: Array<TrezorDevice>, transport: Transport) => {
export const isDisabled = (
selectedDevice: TrezorDevice,
devices: Array<TrezorDevice>,
transport: Transport
) => {
if (isWebUSB(transport)) return false; // always enabled if webusb
if (devices.length < 1) return true; // no devices
if (devices.length === 1) {
@ -97,7 +99,15 @@ export const isDeviceAccessible = (device: ?TrezorDevice): boolean => {
return device.mode === 'normal' && device.firmware !== 'required';
};
export const isSelectedDevice = (selected: ?TrezorDevice, device: ?(TrezorDevice | Device)): boolean => !!((selected && device && (selected.path === device.path && (device.ts && selected.instance === device.instance))));
export const isSelectedDevice = (
selected: ?TrezorDevice,
device: ?(TrezorDevice | Device)
): boolean =>
!!(
selected &&
device &&
(selected.path === device.path && (device.ts && selected.instance === device.instance))
);
export const getVersion = (device: TrezorDevice): string => {
let version;
@ -129,4 +139,4 @@ export const getStatusColor = (deviceStatus: string): string => {
default:
return colors.TEXT_PRIMARY;
}
};
};

@ -25,7 +25,8 @@ export const strip = (str: string): string => {
return padLeftEven(str);
};
export const calcGasPrice = (price: BigNumber, limit: string): string => price.times(limit).toString();
export const calcGasPrice = (price: BigNumber, limit: string): string =>
price.times(limit).toString();
export const validateAddress = (address: string): ?string => {
const hasUpperCase = new RegExp('^(.*[A-Z].*)$');
@ -44,4 +45,4 @@ export const validateAddress = (address: string): ?string => {
export const isHex = (str: string): boolean => {
const regExp = /^(0x|0X)?[0-9A-Fa-f]+$/g;
return regExp.test(str);
};
};

@ -8,4 +8,4 @@ export const getInitialLocale = (defaultLocale = 'en') => {
}
// if browser lang is not supported return en as default locale
return defaultLocale;
};
};

@ -8,7 +8,8 @@ export const httpRequest = async (url: string, type: string = 'text'): any => {
if (type === 'json') {
const txt: string = await response.text();
return JSON.parse(txt);
} if (type === 'binary') {
}
if (type === 'binary') {
await response.arrayBuffer();
}
await response.text();
@ -23,4 +24,4 @@ export const JSONRequest = async (url: string): Promise<JSON> => {
return JSON.parse(txt);
}
throw new Error(`jsonRequest error: ${response.toString()}`);
};
};

@ -1,7 +1,7 @@
import colors from 'config/colors';
import icons from 'config/icons';
const getPrimaryColor = (type) => {
const getPrimaryColor = type => {
let color;
switch (type) {
case 'info':
@ -23,7 +23,7 @@ const getPrimaryColor = (type) => {
return color;
};
const getSecondaryColor = (type) => {
const getSecondaryColor = type => {
let color;
switch (type) {
case 'info':
@ -47,8 +47,4 @@ const getSecondaryColor = (type) => {
const getIcon = type => icons[type.toUpperCase()];
export {
getPrimaryColor,
getSecondaryColor,
getIcon,
};
export { getPrimaryColor, getSecondaryColor, getIcon };

@ -1,5 +1,6 @@
/* @flow */
export const resolveAfter = (msec: number, value?: any): Promise<any> => new Promise((resolve) => {
window.setTimeout(resolve, msec, value);
});
export const resolveAfter = (msec: number, value?: any): Promise<any> =>
new Promise(resolve => {
window.setTimeout(resolve, msec, value);
});

@ -51,12 +51,16 @@ export const clearAll = (type: ?StorageType): void => {
try {
if (clearLocal) {
Object.keys(window.localStorage).forEach(key => key.indexOf(STORAGE_PATH) >= 0 && window.localStorage.removeItem(key));
Object.keys(window.localStorage).forEach(
key => key.indexOf(STORAGE_PATH) >= 0 && window.localStorage.removeItem(key)
);
}
if (clearSession) {
Object.keys(window.sessionStorage).forEach(key => key.indexOf(STORAGE_PATH) >= 0 && window.sessionStorage.removeItem(key));
Object.keys(window.sessionStorage).forEach(
key => key.indexOf(STORAGE_PATH) >= 0 && window.sessionStorage.removeItem(key)
);
}
} catch (error) {
console.error(`Clearing sessionStorage error: ${error}`);
}
};
};

@ -18,7 +18,4 @@ const getOldWalletReleaseUrl = (device: ?TrezorDevice): string => {
return `${url}?fw=${version}`;
};
export {
getOldWalletUrl,
getOldWalletReleaseUrl,
};
export { getOldWalletUrl, getOldWalletReleaseUrl };

@ -1,15 +1,14 @@
/* @flow */
export const getViewportHeight = (): number => (
export const getViewportHeight = (): number =>
// $FlowIssue: "clientHeight" missing in null
document.documentElement.clientHeight || document.body.clientHeight
);
document.documentElement.clientHeight || document.body.clientHeight;
export const getScrollX = (): number => {
if (window.pageXOffset !== undefined) {
return window.pageXOffset;
} if (window.scrollLeft !== undefined) {
}
if (window.scrollLeft !== undefined) {
return window.scrollLeft;
}
// $FlowIssue: parentNode || scrollLeft missing
@ -19,9 +18,10 @@ export const getScrollX = (): number => {
export const getScrollY = (): number => {
if (window.pageYOffset !== undefined) {
return window.pageYOffset;
} if (window.scrollTop !== undefined) {
}
if (window.scrollTop !== undefined) {
return window.scrollTop;
}
// $FlowIssue: parentNode || scrollTop missing
return (document.documentElement || document.body.parentNode || document.body).scrollTop;
};
};

@ -64,4 +64,4 @@ const definedMessages: Messages = defineMessages({
},
});
export default definedMessages;
export default definedMessages;

@ -49,21 +49,77 @@ const App = () => (
<ErrorBoundary>
<ImagesPreloader />
<WalletContainer>
<Route exact path={getPattern('wallet-settings')} component={WalletSettings} />
<Route exact path={getPattern('wallet-dashboard')} component={WalletDashboard} />
<Route exact path={getPattern('wallet-acquire')} component={WalletAcquire} />
<Route exact path={getPattern('wallet-unreadable')} component={WalletUnreadableDevice} />
<Route exact path={getPattern('wallet-bootloader')} component={WalletBootloader} />
<Route exact path={getPattern('wallet-initialize')} component={WalletInitialize} />
<Route exact path={getPattern('wallet-seedless')} component={WalletSeedless} />
<Route exact path={getPattern('wallet-firmware-update')} component={WalletFirmwareUpdate} />
<Route exact path={getPattern('wallet-backup')} component={WalletNoBackup} />
<Route exact path={getPattern('wallet-device-settings')} component={WalletDeviceSettings} />
<Route exact path={getPattern('wallet-account-summary')} component={AccountSummary} />
<Route path={getPattern('wallet-account-send')} component={AccountSend} />
<Route path={getPattern('wallet-account-send-override')} component={AccountSend} />
<Route path={getPattern('wallet-account-receive')} component={AccountReceive} />
<Route path={getPattern('wallet-account-signverify')} component={AccountSignVerify} />
<Route
exact
path={getPattern('wallet-settings')}
component={WalletSettings}
/>
<Route
exact
path={getPattern('wallet-dashboard')}
component={WalletDashboard}
/>
<Route
exact
path={getPattern('wallet-acquire')}
component={WalletAcquire}
/>
<Route
exact
path={getPattern('wallet-unreadable')}
component={WalletUnreadableDevice}
/>
<Route
exact
path={getPattern('wallet-bootloader')}
component={WalletBootloader}
/>
<Route
exact
path={getPattern('wallet-initialize')}
component={WalletInitialize}
/>
<Route
exact
path={getPattern('wallet-seedless')}
component={WalletSeedless}
/>
<Route
exact
path={getPattern('wallet-firmware-update')}
component={WalletFirmwareUpdate}
/>
<Route
exact
path={getPattern('wallet-backup')}
component={WalletNoBackup}
/>
<Route
exact
path={getPattern('wallet-device-settings')}
component={WalletDeviceSettings}
/>
<Route
exact
path={getPattern('wallet-account-summary')}
component={AccountSummary}
/>
<Route
path={getPattern('wallet-account-send')}
component={AccountSend}
/>
<Route
path={getPattern('wallet-account-send-override')}
component={AccountSend}
/>
<Route
path={getPattern('wallet-account-receive')}
component={AccountReceive}
/>
<Route
path={getPattern('wallet-account-signverify')}
component={AccountSignVerify}
/>
</WalletContainer>
</ErrorBoundary>
</Route>
@ -73,4 +129,4 @@ const App = () => (
</Provider>
);
export default hot(module)(App);
export default hot(module)(App);

Loading…
Cancel
Save