From fe41a0d302a12bbde35e67c761b44d9637f98e1c Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Sun, 21 Oct 2018 16:31:45 +0200 Subject: [PATCH 1/5] Fixed underline --- src/views/Wallet/views/Account/Send/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/Wallet/views/Account/Send/index.js b/src/views/Wallet/views/Account/Send/index.js index f57bab8c..74f17322 100644 --- a/src/views/Wallet/views/Account/Send/index.js +++ b/src/views/Wallet/views/Account/Send/index.js @@ -105,6 +105,7 @@ const UpdateFeeWrapper = styled.span` const StyledLink = styled(Link)` margin-left: 4px; + white-space: nowrap; `; const ToggleAdvancedSettingsWrapper = styled.div` From a534830dfff08b4bce8bba3bc0ad323aa47d5a3a Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 22 Oct 2018 10:02:30 +0200 Subject: [PATCH 2/5] discovery status css fix - DiscoveryStatusWrapper css fix (wodth 100%) - rewriten first condition --- .../components/AccountMenu/index.js | 107 +++++++++--------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js b/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js index dfd38016..06d9a216 100644 --- a/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js @@ -78,7 +78,7 @@ const AddAccountIconWrapper = styled.div` const DiscoveryStatusWrapper = styled.div` display: flex; flex-direction: column; - + width: 100%; font-size: ${FONT_SIZE.SMALL}; padding: ${LEFT_NAVIGATION_ROW.PADDING}; white-space: nowrap; @@ -164,15 +164,34 @@ const AccountMenu = (props: Props) => { let discoveryStatus = null; const discovery = props.discovery.find(d => d.deviceState === selected.state && d.network === location.state.network); - if (discovery) { - if (discovery.completed) { - // TODO: add only if last one is not empty - //if (selectedAccounts.length > 0 && selectedAccounts[selectedAccounts.length - 1]) - const lastAccount = deviceAccounts[deviceAccounts.length - 1]; - if (lastAccount && (new BigNumber(lastAccount.balance).greaterThan(0) || lastAccount.nonce > 0)) { - discoveryStatus = ( - - + if (discovery && discovery.completed) { + // TODO: add only if last one is not empty + //if (selectedAccounts.length > 0 && selectedAccounts[selectedAccounts.length - 1]) + const lastAccount = deviceAccounts[deviceAccounts.length - 1]; + if (lastAccount && (new BigNumber(lastAccount.balance).greaterThan(0) || lastAccount.nonce > 0)) { + discoveryStatus = ( + + + + + + Add account + + + ); + } else { + discoveryStatus = ( + To add a new account, last account must have some transactions.} + placement="bottom" + > + + { Add account - ); - } else { - discoveryStatus = ( - To add a new account, last account must have some transactions.} - placement="bottom" - > - - - - - - Add account - - - - ); - } - } else if (!selected.connected || !selected.available) { - discoveryStatus = ( - - - Accounts could not be loaded - - {`Connect ${selected.instanceLabel} device`} - - - - ); - } else { - discoveryStatus = ( - - - - - Loading... - - - + ); } + } else if (!selected.connected) { + discoveryStatus = ( + + + Accounts could not be loaded + + {`Connect ${selected.instanceLabel} device`} + + + + ); + } else { + discoveryStatus = ( + + + + + Loading... + + + + ); } + return ( From 0c61bcb08de3f79712c6c6c570c43306376ead72 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 22 Oct 2018 10:04:19 +0200 Subject: [PATCH 3/5] local storage prefix - common prefix for localstorage and sessionstorage - code refactoring --- src/actions/LocalStorageActions.js | 84 ++++++++++++---------------- src/actions/SessionStorageActions.js | 63 +++++++++------------ src/utils/storage.js | 62 ++++++++++++++++++++ 3 files changed, 123 insertions(+), 86 deletions(-) create mode 100644 src/utils/storage.js diff --git a/src/actions/LocalStorageActions.js b/src/actions/LocalStorageActions.js index 49fbf0fa..786da55c 100644 --- a/src/actions/LocalStorageActions.js +++ b/src/actions/LocalStorageActions.js @@ -10,6 +10,7 @@ import * as PENDING from 'actions/constants/pendingTx'; import * as WALLET from 'actions/constants/wallet'; import { httpRequest } from 'utils/networkUtils'; import * as buildUtils from 'utils/build'; +import * as storageUtils from 'utils/storage'; import { findAccountTokens } from 'reducers/TokensReducer'; import type { Account } from 'reducers/AccountsReducer'; @@ -17,7 +18,6 @@ import type { Token } from 'reducers/TokensReducer'; import type { PendingTx } from 'reducers/PendingTxReducer'; import type { Discovery } from 'reducers/DiscoveryReducer'; - import type { TrezorDevice, ThunkAction, @@ -43,22 +43,15 @@ export type StorageAction = { error: string, }; -const get = (key: string): ?string => { - try { - return window.localStorage.getItem(key); - } catch (error) { - // available = false; - return null; - } -}; - -const set = (key: string, value: string | boolean): void => { - try { - window.localStorage.setItem(key, value); - } catch (error) { - console.error(`Local Storage ERROR: ${error}`); - } -}; +const TYPE: 'local' = 'local'; +const { STORAGE_PATH } = storageUtils; +const KEY_VERSION: string = `${STORAGE_PATH}version`; +const KEY_DEVICES: string = `${STORAGE_PATH}devices`; +const KEY_ACCOUNTS: string = `${STORAGE_PATH}accounts`; +const KEY_DISCOVERY: string = `${STORAGE_PATH}discovery`; +const KEY_TOKENS: string = `${STORAGE_PATH}tokens`; +const KEY_PENDING: string = `${STORAGE_PATH}pending`; +const KEY_BETA_MODAL: string = '/betaModalPrivacy'; // this key needs to be compatible with "parent" (old) wallet // https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js // or @@ -80,25 +73,25 @@ export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): const discovery: Array = findDiscovery(devices, getState().discovery); // save devices - set('devices', JSON.stringify(devices)); + storageUtils.set(TYPE, KEY_DEVICES, JSON.stringify(devices)); // save already preloaded accounts - set('accounts', JSON.stringify(accounts)); + storageUtils.set(TYPE, KEY_ACCOUNTS, JSON.stringify(accounts)); // save discovery state - set('discovery', JSON.stringify(discovery)); + storageUtils.set(TYPE, KEY_DISCOVERY, JSON.stringify(discovery)); // tokens - set('tokens', JSON.stringify(tokens)); + storageUtils.set(TYPE, KEY_TOKENS, JSON.stringify(tokens)); // pending transactions - set('pending', JSON.stringify(pending)); + storageUtils.set(TYPE, KEY_PENDING, JSON.stringify(pending)); }; export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch): void => { if (!event.newValue) return; - if (event.key === 'devices') { + if (event.key === 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); @@ -113,28 +106,28 @@ export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch) // const diff = oldDevices.filter(d => newDevices.indexOf()) } - if (event.key === 'accounts') { + if (event.key === KEY_ACCOUNTS) { dispatch({ type: ACCOUNT.FROM_STORAGE, payload: JSON.parse(event.newValue), }); } - if (event.key === 'tokens') { + if (event.key === KEY_TOKENS) { dispatch({ type: TOKEN.FROM_STORAGE, payload: JSON.parse(event.newValue), }); } - if (event.key === 'pending') { + if (event.key === KEY_PENDING) { dispatch({ type: PENDING.FROM_STORAGE, payload: JSON.parse(event.newValue), }); } - if (event.key === 'discovery') { + if (event.key === KEY_DISCOVERY) { dispatch({ type: DISCOVERY.FROM_STORAGE, payload: JSON.parse(event.newValue), @@ -142,14 +135,13 @@ export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch) } }; -const VERSION: string = '1'; - const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise => { if (typeof window.localStorage === 'undefined') return; try { const config: Config = await httpRequest(AppConfigJSON, 'json'); + // remove ropsten testnet from config networks if (!buildUtils.isDev()) { const index = config.networks.findIndex(c => c.shortcut === 'trop'); delete config.networks[index]; @@ -161,18 +153,6 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise => dispatch(update(event)); }); - // validate version - const version: ?string = get('version'); - if (version !== VERSION) { - try { - window.localStorage.clear(); - window.sessionStorage.clear(); - } catch (error) { - // empty - } - set('version', VERSION); - } - // load tokens const tokens = await config.networks.reduce(async (promise: Promise, network: Network): Promise => { const collection: TokensCollection = await promise; @@ -195,9 +175,17 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise => } }; +const VERSION: string = '1'; const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { - const devices: ?string = get('devices'); + // validate version + const version: ?string = storageUtils.get(TYPE, KEY_VERSION); + if (version && version !== VERSION) { + storageUtils.clearAll(); + } + storageUtils.set(TYPE, KEY_VERSION, VERSION); + + const devices: ?string = storageUtils.get(TYPE, KEY_DEVICES); if (devices) { dispatch({ type: CONNECT.DEVICE_FROM_STORAGE, @@ -205,7 +193,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { }); } - const accounts: ?string = get('accounts'); + const accounts: ?string = storageUtils.get(TYPE, KEY_ACCOUNTS); if (accounts) { dispatch({ type: ACCOUNT.FROM_STORAGE, @@ -213,7 +201,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { }); } - const userTokens: ?string = get('tokens'); + const userTokens: ?string = storageUtils.get(TYPE, KEY_TOKENS); if (userTokens) { dispatch({ type: TOKEN.FROM_STORAGE, @@ -221,7 +209,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { }); } - const pending: ?string = get('pending'); + const pending: ?string = storageUtils.get(TYPE, KEY_PENDING); if (pending) { dispatch({ type: PENDING.FROM_STORAGE, @@ -229,7 +217,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { }); } - const discovery: ?string = get('discovery'); + const discovery: ?string = storageUtils.get(TYPE, KEY_DISCOVERY); if (discovery) { dispatch({ type: DISCOVERY.FROM_STORAGE, @@ -238,7 +226,7 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { } if (buildUtils.isDev() || buildUtils.isBeta()) { - const betaModal = get('/betaModalPrivacy'); + const betaModal = Object.keys(window.localStorage).find(key => key.indexOf(KEY_BETA_MODAL) >= 0); if (!betaModal) { dispatch({ type: WALLET.SHOW_BETA_DISCLAIMER, @@ -258,6 +246,6 @@ export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetSta }; export const hideBetaDisclaimer = (): ThunkAction => (dispatch: Dispatch): void => { - set('/betaModalPrivacy', true); + storageUtils.set(TYPE, KEY_BETA_MODAL, true); dispatch(loadJSON()); }; diff --git a/src/actions/SessionStorageActions.js b/src/actions/SessionStorageActions.js index 33c953c8..e713a791 100644 --- a/src/actions/SessionStorageActions.js +++ b/src/actions/SessionStorageActions.js @@ -1,5 +1,5 @@ /* @flow */ - +import * as storageUtils from 'utils/storage'; import type { State as SendFormState } from 'reducers/SendFormReducer'; import type { @@ -9,52 +9,39 @@ import type { Dispatch, } from 'flowtype'; -const TX_PREFIX: string = 'trezor:draft-tx:'; +const TYPE: 'session' = 'session'; +const { STORAGE_PATH } = storageUtils; +const KEY_TX_DRAFT: string = `${STORAGE_PATH}txdraft`; + +const getTxDraftKey = (getState: GetState): string => { + const { pathname } = getState().router.location; + return `${KEY_TX_DRAFT}${pathname}`; +}; export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { - if (typeof window.localStorage === 'undefined') return; - const state = getState().sendForm; if (state.untouched) return; - const location = getState().router.location.pathname; - try { - // save state as it is - // "loadDraftTransaction" will do the validation - window.sessionStorage.setItem(`${TX_PREFIX}${location}`, JSON.stringify(state)); - } catch (error) { - console.error(`Saving sessionStorage error: ${error}`); - } + const key = getTxDraftKey(getState); + storageUtils.set(TYPE, key, JSON.stringify(state)); }; export const loadDraftTransaction = (): PayloadAction => (dispatch: Dispatch, getState: GetState): ?SendFormState => { - if (typeof window.localStorage === 'undefined') return null; - - try { - const location = getState().router.location.pathname; - const value: string = window.sessionStorage.getItem(`${TX_PREFIX}${location}`); - const state: ?SendFormState = JSON.parse(value); - if (state) { - // decide if draft is valid and should be returned - // ignore this draft if has any error - if (Object.keys(state.errors).length > 0) { - window.sessionStorage.removeItem(`${TX_PREFIX}${location}`); - return null; - } - return state; - } - } catch (error) { - console.error(`Loading sessionStorage error: ${error}`); + const key = getTxDraftKey(getState); + const value: ?string = storageUtils.get(TYPE, key); + if (!value) return null; + const state: ?SendFormState = JSON.parse(value); + if (!state) return null; + // decide if draft is valid and should be returned + // ignore this draft if has any error + if (Object.keys(state.errors).length > 0) { + storageUtils.remove(TYPE, key); + return null; } - return null; + return state; }; export const clear = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { - if (typeof window.localStorage === 'undefined') return; - const location = getState().router.location.pathname; - try { - window.sessionStorage.removeItem(`${TX_PREFIX}${location}`); - } catch (error) { - console.error(`Clearing sessionStorage error: ${error}`); - } -}; \ No newline at end of file + const key = getTxDraftKey(getState); + storageUtils.remove(TYPE, key); +}; diff --git a/src/utils/storage.js b/src/utils/storage.js new file mode 100644 index 00000000..dd89c7ef --- /dev/null +++ b/src/utils/storage.js @@ -0,0 +1,62 @@ +/* @flow */ + +// Copy-paste from mytrezor (old wallet) +// https://github.com/satoshilabs/mytrezor/blob/develop/app/scripts/storage/BackendLocalStorage.js +export const getStoragePath = (): string => { + const regexHash = /^([^#]*)#.*$/; + const path = window.location.href.replace(regexHash, '$1'); + const regexStart = /^[^:]*:\/\/[^/]*\//; + return path.replace(regexStart, '/'); +}; + +export const STORAGE_PATH: string = getStoragePath(); + +type StorageType = 'local' | 'session'; + +export const get: (type: StorageType, key: string) => T | null = (type, key) => { + const storage = type === 'local' ? window.localStorage : window.sessionStorage; + try { + return storage.getItem(key); + } catch (error) { + console.warn(`Get ${type} storage: ${error}`); + return null; + } +}; + +export const set = (type: StorageType, key: string, value: string | number | boolean): void => { + const storage = type === 'local' ? window.localStorage : window.sessionStorage; + try { + storage.setItem(key, value); + } catch (error) { + console.warn(`Save ${type} storage: ${error}`); + } +}; + +export const remove = (type: StorageType, key: string): void => { + const storage = type === 'local' ? window.localStorage : window.sessionStorage; + try { + storage.removeItem(key); + } catch (error) { + console.warn(`Remove ${type} storage: ${error}`); + } +}; + +export const clearAll = (type: ?StorageType): void => { + let clearLocal: boolean = true; + let clearSession: boolean = true; + if (typeof type === 'string') { + clearLocal = type === 'local'; + clearSession = !clearLocal; + } + + try { + if (clearLocal) { + 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)); + } + } catch (error) { + console.error(`Clearing sessionStorage error: ${error}`); + } +}; \ No newline at end of file From 16f830ff9d2d53dca733e8357fd497da0e5034aa Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 22 Oct 2018 10:07:00 +0200 Subject: [PATCH 4/5] fix for indicator first render --- .../TopNavigationAccount/components/Indicator/index.js | 6 +++--- src/views/Wallet/components/TopNavigationAccount/index.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/Wallet/components/TopNavigationAccount/components/Indicator/index.js b/src/views/Wallet/components/TopNavigationAccount/components/Indicator/index.js index 9a414a7d..0e5eda43 100644 --- a/src/views/Wallet/components/TopNavigationAccount/components/Indicator/index.js +++ b/src/views/Wallet/components/TopNavigationAccount/components/Indicator/index.js @@ -6,7 +6,7 @@ import React, { PureComponent } from 'react'; type Props = { pathname: string; - wrapper: ?HTMLElement; + wrapper: () => ?HTMLElement; } type State = { @@ -71,8 +71,8 @@ class Indicator extends PureComponent { handleResize: () => void; reposition(resetAnimation: boolean = true) { - if (!this.props.wrapper) return; - const { wrapper } = this.props; + const wrapper = this.props.wrapper(); + if (!wrapper) return; const active = wrapper.querySelector('.active'); if (!active) return; const bounds = active.getBoundingClientRect(); diff --git a/src/views/Wallet/components/TopNavigationAccount/index.js b/src/views/Wallet/components/TopNavigationAccount/index.js index 2d0f105e..05d52489 100644 --- a/src/views/Wallet/components/TopNavigationAccount/index.js +++ b/src/views/Wallet/components/TopNavigationAccount/index.js @@ -65,7 +65,7 @@ class TopNavigationAccount extends React.PureComponent { Receive Send {/* Sign & Verify */} - + this.wrapper} /> ); } From 95623474aaae5dfd496cbf2f289bede4aff6f874 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 22 Oct 2018 17:22:04 +0200 Subject: [PATCH 5/5] quickfix: deviceUtils test add missing "type" field + update ethUtils snapshot --- src/utils/__tests__/__snapshots__/ethUtils.test.js.snap | 2 +- src/utils/__tests__/device.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/__tests__/__snapshots__/ethUtils.test.js.snap b/src/utils/__tests__/__snapshots__/ethUtils.test.js.snap index f5b70d96..ad90b78c 100644 --- a/src/utils/__tests__/__snapshots__/ethUtils.test.js.snap +++ b/src/utils/__tests__/__snapshots__/ethUtils.test.js.snap @@ -34,7 +34,7 @@ exports[`eth utils sanitizeHex 3`] = `"0x02"`; exports[`eth utils sanitizeHex 4`] = `"0x0100"`; -exports[`eth utils sanitizeHex 5`] = `null`; +exports[`eth utils sanitizeHex 5`] = `"0x0999"`; exports[`eth utils sanitizeHex 6`] = `""`; diff --git a/src/utils/__tests__/device.test.js b/src/utils/__tests__/device.test.js index 68f8e2a2..78281035 100644 --- a/src/utils/__tests__/device.test.js +++ b/src/utils/__tests__/device.test.js @@ -40,9 +40,9 @@ describe('device utils', () => { it('isWebUSB', () => { const data = [ - { transport: { version: 'webusb' } }, - { transport: { version: 'aaaaaa' } }, - { transport: { version: 'webusb' } }, + { transport: { type: 'ParallelTransport', version: 'webusb' } }, + { transport: { type: null, version: 'aaaaaa' } }, + { transport: { type: 'ParallelTransport', version: 'webusb' } }, ]; data.forEach((item) => {