diff --git a/common/store/configAndTokens.ts b/common/store/configAndTokens.ts new file mode 100644 index 00000000..735ad5f1 --- /dev/null +++ b/common/store/configAndTokens.ts @@ -0,0 +1,156 @@ +import { State as ConfigState, config } from 'reducers/config'; +import { dedupeCustomTokens } from 'utils/tokens'; +import { + State as CustomTokenState, + INITIAL_STATE as customTokensInitialState +} from 'reducers/customTokens'; +import { loadStatePropertyOrEmptyObject } from 'utils/localStorage'; +import { + isStaticNodeId, + isStaticNetworkId, + getLanguageSelection, + getCustomNodeConfigs, + getSelectedNode, + getCustomNetworkConfigs, + getSelectedNetwork +} from 'selectors/config'; +import RootReducer, { AppState } from 'reducers'; +import CustomNode from 'libs/nodes/custom'; +import { CustomNodeConfig } from 'types/node'; +const appInitialState = RootReducer(undefined as any, { type: 'inital_state' }); + +type DeepPartial = { [P in keyof T]?: DeepPartial }; +export function getConfigAndCustomTokensStateToSubscribe( + state: AppState +): Pick, 'config' | 'customTokens'> { + const subscribedConfig: DeepPartial = { + meta: { languageSelection: getLanguageSelection(state) }, + nodes: { customNodes: getCustomNodeConfigs(state), selectedNode: getSelectedNode(state) }, + networks: { + customNetworks: getCustomNetworkConfigs(state), + selectedNetwork: getSelectedNetwork(state) + } + }; + + const subscribedTokens = state.customTokens; + + return { config: subscribedConfig, customTokens: subscribedTokens }; +} + +export function rehydrateConfigAndCustomTokenState() { + const configInitialState = config(undefined as any, { type: 'inital_state' }); + const savedConfigState = loadStatePropertyOrEmptyObject('config'); + const nextConfigState = { ...configInitialState }; + + // If they have a saved node, make sure we assign that too. The node selected + // isn't serializable, so we have to assign it here. + if (savedConfigState) { + // we assign networks first so that when we re-hydrate custom nodes, we can check that the network exists + nextConfigState.networks = rehydrateNetworks( + configInitialState.networks, + savedConfigState.networks + ); + nextConfigState.nodes = rehydrateNodes( + configInitialState.nodes, + savedConfigState.nodes, + nextConfigState.networks + ); + nextConfigState.meta = { ...nextConfigState.meta, ...savedConfigState.meta }; + } + + const nextCustomTokenState = rehydrateCustomTokens(nextConfigState.networks); + + return { config: nextConfigState, customTokens: nextCustomTokenState }; +} + +function rehydrateCustomTokens(networkState: ConfigState['networks']) { + // Dedupe custom tokens initially + const savedCustomTokensState = + loadStatePropertyOrEmptyObject('customTokens') || customTokensInitialState; + + const { customNetworks, selectedNetwork, staticNetworks } = networkState; + const network = isStaticNetworkId(appInitialState, selectedNetwork) + ? staticNetworks[selectedNetwork] + : customNetworks[selectedNetwork]; + return network.isCustom + ? savedCustomTokensState + : dedupeCustomTokens(network.tokens, savedCustomTokensState); +} + +function rehydrateNetworks( + initialState: ConfigState['networks'], + savedState: ConfigState['networks'] +): ConfigState['networks'] { + const nextNetworkState = { ...initialState }; + nextNetworkState.customNetworks = savedState.customNetworks; + const { customNetworks, selectedNetwork, staticNetworks } = nextNetworkState; + const nextSelectedNetwork = isStaticNetworkId(appInitialState, savedState.selectedNetwork) + ? staticNetworks[selectedNetwork] + : customNetworks[selectedNetwork]; + nextNetworkState.selectedNetwork = nextSelectedNetwork + ? savedState.selectedNetwork + : initialState.selectedNetwork; + return nextNetworkState; +} + +function rehydrateNodes( + initalState: ConfigState['nodes'], + savedState: ConfigState['nodes'], + networkState: ConfigState['networks'] +): ConfigState['nodes'] { + const nextNodeState = { ...initalState }; + + // re-assign the hydrated nodes + nextNodeState.customNodes = rehydrateCustomNodes(savedState.customNodes, networkState); + const { customNodes, staticNodes } = nextNodeState; + nextNodeState.selectedNode = getSavedSelectedNode( + nextNodeState.selectedNode, + savedState.selectedNode, + customNodes, + staticNodes + ); + return nextNodeState; +} + +function getSavedSelectedNode( + initialState: ConfigState['nodes']['selectedNode'], + savedState: ConfigState['nodes']['selectedNode'], + customNodes: ConfigState['nodes']['customNodes'], + staticNodes: ConfigState['nodes']['staticNodes'] +): ConfigState['nodes']['selectedNode'] { + const { nodeId: savedNodeId } = savedState; + + // if 'web3' has persisted as node selection, reset to app default + // necessary because web3 is only initialized as a node upon MetaMask / Mist unlock + + if (savedNodeId === 'web3') { + return { nodeId: initialState.nodeId, pending: false }; + } + + const nodeConfigExists = isStaticNodeId(appInitialState, savedNodeId) + ? staticNodes[savedNodeId] + : customNodes[savedNodeId]; + + return { nodeId: nodeConfigExists ? savedNodeId : initialState.nodeId, pending: false }; +} + +function rehydrateCustomNodes( + state: ConfigState['nodes']['customNodes'], + networkState: ConfigState['networks'] +) { + const networkExists = (networkId: string) => Object.keys(networkState).includes(networkId); + + const rehydratedCustomNodes = Object.entries(state).reduce( + (hydratedNodes, [customNodeId, configToHydrate]) => { + if (!networkExists(configToHydrate.network)) { + return hydratedNodes; + } + + const lib = new CustomNode(configToHydrate); + const hydratedNode: CustomNodeConfig = { ...configToHydrate, lib }; + return { ...hydratedNodes, [customNodeId]: hydratedNode }; + }, + {} as ConfigState['nodes']['customNodes'] + ); + return rehydratedCustomNodes; +} diff --git a/common/store/index.ts b/common/store/index.ts new file mode 100644 index 00000000..d4068169 --- /dev/null +++ b/common/store/index.ts @@ -0,0 +1 @@ +export * from './store'; diff --git a/common/store.ts b/common/store/store.ts similarity index 57% rename from common/store.ts rename to common/store/store.ts index 980f7274..52417b88 100644 --- a/common/store.ts +++ b/common/store/store.ts @@ -1,13 +1,5 @@ import throttle from 'lodash/throttle'; import { routerMiddleware } from 'react-router-redux'; - -/* -import { State as ConfigState, INITIAL_STATE as configInitialState } from 'reducers/config'; -*/ -import { - State as CustomTokenState, - INITIAL_STATE as customTokensInitialState -} from 'reducers/customTokens'; import { INITIAL_STATE as transactionInitialState, State as TransactionState @@ -18,11 +10,14 @@ import { composeWithDevTools } from 'redux-devtools-extension'; import { createLogger } from 'redux-logger'; import createSagaMiddleware from 'redux-saga'; import { loadStatePropertyOrEmptyObject, saveState } from 'utils/localStorage'; -import RootReducer from './reducers'; +import RootReducer from 'reducers'; import promiseMiddleware from 'redux-promise-middleware'; -import { dedupeCustomTokens } from 'utils/tokens'; -import sagas from './sagas'; +import sagas from 'sagas'; import { gasPricetoBase } from 'libs/units'; +import { + rehydrateConfigAndCustomTokenState, + getConfigAndCustomTokensStateToSubscribe +} from './configAndTokens'; const configureStore = () => { const logger = createLogger({ @@ -62,40 +57,8 @@ const configureStore = () => { : { ...swapInitialState }; const savedTransactionState = loadStatePropertyOrEmptyObject('transaction'); - const savedConfigState = loadStatePropertyOrEmptyObject('config'); - /* - // If they have a saved node, make sure we assign that too. The node selected - // isn't serializable, so we have to assign it here. - if (savedConfigState && savedConfigState.nodes.selectedNode.nodeName) { - const savedNode = getNodeConfigFromId( - savedConfigState.nodeSelection, - savedConfigState.customNodes - ); - // If we couldn't find it, revert to defaults - if (savedNode) { - savedConfigState.node = savedNode; - const network = getNetworkConfigFromId(savedNode.network, savedConfigState.customNetworks); - if (network) { - savedConfigState.network = network; - } - } else { - savedConfigState.nodeSelection = configInitialState.nodeSelection; - } - } - // Dedupe custom tokens initially - const savedCustomTokensState = - loadStatePropertyOrEmptyObject('customTokens') || customTokensInitialState; - const initialNetwork = - (savedConfigState && savedConfigState.network) || configInitialState.network; - const customTokens = dedupeCustomTokens(initialNetwork.tokens, savedCustomTokensState); -*/ const persistedInitialState = { - /* - config: { - ...configInitialState, - ...savedConfigState - },*/ transaction: { ...transactionInitialState, fields: { @@ -109,18 +72,12 @@ const configureStore = () => { : transactionInitialState.fields.gasPrice } }, - // customTokens, + // ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3 - swap: swapState + swap: swapState, + ...rehydrateConfigAndCustomTokenState() }; - // if 'web3' has persisted as node selection, reset to app default - // necessary because web3 is only initialized as a node upon MetaMask / Mist unlock - /* - if (persistedInitialState.config.nodeSelection === 'web3') { - persistedInitialState.config.nodeSelection = configInitialState.nodeSelection; - } -*/ store = createStore(RootReducer, persistedInitialState, middleware); // Add all of the sagas to the middleware @@ -132,14 +89,6 @@ const configureStore = () => { throttle(() => { const state = store.getState(); saveState({ - /* - config: { - nodeSelection: state.config.nodeSelection, - languageSelection: state.config.languageSelection, - customNodes: state.config.customNodes, - customNetworks: state.config.customNetworks, - setGasLimit: state.config.setGasLimit - },*/ transaction: { fields: { gasPrice: state.transaction.fields.gasPrice @@ -160,7 +109,7 @@ const configureStore = () => { allIds: [] } }, - customTokens: state.customTokens + ...getConfigAndCustomTokensStateToSubscribe(state) }); }, 50) );