diff --git a/.babelrc b/.babelrc index f0a0c0bc..ca61982f 100644 --- a/.babelrc +++ b/.babelrc @@ -16,7 +16,10 @@ "regenerator": true }], ["module-resolver", { - "root": ["./src"] + "root": ["./src"], + "alias": { + "public": ["./public"] + } }], "babel-plugin-styled-components" ], diff --git a/src/actions/AccountsActions.js b/src/actions/AccountsActions.js index ba4dd1a6..2bc3a8f1 100644 --- a/src/actions/AccountsActions.js +++ b/src/actions/AccountsActions.js @@ -4,12 +4,6 @@ import * as ACCOUNT from 'actions/constants/account'; import type { Action, TrezorDevice } from 'flowtype'; import type { State } from 'reducers/AccountsReducer'; -export type AccountAction = - AccountFromStorageAction - | AccountCreateAction - | AccountSetBalanceAction - | AccountSetNonceAction; - export type AccountFromStorageAction = { type: typeof ACCOUNT.FROM_STORAGE, payload: State @@ -40,6 +34,12 @@ export type AccountSetNonceAction = { nonce: number } +export type AccountAction = + AccountFromStorageAction + | AccountCreateAction + | AccountSetBalanceAction + | AccountSetNonceAction; + export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => ({ type: ACCOUNT.SET_BALANCE, address, @@ -54,4 +54,4 @@ export const setNonce = (address: string, network: string, deviceState: string, network, deviceState, nonce, -}); \ No newline at end of file +}); diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index d72bfe60..b31534a0 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -1,11 +1,9 @@ /* @flow */ import TrezorConnect from 'trezor-connect'; -import HDKey from 'hdkey'; import EthereumjsUtil from 'ethereumjs-util'; import * as DISCOVERY from 'actions/constants/discovery'; import * as ACCOUNT from 'actions/constants/account'; -import * as TOKEN from 'actions/constants/token'; import * as NOTIFICATION from 'actions/constants/notification'; import type { ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice, @@ -13,18 +11,9 @@ import type { import type { Discovery, State } from 'reducers/DiscoveryReducer'; import * as AccountsActions from './AccountsActions'; -import { getNonceAsync, getBalanceAsync, getTokenBalanceAsync } from './Web3Actions'; -import { setBalance as setTokenBalance } from './TokenActions'; +import { getNonceAsync, getBalanceAsync } from './Web3Actions'; -export type DiscoveryAction = { - type: typeof DISCOVERY.FROM_STORAGE, - payload: State -} | DiscoveryStartAction - | DiscoveryWaitingAction - | DiscoveryStopAction - | DiscoveryCompleteAction; - export type DiscoveryStartAction = { type: typeof DISCOVERY.START, device: TrezorDevice, @@ -51,144 +40,29 @@ export type DiscoveryCompleteAction = { network: string } -export 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) { - console.error('Start discovery: requested device is not selected', device, selected); - return; - } if (!selected.state) { - console.warn("Start discovery: Selected device wasn't authenticated yet..."); - return; - } if (selected.connected && !selected.available) { - console.warn('Start discovery: Selected device is unavailable...'); - return; - } - - const web3 = getState().web3.find(w3 => w3.network === network); - if (!web3) { - console.error('Start discovery: Web3 does not exist', network); - return; - } - - if (!web3.web3.currentProvider.isConnected()) { - console.error('Start discovery: Web3 is not connected', network); - dispatch({ - type: DISCOVERY.WAITING_FOR_BACKEND, - device, - network, - }); - return; - } - - const discovery: State = getState().discovery; - const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network); - - - if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { - dispatch({ - type: DISCOVERY.WAITING_FOR_DEVICE, - device, - network, - }); - return; - } - - if (!discoveryProcess) { - dispatch(begin(device, network)); - } else if (discoveryProcess.completed && !ignoreCompleted) { - dispatch({ - type: DISCOVERY.COMPLETE, - device, - network, - }); - } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) { - // discovery cycle was interrupted - // start from beginning - dispatch(begin(device, network)); - } else { - dispatch(discoverAccount(device, discoveryProcess)); - } -}; - -const begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const { config } = getState().localStorage; - const coinToDiscover = config.coins.find(c => c.network === network); - if (!coinToDiscover) return; - - dispatch({ - type: DISCOVERY.WAITING_FOR_DEVICE, - device, - network, - }); - - // get xpub from TREZOR - const response = await TrezorConnect.getPublicKey({ - device: { - path: device.path, - instance: device.instance, - state: device.state, - }, - path: coinToDiscover.bip44, - keepSession: true, // acquire and hold session - useEmptyPassphrase: !device.instance, - }); - - // handle TREZOR response error - if (!response.success) { - // TODO: check message - console.warn('DISCOVERY ERROR', response); - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'error', - title: 'Discovery error', - message: response.payload.error, - cancelable: true, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch(start(device, network)); - }, - }, - ], - }, - }); - return; - } +export type DiscoveryAction = { + type: typeof DISCOVERY.FROM_STORAGE, + payload: State +} | DiscoveryStartAction + | DiscoveryWaitingAction + | DiscoveryStopAction + | DiscoveryCompleteAction; - // check for interruption - const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === device.state && d.network === network); - if (discoveryProcess && discoveryProcess.interrupted) return; - const basePath: Array = response.payload.path; +// Because start() is calling begin() and begin() is calling start() one of them must be declared first +// otherwise eslint will start complaining +let begin; - // send data to reducer - dispatch({ - type: DISCOVERY.START, - network: coinToDiscover.network, - device, - publicKey: response.payload.publicKey, - chainCode: response.payload.chainCode, - basePath, - }); - - dispatch(start(device, network)); -}; const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const completed: boolean = discoveryProcess.completed; + const { completed } = discoveryProcess; discoveryProcess.completed = false; const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`); const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex); const publicAddress: string = EthereumjsUtil.publicToAddress(derivedKey.publicKey, true).toString('hex'); const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress); - const network = discoveryProcess.network; + const { network } = discoveryProcess; // TODO: check if address was created before @@ -312,6 +186,135 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy } }; +export 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) { + console.error('Start discovery: requested device is not selected', device, selected); + return; + } if (!selected.state) { + console.warn("Start discovery: Selected device wasn't authenticated yet..."); + return; + } if (selected.connected && !selected.available) { + console.warn('Start discovery: Selected device is unavailable...'); + return; + } + + const web3 = getState().web3.find(w3 => w3.network === network); + if (!web3) { + console.error('Start discovery: Web3 does not exist', network); + return; + } + + if (!web3.web3.currentProvider.isConnected()) { + console.error('Start discovery: Web3 is not connected', network); + dispatch({ + type: DISCOVERY.WAITING_FOR_BACKEND, + device, + network, + }); + return; + } + + const { discovery }: { discovery: State } = getState(); + const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network); + + + if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { + dispatch({ + type: DISCOVERY.WAITING_FOR_DEVICE, + device, + network, + }); + return; + } + + if (!discoveryProcess) { + dispatch(begin(device, network)); + } else if (discoveryProcess.completed && !ignoreCompleted) { + dispatch({ + type: DISCOVERY.COMPLETE, + device, + network, + }); + } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) { + // discovery cycle was interrupted + // start from beginning + dispatch(begin(device, network)); + } else { + dispatch(discoverAccount(device, discoveryProcess)); + } +}; + +begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { config } = getState().localStorage; + const coinToDiscover = config.coins.find(c => c.network === network); + if (!coinToDiscover) return; + + dispatch({ + type: DISCOVERY.WAITING_FOR_DEVICE, + device, + network, + }); + + // get xpub from TREZOR + const response = await TrezorConnect.getPublicKey({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + path: coinToDiscover.bip44, + keepSession: true, // acquire and hold session + useEmptyPassphrase: !device.instance, + }); + + // handle TREZOR response error + if (!response.success) { + // TODO: check message + console.warn('DISCOVERY ERROR', response); + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Discovery error', + message: response.payload.error, + cancelable: true, + actions: [ + { + label: 'Try again', + callback: () => { + dispatch(start(device, network)); + }, + }, + ], + }, + }); + return; + } + + // check for interruption + const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === device.state && d.network === network); + if (discoveryProcess && discoveryProcess.interrupted) return; + + const basePath: Array = response.payload.path; + + // send data to reducer + dispatch({ + type: DISCOVERY.START, + network: coinToDiscover.network, + device, + publicKey: response.payload.publicKey, + chainCode: response.payload.chainCode, + basePath, + }); + + dispatch(start(device, network)); +}; + export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { const selected = getState().wallet.selectedDevice; diff --git a/src/actions/LocalStorageActions.js b/src/actions/LocalStorageActions.js index 1cce5ca9..ae7f7858 100644 --- a/src/actions/LocalStorageActions.js +++ b/src/actions/LocalStorageActions.js @@ -7,10 +7,10 @@ import * as TOKEN from 'actions/constants/token'; import * as DISCOVERY from 'actions/constants/discovery'; import * as STORAGE from 'actions/constants/localStorage'; import * as PENDING from 'actions/constants/pendingTx'; -import { JSONRequest, httpRequest } from 'utils/networkUtils'; +import { httpRequest } from 'utils/networkUtils'; import type { - ThunkAction, AsyncAction, GetState, Dispatch, TrezorDevice, + ThunkAction, AsyncAction, /* GetState, */ Dispatch, } from 'flowtype'; import type { Config, Coin, TokensCollection } from 'reducers/LocalStorageReducer'; @@ -30,59 +30,66 @@ export type StorageAction = { error: string, }; -export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { - // check if local storage is available - // let available: boolean = true; - // if (typeof window.localStorage === 'undefined') { - // available = false; - // } else { - // try { - // window.localStorage.setItem('ethereum_wallet', true); - // } catch (error) { - // available = false; - // } - // } - - dispatch(loadTokensFromJSON()); +export const get = (key: string): ?string => { + try { + return window.localStorage.getItem(key); + } catch (error) { + // available = false; + return null; + } }; -// const parseConfig = (json: JSON): Config => { - -// if (json['coins']) { - -// } +export function update(event: StorageEvent): AsyncAction { + return async (dispatch: Dispatch/* , getState: GetState */): Promise => { + if (!event.newValue) return; -// for (let key in json) { -// if (key === 'coins') { + if (event.key === 'devices') { + // check if device was added/ removed + // const newDevices: Array = JSON.parse(event.newValue); + // const myDevices: Array = 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 coins: Array = json.coins || []; + // const diff = oldDevices.filter(d => newDevices.indexOf()) + } -// if ("coins" in json){ -// json.coins -// } -// if (!json.hasOwnProperty("fiatValueTickers")) throw new Error(`Property "fiatValueTickers" is missing in appConfig.json`); -// if (json.config && json.hasOwnProperty('coins') && Array.isArray(json.coins)) { -// json.coins.map(c => { -// return { + if (event.key === 'accounts') { + dispatch({ + type: ACCOUNT.FROM_STORAGE, + payload: JSON.parse(event.newValue), + }); + } -// } -// }) -// } else { -// throw new Error(`Property "coins" is missing in appConfig.json`); -// } + if (event.key === 'tokens') { + dispatch({ + type: TOKEN.FROM_STORAGE, + payload: JSON.parse(event.newValue), + }); + } + if (event.key === 'pending') { + dispatch({ + type: PENDING.FROM_STORAGE, + payload: JSON.parse(event.newValue), + }); + } -// return { -// coins: [], -// fiatValueTickers: [] -// } -// } + if (event.key === 'discovery') { + dispatch({ + type: DISCOVERY.FROM_STORAGE, + payload: JSON.parse(event.newValue), + }); + } + }; +} export function loadTokensFromJSON(): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { + return async (dispatch: Dispatch): Promise => { if (typeof window.localStorage === 'undefined') return; try { @@ -157,57 +164,58 @@ export function loadTokensFromJSON(): AsyncAction { }; } -export function update(event: StorageEvent): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { - if (!event.newValue) return; +export const loadData = (): ThunkAction => (dispatch: Dispatch): void => { + // check if local storage is available + // let available: boolean = true; + // if (typeof window.localStorage === 'undefined') { + // available = false; + // } else { + // try { + // window.localStorage.setItem('ethereum_wallet', true); + // } catch (error) { + // available = false; + // } + // } - if (event.key === 'devices') { - // check if device was added/ removed - // const newDevices: Array = JSON.parse(event.newValue); - // const myDevices: Array = getState().connect.devices.filter(d => d.features); + dispatch(loadTokensFromJSON()); +}; - // 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 parseConfig = (json: JSON): Config => { - // const diff = oldDevices.filter(d => newDevices.indexOf()) - } +// if (json['coins']) { - if (event.key === 'accounts') { - dispatch({ - type: ACCOUNT.FROM_STORAGE, - payload: JSON.parse(event.newValue), - }); - } +// } - if (event.key === 'tokens') { - dispatch({ - type: TOKEN.FROM_STORAGE, - payload: JSON.parse(event.newValue), - }); - } +// for (let key in json) { +// if (key === 'coins') { - if (event.key === 'pending') { - dispatch({ - type: PENDING.FROM_STORAGE, - payload: JSON.parse(event.newValue), - }); - } +// } +// } - if (event.key === 'discovery') { - dispatch({ - type: DISCOVERY.FROM_STORAGE, - payload: JSON.parse(event.newValue), - }); - } - }; -} +// const coins: Array = json.coins || []; + +// if ("coins" in json){ +// json.coins +// } +// if (!json.hasOwnProperty("fiatValueTickers")) throw new Error(`Property "fiatValueTickers" is missing in appConfig.json`); +// if (json.config && json.hasOwnProperty('coins') && Array.isArray(json.coins)) { +// json.coins.map(c => { +// return { +// } +// }) +// } else { +// throw new Error(`Property "coins" is missing in appConfig.json`); +// } + + +// return { +// coins: [], +// fiatValueTickers: [] +// } +// } -export const save = (key: string, value: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { +export const save = (key: string, value: string): ThunkAction => (): void => { if (typeof window.localStorage !== 'undefined') { try { window.localStorage.setItem(key, value); @@ -216,13 +224,4 @@ export const save = (key: string, value: string): ThunkAction => (dispatch: Disp console.error(`Local Storage ERROR: ${error}`); } } -}; - -export const get = (key: string): ?string => { - try { - return window.localStorage.getItem(key); - } catch (error) { - // available = false; - return null; - } }; \ No newline at end of file diff --git a/src/actions/ModalActions.js b/src/actions/ModalActions.js index 7cc0fc96..1c0675af 100644 --- a/src/actions/ModalActions.js +++ b/src/actions/ModalActions.js @@ -1,6 +1,6 @@ /* @flow */ -import TrezorConnect, { UI, UI_EVENT } from 'trezor-connect'; +import TrezorConnect, { UI } from 'trezor-connect'; import type { Device } from 'trezor-connect'; import * as MODAL from 'actions/constants/modal'; import * as CONNECT from 'actions/constants/TrezorConnect'; @@ -24,14 +24,14 @@ export const onPinSubmit = (value: string): Action => { }; }; -export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const resp = await TrezorConnect.uiResponse({ - type: UI.RECEIVE_PASSPHRASE, - payload: { - value: passphrase, - save: true, - }, - }); +export const onPassphraseSubmit = (/* passphrase: string */): AsyncAction => async (dispatch: Dispatch): Promise => { + // const resp = await TrezorConnect.uiResponse({ + // type: UI.RECEIVE_PASSPHRASE, + // payload: { + // value: passphrase, + // save: true, + // }, + // }); dispatch({ type: MODAL.CLOSE, @@ -64,7 +64,7 @@ export const onCancel = (): Action => ({ type: MODAL.CLOSE, }); -export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { +export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => { dispatch(onCancel()); dispatch({ diff --git a/src/actions/ReceiveActions.js b/src/actions/ReceiveActions.js index 62b1b021..51b7f548 100644 --- a/src/actions/ReceiveActions.js +++ b/src/actions/ReceiveActions.js @@ -28,7 +28,7 @@ export type ReceiveAction = { type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS } -export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { +export const init = (): ThunkAction => (dispatch: Dispatch): void => { const state: State = { ...initialState, }; diff --git a/src/actions/SelectedAccountActions.js b/src/actions/SelectedAccountActions.js index 18056b55..cb426256 100644 --- a/src/actions/SelectedAccountActions.js +++ b/src/actions/SelectedAccountActions.js @@ -9,19 +9,13 @@ import * as PENDING from 'actions/constants/pendingTx'; import * as stateUtils from 'reducers/utils'; -import { initialState } from 'reducers/SelectedAccountReducer'; - import type { - Coin, - TrezorDevice, AsyncAction, - ThunkAction, Action, GetState, Dispatch, State, } from 'flowtype'; -import * as SessionStorageActions from './SessionStorageActions'; import * as SendFormActions from './SendFormActions'; @@ -32,13 +26,14 @@ export type SelectedAccountAction = { payload: $ElementType }; +export const dispose = (): Action => ({ + type: ACCOUNT.DISPOSE, +}); + export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { const locationChange: boolean = action.type === LOCATION_CHANGE; const state: State = getState(); - const location = state.router.location; - const prevLocation = prevState.router.location; - - const needUpdate: boolean = false; + const { location } = state.router; // reset form to default if (action.type === SEND.TX_COMPLETE) { @@ -126,7 +121,3 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct } } }; - -export const dispose = (): Action => ({ - type: ACCOUNT.DISPOSE, -}); \ No newline at end of file