mirror of
https://github.com/status-im/MyCrypto.git
synced 2025-01-11 03:26:14 +00:00
Fix up components to use selectors, work on fixing sagas
This commit is contained in:
parent
139cf405e7
commit
7fbe1966de
@ -24,10 +24,12 @@ export function changeLanguage(sign: string): interfaces.ChangeLanguageAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TChangeNode = typeof changeNode;
|
export type TChangeNode = typeof changeNode;
|
||||||
export function changeNode(networkName: string, nodeName: string): interfaces.ChangeNodeAction {
|
export function changeNode(
|
||||||
|
payload: interfaces.ChangeNodeAction['payload']
|
||||||
|
): interfaces.ChangeNodeAction {
|
||||||
return {
|
return {
|
||||||
type: TypeKeys.CONFIG_NODE_CHANGE,
|
type: TypeKeys.CONFIG_NODE_CHANGE,
|
||||||
payload: { networkName, nodeName }
|
payload
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,19 @@ import React from 'react';
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Modal, { IButton } from 'components/ui/Modal';
|
import Modal, { IButton } from 'components/ui/Modal';
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
import { NETWORKS, CustomNodeConfig, CustomNetworkConfig } from 'config';
|
|
||||||
import { makeCustomNodeId } from 'utils/node';
|
|
||||||
import { makeCustomNetworkId } from 'utils/network';
|
import { makeCustomNetworkId } from 'utils/network';
|
||||||
|
import { CustomNetworkConfig } from 'types/network';
|
||||||
|
import { CustomNodeConfig } from 'types/node';
|
||||||
|
import { TAddCustomNetwork, addCustomNetwork, AddCustomNodeAction } from 'actions/config';
|
||||||
|
import { connect, Omit } from 'react-redux';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
import {
|
||||||
|
getCustomNetworkConfigs,
|
||||||
|
getCustomNodeConfigs,
|
||||||
|
getStaticNetworkConfigs
|
||||||
|
} from 'selectors/config';
|
||||||
|
import { CustomNode } from 'libs/nodes';
|
||||||
|
|
||||||
const NETWORK_KEYS = Object.keys(NETWORKS);
|
|
||||||
const CUSTOM = 'custom';
|
const CUSTOM = 'custom';
|
||||||
|
|
||||||
interface Input {
|
interface Input {
|
||||||
@ -15,14 +23,21 @@ interface Input {
|
|||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface OwnProps {
|
||||||
customNodes: CustomNodeConfig[];
|
addCustomNode(payload: AddCustomNodeAction['payload']): void;
|
||||||
customNetworks: CustomNetworkConfig[];
|
|
||||||
handleAddCustomNode(node: CustomNodeConfig): void;
|
|
||||||
handleAddCustomNetwork(node: CustomNetworkConfig): void;
|
|
||||||
handleClose(): void;
|
handleClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
addCustomNetwork: TAddCustomNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
customNodes: AppState['config']['nodes']['customNodes'];
|
||||||
|
customNetworks: AppState['config']['networks']['customNetworks'];
|
||||||
|
staticNetworks: AppState['config']['networks']['staticNetworks'];
|
||||||
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
@ -36,12 +51,14 @@ interface State {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CustomNodeModal extends React.Component<Props, State> {
|
type Props = OwnProps & StateProps & DispatchProps;
|
||||||
|
|
||||||
|
class CustomNodeModal extends React.Component<Props, State> {
|
||||||
public state: State = {
|
public state: State = {
|
||||||
name: '',
|
name: '',
|
||||||
url: '',
|
url: '',
|
||||||
port: '',
|
port: '',
|
||||||
network: NETWORK_KEYS[0],
|
network: Object.keys(this.props.staticNetworks)[0],
|
||||||
customNetworkName: '',
|
customNetworkName: '',
|
||||||
customNetworkUnit: '',
|
customNetworkUnit: '',
|
||||||
customNetworkChainId: '',
|
customNetworkChainId: '',
|
||||||
@ -51,7 +68,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { customNetworks, handleClose } = this.props;
|
const { customNetworks, handleClose, staticNetworks } = this.props;
|
||||||
const { network } = this.state;
|
const { network } = this.state;
|
||||||
const isHttps = window.location.protocol.includes('https');
|
const isHttps = window.location.protocol.includes('https');
|
||||||
const invalids = this.getInvalids();
|
const invalids = this.getInvalids();
|
||||||
@ -109,12 +126,12 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
value={network}
|
value={network}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
>
|
>
|
||||||
{NETWORK_KEYS.map(net => (
|
{Object.keys(staticNetworks).map(net => (
|
||||||
<option key={net} value={net}>
|
<option key={net} value={net}>
|
||||||
{net}
|
{net}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
{customNetworks.map(net => {
|
{Object.values(customNetworks).map(net => {
|
||||||
const id = makeCustomNetworkId(net);
|
const id = makeCustomNetworkId(net);
|
||||||
return (
|
return (
|
||||||
<option key={id} value={id}>
|
<option key={id} value={id}>
|
||||||
@ -173,7 +190,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
placeholder: 'http://127.0.0.1/'
|
placeholder: 'http://127.0.0.1/'
|
||||||
},
|
},
|
||||||
invalids
|
invalids
|
||||||
)}
|
)}node
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-3">
|
<div className="col-sm-3">
|
||||||
@ -303,12 +320,13 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private makeCustomNetworkConfigFromState(): CustomNetworkConfig {
|
private makeCustomNetworkConfigFromState(): CustomNetworkConfig {
|
||||||
const similarNetworkConfig = Object.values(NETWORKS).find(
|
const similarNetworkConfig = Object.values(this.props.staticNetworks).find(
|
||||||
n => n.chainId === +this.state.customNetworkChainId
|
n => n.chainId === +this.state.customNetworkChainId
|
||||||
);
|
);
|
||||||
const dPathFormats = similarNetworkConfig ? similarNetworkConfig.dPathFormats : null;
|
const dPathFormats = similarNetworkConfig ? similarNetworkConfig.dPathFormats : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isCustom: true,
|
||||||
name: this.state.customNetworkName,
|
name: this.state.customNetworkName,
|
||||||
unit: this.state.customNetworkUnit,
|
unit: this.state.customNetworkUnit,
|
||||||
chainId: this.state.customNetworkChainId ? parseInt(this.state.customNetworkChainId, 10) : 0,
|
chainId: this.state.customNetworkChainId ? parseInt(this.state.customNetworkChainId, 10) : 0,
|
||||||
@ -318,14 +336,22 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
|
|
||||||
private makeCustomNodeConfigFromState(): CustomNodeConfig {
|
private makeCustomNodeConfigFromState(): CustomNodeConfig {
|
||||||
const { network } = this.state;
|
const { network } = this.state;
|
||||||
const node: CustomNodeConfig = {
|
const networkId =
|
||||||
|
network === CUSTOM ? makeCustomNetworkId(this.makeCustomNetworkConfigFromState()) : network;
|
||||||
|
|
||||||
|
const port = parseInt(this.state.port, 10);
|
||||||
|
const url = this.state.url.trim();
|
||||||
|
const node: Omit<CustomNodeConfig, 'lib'> = {
|
||||||
|
isCustom: true,
|
||||||
|
service: 'your custom node',
|
||||||
|
id: `${url}:${port}`,
|
||||||
name: this.state.name.trim(),
|
name: this.state.name.trim(),
|
||||||
url: this.state.url.trim(),
|
url,
|
||||||
port: parseInt(this.state.port, 10),
|
port,
|
||||||
network:
|
network: networkId
|
||||||
network === CUSTOM ? makeCustomNetworkId(this.makeCustomNetworkConfigFromState()) : network
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const lib = new CustomNode(node);
|
||||||
if (this.state.hasAuth) {
|
if (this.state.hasAuth) {
|
||||||
node.auth = {
|
node.auth = {
|
||||||
username: this.state.username,
|
username: this.state.username,
|
||||||
@ -333,14 +359,14 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return { ...node, lib };
|
||||||
}
|
}
|
||||||
|
|
||||||
private getConflictedNode(): CustomNodeConfig | undefined {
|
private getConflictedNode(): CustomNodeConfig | undefined {
|
||||||
const { customNodes } = this.props;
|
const { customNodes } = this.props;
|
||||||
const config = this.makeCustomNodeConfigFromState();
|
const config = this.makeCustomNodeConfigFromState();
|
||||||
const thisId = makeCustomNodeId(config);
|
|
||||||
return customNodes.find(conf => makeCustomNodeId(conf) === thisId);
|
return customNodes[config.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleChange = (ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>) => {
|
private handleChange = (ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||||
@ -359,9 +385,21 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
if (this.state.network === CUSTOM) {
|
if (this.state.network === CUSTOM) {
|
||||||
const network = this.makeCustomNetworkConfigFromState();
|
const network = this.makeCustomNetworkConfigFromState();
|
||||||
|
|
||||||
this.props.handleAddCustomNetwork(network);
|
this.props.addCustomNetwork({ config: network, id: node.network });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.handleAddCustomNode(node);
|
this.props.addCustomNode({ config: node, id: node.id });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: AppState): StateProps => ({
|
||||||
|
customNetworks: getCustomNetworkConfigs(state),
|
||||||
|
customNodes: getCustomNodeConfigs(state),
|
||||||
|
staticNetworks: getStaticNetworkConfigs(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps: DispatchProps = {
|
||||||
|
addCustomNetwork
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(CustomNodeModal);
|
||||||
|
@ -46,7 +46,7 @@ const tabs: TabLink[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
color?: string;
|
color?: string | false;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -3,40 +3,44 @@ import {
|
|||||||
TChangeNodeIntent,
|
TChangeNodeIntent,
|
||||||
TAddCustomNode,
|
TAddCustomNode,
|
||||||
TRemoveCustomNode,
|
TRemoveCustomNode,
|
||||||
TAddCustomNetwork
|
TAddCustomNetwork,
|
||||||
|
AddCustomNodeAction,
|
||||||
|
changeLanguage,
|
||||||
|
changeNodeIntent,
|
||||||
|
addCustomNode,
|
||||||
|
removeCustomNode,
|
||||||
|
addCustomNetwork
|
||||||
} from 'actions/config';
|
} from 'actions/config';
|
||||||
import logo from 'assets/images/logo-myetherwallet.svg';
|
import logo from 'assets/images/logo-myetherwallet.svg';
|
||||||
import { Dropdown, ColorDropdown } from 'components/ui';
|
import { Dropdown, ColorDropdown } from 'components/ui';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { TSetGasPriceField } from 'actions/transaction';
|
import { TSetGasPriceField, setGasPriceField } from 'actions/transaction';
|
||||||
import {
|
import { ANNOUNCEMENT_MESSAGE, ANNOUNCEMENT_TYPE, languages } from 'config';
|
||||||
ANNOUNCEMENT_MESSAGE,
|
|
||||||
ANNOUNCEMENT_TYPE,
|
|
||||||
languages,
|
|
||||||
NODES,
|
|
||||||
NodeConfig,
|
|
||||||
CustomNodeConfig,
|
|
||||||
CustomNetworkConfig
|
|
||||||
} from 'config';
|
|
||||||
import Navigation from './components/Navigation';
|
import Navigation from './components/Navigation';
|
||||||
import CustomNodeModal from './components/CustomNodeModal';
|
import CustomNodeModal from './components/CustomNodeModal';
|
||||||
import OnlineStatus from './components/OnlineStatus';
|
import OnlineStatus from './components/OnlineStatus';
|
||||||
import Version from './components/Version';
|
import Version from './components/Version';
|
||||||
import { getKeyByValue } from 'utils/helpers';
|
import { getKeyByValue } from 'utils/helpers';
|
||||||
import { makeCustomNodeId } from 'utils/node';
|
import { NodeConfig } from 'types/node';
|
||||||
import { getNetworkConfigFromId } from 'utils/network';
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
import {
|
||||||
|
getOffline,
|
||||||
|
isNodeChanging,
|
||||||
|
getLanguageSelection,
|
||||||
|
getNodeName,
|
||||||
|
getNodeConfig,
|
||||||
|
CustomNodeOption,
|
||||||
|
NodeOption,
|
||||||
|
getNodeOptions,
|
||||||
|
getNetworkConfig
|
||||||
|
} from 'selectors/config';
|
||||||
|
import { NetworkConfig } from 'types/network';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
interface Props {
|
interface DispatchProps {
|
||||||
languageSelection: string;
|
|
||||||
node: NodeConfig;
|
|
||||||
nodeSelection: string;
|
|
||||||
isChangingNode: boolean;
|
|
||||||
isOffline: boolean;
|
|
||||||
customNodes: CustomNodeConfig[];
|
|
||||||
customNetworks: CustomNetworkConfig[];
|
|
||||||
changeLanguage: TChangeLanguage;
|
changeLanguage: TChangeLanguage;
|
||||||
changeNodeIntent: TChangeNodeIntent;
|
changeNodeIntent: TChangeNodeIntent;
|
||||||
setGasPriceField: TSetGasPriceField;
|
setGasPriceField: TSetGasPriceField;
|
||||||
@ -45,11 +49,42 @@ interface Props {
|
|||||||
addCustomNetwork: TAddCustomNetwork;
|
addCustomNetwork: TAddCustomNetwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
network: NetworkConfig;
|
||||||
|
languageSelection: AppState['config']['meta']['languageSelection'];
|
||||||
|
node: NodeConfig;
|
||||||
|
nodeSelection: AppState['config']['nodes']['selectedNode']['nodeName'];
|
||||||
|
isChangingNode: AppState['config']['nodes']['selectedNode']['pending'];
|
||||||
|
isOffline: AppState['config']['meta']['offline'];
|
||||||
|
nodeOptions: (CustomNodeOption | NodeOption)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: AppState): StateProps => ({
|
||||||
|
isOffline: getOffline(state),
|
||||||
|
isChangingNode: isNodeChanging(state),
|
||||||
|
languageSelection: getLanguageSelection(state),
|
||||||
|
nodeSelection: getNodeName(state),
|
||||||
|
node: getNodeConfig(state),
|
||||||
|
nodeOptions: getNodeOptions(state),
|
||||||
|
network: getNetworkConfig(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps: DispatchProps = {
|
||||||
|
setGasPriceField,
|
||||||
|
changeLanguage,
|
||||||
|
changeNodeIntent,
|
||||||
|
addCustomNode,
|
||||||
|
removeCustomNode,
|
||||||
|
addCustomNetwork
|
||||||
|
};
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
isAddingCustomNode: boolean;
|
isAddingCustomNode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Header extends Component<Props, State> {
|
type Props = StateProps & DispatchProps;
|
||||||
|
|
||||||
|
class Header extends Component<Props, State> {
|
||||||
public state = {
|
public state = {
|
||||||
isAddingCustomNode: false
|
isAddingCustomNode: false
|
||||||
};
|
};
|
||||||
@ -57,50 +92,40 @@ export default class Header extends Component<Props, State> {
|
|||||||
public render() {
|
public render() {
|
||||||
const {
|
const {
|
||||||
languageSelection,
|
languageSelection,
|
||||||
changeNodeIntent,
|
|
||||||
node,
|
node,
|
||||||
nodeSelection,
|
nodeSelection,
|
||||||
isChangingNode,
|
isChangingNode,
|
||||||
isOffline,
|
isOffline,
|
||||||
customNodes,
|
nodeOptions,
|
||||||
customNetworks
|
network
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isAddingCustomNode } = this.state;
|
const { isAddingCustomNode } = this.state;
|
||||||
const selectedLanguage = languageSelection;
|
const selectedLanguage = languageSelection;
|
||||||
const selectedNetwork = getNetworkConfigFromId(node.network, customNetworks);
|
|
||||||
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
|
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
|
||||||
|
const options = nodeOptions.map(n => {
|
||||||
const nodeOptions = Object.keys(NODES)
|
if (n.isCustom) {
|
||||||
.map(key => {
|
const { name: { networkName, nodeName }, isCustom, id, ...rest } = n;
|
||||||
const n = NODES[key];
|
|
||||||
const network = getNetworkConfigFromId(n.network, customNetworks);
|
|
||||||
return {
|
return {
|
||||||
value: key,
|
...rest,
|
||||||
name: (
|
name: (
|
||||||
<span>
|
<span>
|
||||||
{network && network.name} <small>({n.service})</small>
|
{networkName} - {nodeName} <small>(custom)</small>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
color: network && network.color,
|
onRemove: () => this.props.removeCustomNode({ id })
|
||||||
hidden: n.hidden
|
|
||||||
};
|
};
|
||||||
})
|
} else {
|
||||||
.concat(
|
const { name: { networkName, service }, isCustom, ...rest } = n;
|
||||||
customNodes.map(cn => {
|
|
||||||
const network = getNetworkConfigFromId(cn.network, customNetworks);
|
|
||||||
return {
|
return {
|
||||||
value: makeCustomNodeId(cn),
|
...rest,
|
||||||
name: (
|
name: (
|
||||||
<span>
|
<span>
|
||||||
{network && network.name} - {cn.name} <small>(custom)</small>
|
{networkName} <small>({service})</small>
|
||||||
</span>
|
</span>
|
||||||
),
|
)
|
||||||
color: network && network.color,
|
|
||||||
hidden: false,
|
|
||||||
onRemove: () => this.props.removeCustomNode(cn)
|
|
||||||
};
|
};
|
||||||
})
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Header">
|
<div className="Header">
|
||||||
@ -162,8 +187,8 @@ export default class Header extends Component<Props, State> {
|
|||||||
change node. current node is on the ${node.network} network
|
change node. current node is on the ${node.network} network
|
||||||
provided by ${node.service}
|
provided by ${node.service}
|
||||||
`}
|
`}
|
||||||
options={nodeOptions}
|
options={options}
|
||||||
value={nodeSelection}
|
value={nodeSelection || ''}
|
||||||
extra={
|
extra={
|
||||||
<li>
|
<li>
|
||||||
<a onClick={this.openCustomNodeModal}>Add Custom Node</a>
|
<a onClick={this.openCustomNodeModal}>Add Custom Node</a>
|
||||||
@ -180,14 +205,11 @@ export default class Header extends Component<Props, State> {
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Navigation color={selectedNetwork && selectedNetwork.color} />
|
<Navigation color={!network.isCustom && network.color} />
|
||||||
|
|
||||||
{isAddingCustomNode && (
|
{isAddingCustomNode && (
|
||||||
<CustomNodeModal
|
<CustomNodeModal
|
||||||
customNodes={customNodes}
|
addCustomNode={this.addCustomNode}
|
||||||
customNetworks={customNetworks}
|
|
||||||
handleAddCustomNode={this.addCustomNode}
|
|
||||||
handleAddCustomNetwork={this.props.addCustomNetwork}
|
|
||||||
handleClose={this.closeCustomNodeModal}
|
handleClose={this.closeCustomNodeModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -210,8 +232,10 @@ export default class Header extends Component<Props, State> {
|
|||||||
this.setState({ isAddingCustomNode: false });
|
this.setState({ isAddingCustomNode: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
private addCustomNode = (node: CustomNodeConfig) => {
|
private addCustomNode = (payload: AddCustomNodeAction['payload']) => {
|
||||||
this.setState({ isAddingCustomNode: false });
|
this.setState({ isAddingCustomNode: false });
|
||||||
this.props.addCustomNode(node);
|
this.props.addCustomNode(payload);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Header);
|
||||||
|
@ -8,7 +8,7 @@ interface Option<T> {
|
|||||||
name: any;
|
name: any;
|
||||||
value: T;
|
value: T;
|
||||||
color?: string;
|
color?: string;
|
||||||
hidden: boolean | undefined;
|
hidden?: boolean | undefined;
|
||||||
onRemove?(): void;
|
onRemove?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,51 +1,31 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
|
||||||
changeLanguage as dChangeLanguage,
|
|
||||||
changeNodeIntent as dChangeNodeIntent,
|
|
||||||
addCustomNode as dAddCustomNode,
|
|
||||||
removeCustomNode as dRemoveCustomNode,
|
|
||||||
addCustomNetwork as dAddCustomNetwork,
|
|
||||||
TChangeLanguage,
|
|
||||||
TChangeNodeIntent,
|
|
||||||
TAddCustomNode,
|
|
||||||
TRemoveCustomNode,
|
|
||||||
TAddCustomNetwork
|
|
||||||
} from 'actions/config';
|
|
||||||
import { TSetGasPriceField, setGasPriceField as dSetGasPriceField } from 'actions/transaction';
|
|
||||||
import { AlphaAgreement, Footer, Header } from 'components';
|
import { AlphaAgreement, Footer, Header } from 'components';
|
||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
import Notifications from './Notifications';
|
import Notifications from './Notifications';
|
||||||
import OfflineTab from './OfflineTab';
|
import OfflineTab from './OfflineTab';
|
||||||
import {
|
import { getOffline, getLatestBlock } from 'selectors/config';
|
||||||
getOffline,
|
|
||||||
getLanguageSelection,
|
|
||||||
getCustomNodeConfigs,
|
|
||||||
getCustomNetworkConfigs,
|
|
||||||
getLatestBlock,
|
|
||||||
isNodeChanging
|
|
||||||
} from 'selectors/config';
|
|
||||||
import { NodeConfig, CustomNodeConfig } from 'types/node';
|
|
||||||
import { CustomNetworkConfig } from 'types/network';
|
|
||||||
|
|
||||||
interface Props {
|
interface StateProps {
|
||||||
|
isOffline: AppState['config']['meta']['offline'];
|
||||||
|
latestBlock: AppState['config']['meta']['latestBlock'];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OwnProps {
|
||||||
isUnavailableOffline?: boolean;
|
isUnavailableOffline?: boolean;
|
||||||
children: string | React.ReactElement<string> | React.ReactElement<string>[];
|
children: string | React.ReactElement<string> | React.ReactElement<string>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & StateProps;
|
||||||
|
|
||||||
class TabSection extends Component<Props, {}> {
|
class TabSection extends Component<Props, {}> {
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const { isUnavailableOffline, children, isOffline, latestBlock } = this.props;
|
||||||
isUnavailableOffline,
|
|
||||||
children,
|
|
||||||
// APP
|
|
||||||
isOffline
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-layout">
|
<div className="page-layout">
|
||||||
<main>
|
<main>
|
||||||
<Header {...headerProps} />
|
<Header />
|
||||||
<div className="Tab container">
|
<div className="Tab container">
|
||||||
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
|
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
|
||||||
</div>
|
</div>
|
||||||
@ -58,15 +38,9 @@ class TabSection extends Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: AppState): ReduxProps {
|
function mapStateToProps(state: AppState): StateProps {
|
||||||
return {
|
return {
|
||||||
node: state.config.node,
|
|
||||||
nodeSelection: state.config.nodeSelection,
|
|
||||||
isChangingNode: isNodeChanging(state),
|
|
||||||
isOffline: getOffline(state),
|
isOffline: getOffline(state),
|
||||||
languageSelection: getLanguageSelection(state),
|
|
||||||
customNodes: getCustomNodeConfigs(state),
|
|
||||||
customNetworks: getCustomNetworkConfigs(state),
|
|
||||||
latestBlock: getLatestBlock(state)
|
latestBlock: getLatestBlock(state)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import RPCNode from '../rpc';
|
import RPCNode from '../rpc';
|
||||||
import RPCClient from '../rpc/client';
|
import RPCClient from '../rpc/client';
|
||||||
import { CustomNodeConfig } from 'types/node';
|
import { CustomNodeConfig } from 'types/node';
|
||||||
|
import { Omit } from 'react-router';
|
||||||
|
|
||||||
export default class CustomNode extends RPCNode {
|
export default class CustomNode extends RPCNode {
|
||||||
constructor(config: CustomNodeConfig) {
|
constructor(config: Omit<CustomNodeConfig, 'lib'>) {
|
||||||
const endpoint = `${config.url}:${config.port}`;
|
super(config.id);
|
||||||
super(endpoint);
|
|
||||||
|
|
||||||
const headers: { [key: string]: string } = {};
|
const headers: { [key: string]: string } = {};
|
||||||
if (config.auth) {
|
if (config.auth) {
|
||||||
@ -13,6 +13,6 @@ export default class CustomNode extends RPCNode {
|
|||||||
headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new RPCClient(endpoint, headers);
|
this.client = new RPCClient(config.id, headers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ interface NodeLoaded {
|
|||||||
|
|
||||||
interface NodeChangePending {
|
interface NodeChangePending {
|
||||||
pending: true;
|
pending: true;
|
||||||
nodeName: undefined;
|
nodeName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = NodeLoaded | NodeChangePending;
|
export type State = NodeLoaded | NodeChangePending;
|
||||||
@ -22,8 +22,8 @@ const changeNode = (_: State, { payload }: ChangeNodeAction): State => ({
|
|||||||
pending: false
|
pending: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const changeNodeIntent = (_: State, _2: ChangeNodeIntentAction): State => ({
|
const changeNodeIntent = (state: State, _: ChangeNodeIntentAction): State => ({
|
||||||
nodeName: undefined,
|
...state,
|
||||||
pending: true
|
pending: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,72 +7,84 @@ export type State = NonWeb3NodeConfigs & Web3NodeConfig;
|
|||||||
export const INITIAL_STATE: State = {
|
export const INITIAL_STATE: State = {
|
||||||
eth_mew: {
|
eth_mew: {
|
||||||
network: 'ETH',
|
network: 'ETH',
|
||||||
|
isCustom: false,
|
||||||
lib: new RPCNode('https://api.myetherapi.com/eth'),
|
lib: new RPCNode('https://api.myetherapi.com/eth'),
|
||||||
service: 'MyEtherWallet',
|
service: 'MyEtherWallet',
|
||||||
estimateGas: true
|
estimateGas: true
|
||||||
},
|
},
|
||||||
eth_mycrypto: {
|
eth_mycrypto: {
|
||||||
network: 'ETH',
|
network: 'ETH',
|
||||||
|
isCustom: false,
|
||||||
lib: new RPCNode('https://api.mycryptoapi.com/eth'),
|
lib: new RPCNode('https://api.mycryptoapi.com/eth'),
|
||||||
service: 'MyCrypto',
|
service: 'MyCrypto',
|
||||||
estimateGas: true
|
estimateGas: true
|
||||||
},
|
},
|
||||||
eth_ethscan: {
|
eth_ethscan: {
|
||||||
network: 'ETH',
|
network: 'ETH',
|
||||||
|
isCustom: false,
|
||||||
service: 'Etherscan.io',
|
service: 'Etherscan.io',
|
||||||
lib: new EtherscanNode('https://api.etherscan.io/api'),
|
lib: new EtherscanNode('https://api.etherscan.io/api'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
eth_infura: {
|
eth_infura: {
|
||||||
network: 'ETH',
|
network: 'ETH',
|
||||||
|
isCustom: false,
|
||||||
service: 'infura.io',
|
service: 'infura.io',
|
||||||
lib: new InfuraNode('https://mainnet.infura.io/mew'),
|
lib: new InfuraNode('https://mainnet.infura.io/mew'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
rop_mew: {
|
rop_mew: {
|
||||||
network: 'Ropsten',
|
network: 'Ropsten',
|
||||||
|
isCustom: false,
|
||||||
service: 'MyEtherWallet',
|
service: 'MyEtherWallet',
|
||||||
lib: new RPCNode('https://api.myetherapi.com/rop'),
|
lib: new RPCNode('https://api.myetherapi.com/rop'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
rop_infura: {
|
rop_infura: {
|
||||||
network: 'Ropsten',
|
network: 'Ropsten',
|
||||||
|
isCustom: false,
|
||||||
service: 'infura.io',
|
service: 'infura.io',
|
||||||
lib: new InfuraNode('https://ropsten.infura.io/mew'),
|
lib: new InfuraNode('https://ropsten.infura.io/mew'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
kov_ethscan: {
|
kov_ethscan: {
|
||||||
network: 'Kovan',
|
network: 'Kovan',
|
||||||
|
isCustom: false,
|
||||||
service: 'Etherscan.io',
|
service: 'Etherscan.io',
|
||||||
lib: new EtherscanNode('https://kovan.etherscan.io/api'),
|
lib: new EtherscanNode('https://kovan.etherscan.io/api'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
rin_ethscan: {
|
rin_ethscan: {
|
||||||
network: 'Rinkeby',
|
network: 'Rinkeby',
|
||||||
|
isCustom: false,
|
||||||
service: 'Etherscan.io',
|
service: 'Etherscan.io',
|
||||||
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
|
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
rin_infura: {
|
rin_infura: {
|
||||||
network: 'Rinkeby',
|
network: 'Rinkeby',
|
||||||
|
isCustom: false,
|
||||||
service: 'infura.io',
|
service: 'infura.io',
|
||||||
lib: new InfuraNode('https://rinkeby.infura.io/mew'),
|
lib: new InfuraNode('https://rinkeby.infura.io/mew'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
etc_epool: {
|
etc_epool: {
|
||||||
network: 'ETC',
|
network: 'ETC',
|
||||||
|
isCustom: false,
|
||||||
service: 'Epool.io',
|
service: 'Epool.io',
|
||||||
lib: new RPCNode('https://mewapi.epool.io'),
|
lib: new RPCNode('https://mewapi.epool.io'),
|
||||||
estimateGas: false
|
estimateGas: false
|
||||||
},
|
},
|
||||||
ubq: {
|
ubq: {
|
||||||
network: 'UBQ',
|
network: 'UBQ',
|
||||||
|
isCustom: false,
|
||||||
service: 'ubiqscan.io',
|
service: 'ubiqscan.io',
|
||||||
lib: new RPCNode('https://pyrus2.ubiqscan.io'),
|
lib: new RPCNode('https://pyrus2.ubiqscan.io'),
|
||||||
estimateGas: true
|
estimateGas: true
|
||||||
},
|
},
|
||||||
exp_tech: {
|
exp_tech: {
|
||||||
network: 'EXP',
|
network: 'EXP',
|
||||||
|
isCustom: false,
|
||||||
service: 'Expanse.tech',
|
service: 'Expanse.tech',
|
||||||
lib: new RPCNode('https://node.expanse.tech/'),
|
lib: new RPCNode('https://node.expanse.tech/'),
|
||||||
estimateGas: true
|
estimateGas: true
|
||||||
|
@ -8,20 +8,22 @@ import {
|
|||||||
takeLatest,
|
takeLatest,
|
||||||
takeEvery,
|
takeEvery,
|
||||||
select,
|
select,
|
||||||
race
|
race,
|
||||||
|
apply
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import {
|
import { makeCustomNetworkId } from 'utils/network';
|
||||||
makeCustomNodeId,
|
|
||||||
getCustomNodeConfigFromId,
|
|
||||||
makeNodeConfigFromCustomConfig
|
|
||||||
} from 'utils/node';
|
|
||||||
import { makeCustomNetworkId, getNetworkConfigFromId } from 'utils/network';
|
|
||||||
import {
|
import {
|
||||||
getNodeName,
|
getNodeName,
|
||||||
getNodeConfig,
|
getNodeConfig,
|
||||||
getCustomNodeConfigs,
|
getCustomNodeConfigs,
|
||||||
getCustomNetworkConfigs,
|
getCustomNetworkConfigs,
|
||||||
getOffline
|
getOffline,
|
||||||
|
getNetworkConfig,
|
||||||
|
isStaticNodeName,
|
||||||
|
getCustomNodeFromId,
|
||||||
|
getStaticNodeFromId,
|
||||||
|
getNetworkConfigById,
|
||||||
|
getStaticAltNodeToWeb3
|
||||||
} from 'selectors/config';
|
} from 'selectors/config';
|
||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
import { TypeKeys } from 'actions/config/constants';
|
import { TypeKeys } from 'actions/config/constants';
|
||||||
@ -38,9 +40,10 @@ import { showNotification } from 'actions/notifications';
|
|||||||
import { translateRaw } from 'translations';
|
import { translateRaw } from 'translations';
|
||||||
import { Web3Wallet } from 'libs/wallet';
|
import { Web3Wallet } from 'libs/wallet';
|
||||||
import { TypeKeys as WalletTypeKeys } from 'actions/wallet/constants';
|
import { TypeKeys as WalletTypeKeys } from 'actions/wallet/constants';
|
||||||
import { State as ConfigState, INITIAL_STATE as configInitialState } from 'reducers/config';
|
import { State as ConfigState } from 'reducers/config';
|
||||||
import { StaticNodeConfig, CustomNodeConfig } from 'types/node';
|
import { StaticNodeConfig, CustomNodeConfig, NodeConfig } from 'types/node';
|
||||||
import { CustomNetworkConfig } from 'types/network';
|
import { CustomNetworkConfig, StaticNetworkConfig } from 'types/network';
|
||||||
|
import { Web3Service } from 'reducers/config/nodes/typings';
|
||||||
|
|
||||||
export const getConfig = (state: AppState): ConfigState => state.config;
|
export const getConfig = (state: AppState): ConfigState => state.config;
|
||||||
|
|
||||||
@ -113,41 +116,45 @@ export function* reload(): SagaIterator {
|
|||||||
setTimeout(() => location.reload(), 1150);
|
setTimeout(() => location.reload(), 1150);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIterator {
|
export function* handleNodeChangeIntent({
|
||||||
const currentNode: string = yield select(getNodeName);
|
payload: nodeIdToSwitchTo
|
||||||
const currentConfig: StaticNodeConfig = yield select(getNodeConfig);
|
}: ChangeNodeIntentAction): SagaIterator {
|
||||||
const customNets: CustomNetworkConfig[] = yield select(getCustomNetworkConfigs);
|
const isStaticNode: boolean = yield select(isStaticNodeName, nodeIdToSwitchTo);
|
||||||
const currentNetwork =
|
const currentConfig: NodeConfig = yield select(getNodeConfig);
|
||||||
getNetworkConfigFromId(currentConfig.network, customNets) || NETWORKS[currentConfig.network];
|
|
||||||
|
|
||||||
function* bailOut(message: string) {
|
function* bailOut(message: string) {
|
||||||
|
const currentNodeName: string = yield select(getNodeName);
|
||||||
yield put(showNotification('danger', message, 5000));
|
yield put(showNotification('danger', message, 5000));
|
||||||
yield put(changeNode(currentNode, currentConfig, currentNetwork));
|
yield put(changeNode({ networkName: currentConfig.network, nodeName: currentNodeName }));
|
||||||
}
|
}
|
||||||
|
|
||||||
let actionConfig = NODES[action.payload];
|
let nextNodeConfig: CustomNodeConfig | StaticNodeConfig;
|
||||||
if (!actionConfig) {
|
|
||||||
const customConfigs: CustomNodeConfig[] = yield select(getCustomNodeConfigs);
|
if (!isStaticNode) {
|
||||||
const config = getCustomNodeConfigFromId(action.payload, customConfigs);
|
const config: CustomNodeConfig | undefined = yield select(
|
||||||
|
getCustomNodeFromId,
|
||||||
|
nodeIdToSwitchTo
|
||||||
|
);
|
||||||
|
|
||||||
if (config) {
|
if (config) {
|
||||||
actionConfig = makeNodeConfigFromCustomConfig(config);
|
nextNodeConfig = config;
|
||||||
|
} else {
|
||||||
|
return yield* bailOut(`Attempted to switch to unknown node '${nodeIdToSwitchTo}'`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
nextNodeConfig = yield select(getStaticNodeFromId, nodeIdToSwitchTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!actionConfig) {
|
// Grab current block from the node, before switching, to confirm it's online
|
||||||
return yield* bailOut(`Attempted to switch to unknown node '${action.payload}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab latest block from the node, before switching, to confirm it's online
|
|
||||||
// Give it 5 seconds before we call it offline
|
// Give it 5 seconds before we call it offline
|
||||||
let latestBlock;
|
let currentBlock;
|
||||||
let timeout;
|
let timeout;
|
||||||
try {
|
try {
|
||||||
const { lb, to } = yield race({
|
const { lb, to } = yield race({
|
||||||
lb: call(actionConfig.lib.getCurrentBlock.bind(actionConfig.lib)),
|
lb: apply(nextNodeConfig, nextNodeConfig.lib.getCurrentBlock),
|
||||||
to: call(delay, 5000)
|
to: call(delay, 5000)
|
||||||
});
|
});
|
||||||
latestBlock = lb;
|
currentBlock = lb;
|
||||||
timeout = to;
|
timeout = to;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Whether it times out or errors, same message
|
// Whether it times out or errors, same message
|
||||||
@ -158,16 +165,19 @@ export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIte
|
|||||||
return yield* bailOut(translateRaw('ERROR_32'));
|
return yield* bailOut(translateRaw('ERROR_32'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionNetwork = getNetworkConfigFromId(actionConfig.network, customNets);
|
const nextNetwork: StaticNetworkConfig | CustomNetworkConfig = yield select(
|
||||||
|
getNetworkConfigById,
|
||||||
|
nextNodeConfig.network
|
||||||
|
);
|
||||||
|
|
||||||
if (!actionNetwork) {
|
if (!nextNetwork) {
|
||||||
return yield* bailOut(
|
return yield* bailOut(
|
||||||
`Unknown custom network for your node '${action.payload}', try re-adding it`
|
`Unknown custom network for your node '${nodeIdToSwitchTo}', try re-adding it`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(setLatestBlock(latestBlock));
|
yield put(setLatestBlock(currentBlock));
|
||||||
yield put(changeNode(action.payload, actionConfig, actionNetwork));
|
yield put(changeNode({ networkName: nextNodeConfig.network, nodeName: nodeIdToSwitchTo }));
|
||||||
|
|
||||||
// TODO - re-enable once DeterministicWallet state is fixed to flush properly.
|
// TODO - re-enable once DeterministicWallet state is fixed to flush properly.
|
||||||
// DeterministicWallet keeps path related state we need to flush before we can stop reloading
|
// DeterministicWallet keeps path related state we need to flush before we can stop reloading
|
||||||
@ -176,8 +186,8 @@ export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIte
|
|||||||
// if there's no wallet, do not reload as there's no component state to resync
|
// if there's no wallet, do not reload as there's no component state to resync
|
||||||
// if (currentWallet && currentConfig.network !== actionConfig.network) {
|
// if (currentWallet && currentConfig.network !== actionConfig.network) {
|
||||||
|
|
||||||
const isNewNetwork = currentConfig.network !== actionConfig.network;
|
const isNewNetwork = currentConfig.network !== nextNodeConfig.network;
|
||||||
const newIsWeb3 = actionConfig.service === Web3Service;
|
const newIsWeb3 = nextNodeConfig.service === Web3Service;
|
||||||
// don't reload when web3 is selected; node will automatically re-set and state is not an issue here
|
// don't reload when web3 is selected; node will automatically re-set and state is not an issue here
|
||||||
if (isNewNetwork && !newIsWeb3) {
|
if (isNewNetwork && !newIsWeb3) {
|
||||||
yield call(reload);
|
yield call(reload);
|
||||||
@ -185,8 +195,7 @@ export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function* switchToNewNode(action: AddCustomNodeAction): SagaIterator {
|
export function* switchToNewNode(action: AddCustomNodeAction): SagaIterator {
|
||||||
const nodeId = makeCustomNodeId(action.payload);
|
yield put(changeNodeIntent(action.payload.id));
|
||||||
yield put(changeNodeIntent(nodeId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are any orphaned custom networks, purge them
|
// If there are any orphaned custom networks, purge them
|
||||||
@ -208,7 +217,6 @@ export function* cleanCustomNetworks(): SagaIterator {
|
|||||||
// unset web3 as the selected node if a non-web3 wallet has been selected
|
// unset web3 as the selected node if a non-web3 wallet has been selected
|
||||||
export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
|
export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
|
||||||
const node = yield select(getNodeName);
|
const node = yield select(getNodeName);
|
||||||
const nodeConfig = yield select(getNodeConfig);
|
|
||||||
const newWallet = action.payload;
|
const newWallet = action.payload;
|
||||||
const isWeb3Wallet = newWallet instanceof Web3Wallet;
|
const isWeb3Wallet = newWallet instanceof Web3Wallet;
|
||||||
|
|
||||||
@ -216,8 +224,9 @@ export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const altNode = yield select(getStaticAltNodeToWeb3);
|
||||||
// switch back to a node with the same network as MetaMask/Mist
|
// switch back to a node with the same network as MetaMask/Mist
|
||||||
yield put(changeNodeIntent(equivalentNodeOrDefault(nodeConfig)));
|
yield put(changeNodeIntent(altNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* unsetWeb3Node(): SagaIterator {
|
export function* unsetWeb3Node(): SagaIterator {
|
||||||
@ -227,30 +236,11 @@ export function* unsetWeb3Node(): SagaIterator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeConfig: StaticNodeConfig = yield select(getNodeConfig);
|
const altNode = yield select(getStaticAltNodeToWeb3);
|
||||||
const newNode = equivalentNodeOrDefault(nodeConfig);
|
// switch back to a node with the same network as MetaMask/Mist
|
||||||
|
yield put(changeNodeIntent(altNode));
|
||||||
yield put(changeNodeIntent(newNode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const equivalentNodeOrDefault = (nodeConfig: StaticNodeConfig) => {
|
|
||||||
const node = Object.keys(NODES)
|
|
||||||
.filter(key => key !== 'web3')
|
|
||||||
.reduce((found, key) => {
|
|
||||||
const config = NODES[key];
|
|
||||||
if (found.length) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
if (nodeConfig.network === config.network) {
|
|
||||||
return (found = key);
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}, '');
|
|
||||||
|
|
||||||
// if no equivalent node was found, use the app default
|
|
||||||
return node.length ? node : configInitialState.nodeSelection;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function* configSaga(): SagaIterator {
|
export default function* configSaga(): SagaIterator {
|
||||||
yield takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus);
|
yield takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus);
|
||||||
yield takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent);
|
yield takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent);
|
||||||
|
@ -38,7 +38,7 @@ import {
|
|||||||
} from 'libs/wallet';
|
} from 'libs/wallet';
|
||||||
import { SagaIterator, delay, Task } from 'redux-saga';
|
import { SagaIterator, delay, Task } from 'redux-saga';
|
||||||
import { apply, call, fork, put, select, takeEvery, take, cancel } from 'redux-saga/effects';
|
import { apply, call, fork, put, select, takeEvery, take, cancel } from 'redux-saga/effects';
|
||||||
import { getNodeLib, getAllTokens, getOffline } from 'selectors/config';
|
import { getNodeLib, getAllTokens, getOffline, getWeb3Node } from 'selectors/config';
|
||||||
import {
|
import {
|
||||||
getTokens,
|
getTokens,
|
||||||
getWalletInst,
|
getWalletInst,
|
||||||
@ -51,6 +51,7 @@ import Web3Node, { isWeb3Node } from 'libs/nodes/web3';
|
|||||||
import { loadWalletConfig, saveWalletConfig } from 'utils/localStorage';
|
import { loadWalletConfig, saveWalletConfig } from 'utils/localStorage';
|
||||||
import { getTokenBalances, filterScannedTokenBalances } from './helpers';
|
import { getTokenBalances, filterScannedTokenBalances } from './helpers';
|
||||||
import { Token } from 'types/network';
|
import { Token } from 'types/network';
|
||||||
|
import { Web3NodeConfig } from '../../../shared/types/node';
|
||||||
|
|
||||||
export interface TokenBalanceLookup {
|
export interface TokenBalanceLookup {
|
||||||
[symbol: string]: TokenBalance;
|
[symbol: string]: TokenBalance;
|
||||||
@ -265,11 +266,12 @@ export function* unlockWeb3(): SagaIterator {
|
|||||||
action.type === ConfigTypeKeys.CONFIG_NODE_CHANGE && action.payload.nodeSelection === 'web3'
|
action.type === ConfigTypeKeys.CONFIG_NODE_CHANGE && action.payload.nodeSelection === 'web3'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!NODES.web3) {
|
const web3Node: Web3NodeConfig | null = yield select(getWeb3Node);
|
||||||
|
if (!web3Node) {
|
||||||
throw Error('Web3 node config not found!');
|
throw Error('Web3 node config not found!');
|
||||||
}
|
}
|
||||||
const network = NODES.web3.network;
|
const network = web3Node.network;
|
||||||
const nodeLib: INode | Web3Node = yield select(getNodeLib);
|
const nodeLib: Web3Node = web3Node.lib;
|
||||||
|
|
||||||
if (!isWeb3Node(nodeLib)) {
|
if (!isWeb3Node(nodeLib)) {
|
||||||
throw new Error('Cannot use Web3 wallet without a Web3 node.');
|
throw new Error('Cannot use Web3 wallet without a Web3 node.');
|
||||||
|
@ -9,14 +9,24 @@ import {
|
|||||||
|
|
||||||
export const getNetworks = (state: AppState) => getConfig(state).networks;
|
export const getNetworks = (state: AppState) => getConfig(state).networks;
|
||||||
|
|
||||||
|
export const getNetworkConfigById = (state: AppState, networkId: string) =>
|
||||||
|
isStaticNetworkName(state, networkId)
|
||||||
|
? getStaticNetworkConfigs(state)[networkId]
|
||||||
|
: getCustomNetworkConfigs(state)[networkId];
|
||||||
|
|
||||||
export const getStaticNetworkNames = (state: AppState): StaticNetworkNames[] =>
|
export const getStaticNetworkNames = (state: AppState): StaticNetworkNames[] =>
|
||||||
Object.keys(getNetworks(state).staticNetworks) as StaticNetworkNames[];
|
Object.keys(getNetworks(state).staticNetworks) as StaticNetworkNames[];
|
||||||
|
|
||||||
|
export const isStaticNetworkName = (
|
||||||
|
state: AppState,
|
||||||
|
networkName: string
|
||||||
|
): networkName is StaticNetworkNames =>
|
||||||
|
Object.keys(getStaticNetworkConfigs(state)).includes(networkName);
|
||||||
|
|
||||||
export const getStaticNetworkConfig = (state: AppState): StaticNetworkConfig | undefined => {
|
export const getStaticNetworkConfig = (state: AppState): StaticNetworkConfig | undefined => {
|
||||||
const { staticNetworks, selectedNetwork } = getNetworks(state);
|
const { staticNetworks, selectedNetwork } = getNetworks(state);
|
||||||
const isDefaultNetworkName = (networkName: string): networkName is StaticNetworkNames =>
|
|
||||||
Object.keys(staticNetworks).includes(networkName);
|
const defaultNetwork = isStaticNetworkName(state, selectedNetwork)
|
||||||
const defaultNetwork = isDefaultNetworkName(selectedNetwork)
|
|
||||||
? staticNetworks[selectedNetwork]
|
? staticNetworks[selectedNetwork]
|
||||||
: undefined;
|
: undefined;
|
||||||
return defaultNetwork;
|
return defaultNetwork;
|
||||||
|
@ -1,29 +1,70 @@
|
|||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
import { getConfig, getStaticNetworkConfigs } from 'selectors/config';
|
import {
|
||||||
import { CustomNodeConfig, StaticNodeConfig, StaticNodeName } from 'types/node';
|
getConfig,
|
||||||
|
getStaticNetworkConfigs,
|
||||||
|
getCustomNetworkConfigs,
|
||||||
|
isStaticNetworkName
|
||||||
|
} from 'selectors/config';
|
||||||
|
import { CustomNodeConfig, StaticNodeConfig, StaticNodeName, Web3NodeConfig } from 'types/node';
|
||||||
|
import { INITIAL_STATE as SELECTED_NODE_INITIAL_STATE } from 'reducers/config/nodes/selectedNode';
|
||||||
|
|
||||||
export const getNodes = (state: AppState) => getConfig(state).nodes;
|
export const getNodes = (state: AppState) => getConfig(state).nodes;
|
||||||
|
|
||||||
|
export function isNodeCustom(state: AppState, nodeName: string): CustomNodeConfig | undefined {
|
||||||
|
return getCustomNodeConfigs(state)[nodeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCustomNodeFromId = (
|
||||||
|
state: AppState,
|
||||||
|
nodeName: string
|
||||||
|
): CustomNodeConfig | undefined => getCustomNodeConfigs(state)[nodeName];
|
||||||
|
|
||||||
|
export const getStaticAltNodeToWeb3 = (state: AppState) => {
|
||||||
|
const { web3, ...configs } = getStaticNodeConfigs(state);
|
||||||
|
if (!web3) {
|
||||||
|
return SELECTED_NODE_INITIAL_STATE.nodeName;
|
||||||
|
}
|
||||||
|
const res = Object.entries(configs).find(
|
||||||
|
([_, config]: [StaticNodeName, StaticNodeConfig]) => web3.network === config.network
|
||||||
|
);
|
||||||
|
if (res) {
|
||||||
|
return res[0];
|
||||||
|
}
|
||||||
|
return SELECTED_NODE_INITIAL_STATE.nodeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStaticNodeFromId = (state: AppState, nodeName: StaticNodeName) =>
|
||||||
|
getStaticNodeConfigs(state)[nodeName];
|
||||||
|
|
||||||
|
export const isStaticNodeName = (state: AppState, nodeName: string): nodeName is StaticNodeName =>
|
||||||
|
Object.keys(getStaticNodeConfigs(state)).includes(nodeName);
|
||||||
|
|
||||||
|
const getStaticNodeConfigs = (state: AppState) => getNodes(state).staticNodes;
|
||||||
|
|
||||||
export const getStaticNodeConfig = (state: AppState): StaticNodeConfig | undefined => {
|
export const getStaticNodeConfig = (state: AppState): StaticNodeConfig | undefined => {
|
||||||
const { staticNodes, selectedNode: { nodeName } } = getNodes(state);
|
const { staticNodes, selectedNode: { nodeName } } = getNodes(state);
|
||||||
if (nodeName === undefined) {
|
|
||||||
return nodeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isStaticNodeName = (networkName: string): networkName is StaticNodeName =>
|
const defaultNetwork = isStaticNodeName(state, nodeName) ? staticNodes[nodeName] : undefined;
|
||||||
Object.keys(staticNodes).includes(networkName);
|
|
||||||
|
|
||||||
const defaultNetwork = isStaticNodeName(nodeName) ? staticNodes[nodeName] : undefined;
|
|
||||||
return defaultNetwork;
|
return defaultNetwork;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getWeb3Node = (state: AppState): Web3NodeConfig | null => {
|
||||||
|
const currNode = getStaticNodeConfig(state);
|
||||||
|
const currNodeName = getNodeName(state);
|
||||||
|
if (
|
||||||
|
currNode &&
|
||||||
|
currNodeName &&
|
||||||
|
isStaticNodeName(state, currNodeName) &&
|
||||||
|
currNodeName === 'web3'
|
||||||
|
) {
|
||||||
|
return currNode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const getCustomNodeConfig = (state: AppState): CustomNodeConfig | undefined => {
|
export const getCustomNodeConfig = (state: AppState): CustomNodeConfig | undefined => {
|
||||||
const { customNodes, selectedNode: { nodeName } } = getNodes(state);
|
const { customNodes, selectedNode: { nodeName } } = getNodes(state);
|
||||||
|
|
||||||
if (nodeName === undefined) {
|
|
||||||
return nodeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const customNode = customNodes[nodeName];
|
const customNode = customNodes[nodeName];
|
||||||
return customNode;
|
return customNode;
|
||||||
};
|
};
|
||||||
@ -40,7 +81,7 @@ export function isNodeChanging(state): boolean {
|
|||||||
return getNodes(state).selectedNode.pending;
|
return getNodes(state).selectedNode.pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeName(state: AppState): string | undefined {
|
export function getNodeName(state: AppState): string {
|
||||||
return getNodes(state).selectedNode.nodeName;
|
return getNodes(state).selectedNode.nodeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,15 +89,15 @@ export function getIsWeb3Node(state: AppState): boolean {
|
|||||||
return getNodeName(state) === 'web3';
|
return getNodeName(state) === 'web3';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeConfig(state: AppState): StaticNodeConfig | CustomNodeConfig | undefined {
|
export function getNodeConfig(state: AppState): StaticNodeConfig | CustomNodeConfig {
|
||||||
const config = getStaticNodeConfig(state) || getCustomNodeConfig(state);
|
const config = getStaticNodeConfig(state) || getCustomNodeConfig(state);
|
||||||
/*
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
const { selectedNode } = getNodes(state);
|
const { selectedNode } = getNodes(state);
|
||||||
throw Error(
|
throw Error(
|
||||||
`No node config found for ${selectedNode.nodeName} in either static or custom nodes`
|
`No node config found for ${selectedNode.nodeName} in either static or custom nodes`
|
||||||
);
|
);
|
||||||
}*/
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,25 +109,63 @@ export function getNodeLib(state: AppState) {
|
|||||||
return config.lib;
|
return config.lib;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NodeOption {
|
export interface NodeOption {
|
||||||
|
isCustom: false;
|
||||||
value: string;
|
value: string;
|
||||||
name: { networkName?: string; service: string };
|
name: { networkName?: string; service: string };
|
||||||
color?: string;
|
color?: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStaticNodeOptions(state: AppState) {
|
export function getStaticNodeOptions(state: AppState): NodeOption[] {
|
||||||
const staticNetworkConfigs = getStaticNetworkConfigs(state);
|
const staticNetworkConfigs = getStaticNetworkConfigs(state);
|
||||||
Object.entries(getStaticNodes(state)).map(
|
return Object.entries(getStaticNodes(state)).map(
|
||||||
([nodeName, nodeConfig]: [string, StaticNodeConfig]) => {
|
([nodeName, node]: [string, StaticNodeConfig]) => {
|
||||||
const networkName = nodeConfig.network;
|
const networkName = node.network;
|
||||||
const associatedNetwork = staticNetworkConfigs[networkName];
|
const associatedNetwork = staticNetworkConfigs[networkName];
|
||||||
return {
|
const opt: NodeOption = {
|
||||||
|
isCustom: node.isCustom,
|
||||||
value: nodeName,
|
value: nodeName,
|
||||||
name: { networkName, service: nodeConfig.service },
|
name: { networkName, service: node.service },
|
||||||
color: associatedNetwork.color,
|
color: associatedNetwork.color,
|
||||||
hidden: nodeConfig.hidden
|
hidden: node.hidden
|
||||||
};
|
};
|
||||||
|
return opt;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CustomNodeOption {
|
||||||
|
isCustom: true;
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
name: { networkName?: string; nodeName: string };
|
||||||
|
color?: string;
|
||||||
|
hidden?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCustomNodeOptions(state: AppState): CustomNodeOption[] {
|
||||||
|
const staticNetworkConfigs = getStaticNetworkConfigs(state);
|
||||||
|
const customNetworkConfigs = getCustomNetworkConfigs(state);
|
||||||
|
return Object.entries(getCustomNodeConfigs(state)).map(
|
||||||
|
([nodeName, node]: [string, CustomNodeConfig]) => {
|
||||||
|
const networkName = node.network;
|
||||||
|
const associatedNetwork = isStaticNetworkName(state, networkName)
|
||||||
|
? staticNetworkConfigs[networkName]
|
||||||
|
: customNetworkConfigs[networkName];
|
||||||
|
const opt: CustomNodeOption = {
|
||||||
|
isCustom: node.isCustom,
|
||||||
|
value: node.id,
|
||||||
|
name: { networkName, nodeName },
|
||||||
|
color: associatedNetwork.isCustom ? undefined : associatedNetwork.color,
|
||||||
|
hidden: false,
|
||||||
|
id: node.id
|
||||||
|
};
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNodeOptions(state: AppState) {
|
||||||
|
return [...getStaticNodeOptions(state), ...getCustomNodeOptions(state)];
|
||||||
|
}
|
||||||
|
@ -65,7 +65,7 @@ const configureStore = () => {
|
|||||||
|
|
||||||
// If they have a saved node, make sure we assign that too. The node selected
|
// 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.
|
// isn't serializable, so we have to assign it here.
|
||||||
if (savedConfigState && savedConfigState.nodeSelection) {
|
if (savedConfigState && savedConfigState.nodes.selectedNode.nodeName) {
|
||||||
const savedNode = getNodeConfigFromId(
|
const savedNode = getNodeConfigFromId(
|
||||||
savedConfigState.nodeSelection,
|
savedConfigState.nodeSelection,
|
||||||
savedConfigState.customNodes
|
savedConfigState.customNodes
|
||||||
|
@ -39,20 +39,6 @@ export function makeNetworkConfigFromCustomConfig(
|
|||||||
return customConfig;
|
return customConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNetworkConfigFromId(
|
|
||||||
id: string,
|
|
||||||
configs: CustomNetworkConfig[]
|
|
||||||
): StaticNetworkConfig | undefined {
|
|
||||||
if (NETWORKS[id]) {
|
|
||||||
return NETWORKS[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
const customConfig = configs.find(conf => makeCustomNetworkId(conf) === id);
|
|
||||||
if (customConfig) {
|
|
||||||
return makeNetworkConfigFromCustomConfig(customConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PathType = keyof DPathFormats;
|
type PathType = keyof DPathFormats;
|
||||||
|
|
||||||
type DPathFormat =
|
type DPathFormat =
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
import { CustomNode } from 'libs/nodes';
|
|
||||||
import { CustomNodeConfig, StaticNodeConfig } from 'types/node';
|
|
||||||
|
|
||||||
export function makeCustomNodeId(config: CustomNodeConfig): string {
|
|
||||||
return `${config.url}:${config.port}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCustomNodeConfigFromId(
|
|
||||||
id: string,
|
|
||||||
configs: CustomNodeConfig[]
|
|
||||||
): CustomNodeConfig | undefined {
|
|
||||||
return configs.find(node => makeCustomNodeId(node) === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNodeConfigFromId(
|
|
||||||
id: string,
|
|
||||||
configs: CustomNodeConfig[]
|
|
||||||
): StaticNodeConfig | undefined {
|
|
||||||
if (NODES[id]) {
|
|
||||||
return NODES[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = getCustomNodeConfigFromId(id, configs);
|
|
||||||
if (config) {
|
|
||||||
return makeNodeConfigFromCustomConfig(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeNodeConfigFromCustomConfig(config: CustomNodeConfig): StaticNodeConfig {
|
|
||||||
interface Override extends StaticNodeConfig {
|
|
||||||
network: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const customConfig: Override = {
|
|
||||||
network: config.network,
|
|
||||||
lib: new CustomNode(config),
|
|
||||||
service: 'your custom node',
|
|
||||||
estimateGas: true
|
|
||||||
};
|
|
||||||
|
|
||||||
return customConfig;
|
|
||||||
}
|
|
1
shared/types/network.d.ts
vendored
1
shared/types/network.d.ts
vendored
@ -28,7 +28,6 @@ interface DPathFormats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface StaticNetworkConfig {
|
interface StaticNetworkConfig {
|
||||||
// TODO really try not to allow strings due to custom networks
|
|
||||||
isCustom: false; // used for type guards
|
isCustom: false; // used for type guards
|
||||||
name: StaticNetworkNames;
|
name: StaticNetworkNames;
|
||||||
unit: string;
|
unit: string;
|
||||||
|
12
shared/types/node.d.ts
vendored
12
shared/types/node.d.ts
vendored
@ -1,9 +1,14 @@
|
|||||||
import { RPCNode, Web3Node } from 'libs/nodes';
|
import { RPCNode, Web3Node } from 'libs/nodes';
|
||||||
import { StaticNetworkNames } from './network';
|
import { StaticNetworkNames } from './network';
|
||||||
import { StaticNodesState, CustomNodesState } from 'reducers/config/nodes';
|
import { StaticNodesState, CustomNodesState } from 'reducers/config/nodes';
|
||||||
|
import CustomNode from 'libs/nodes/custom';
|
||||||
|
|
||||||
interface CustomNodeConfig {
|
interface CustomNodeConfig {
|
||||||
|
id: string;
|
||||||
|
isCustom: true;
|
||||||
name: string;
|
name: string;
|
||||||
|
lib: CustomNode;
|
||||||
|
service: 'your custom node';
|
||||||
url: string;
|
url: string;
|
||||||
port: number;
|
port: number;
|
||||||
network: string;
|
network: string;
|
||||||
@ -14,6 +19,7 @@ interface CustomNodeConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface StaticNodeConfig {
|
interface StaticNodeConfig {
|
||||||
|
isCustom: false;
|
||||||
network: StaticNetworkNames;
|
network: StaticNetworkNames;
|
||||||
lib: RPCNode | Web3Node;
|
lib: RPCNode | Web3Node;
|
||||||
service: string;
|
service: string;
|
||||||
@ -21,6 +27,10 @@ interface StaticNodeConfig {
|
|||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Web3NodeConfig extends StaticNodeConfig {
|
||||||
|
lib: Web3Node;
|
||||||
|
}
|
||||||
|
|
||||||
declare enum StaticNodeName {
|
declare enum StaticNodeName {
|
||||||
ETH_MEW = 'eth_mew',
|
ETH_MEW = 'eth_mew',
|
||||||
ETH_MYCRYPTO = 'eth_mycrypto',
|
ETH_MYCRYPTO = 'eth_mycrypto',
|
||||||
@ -39,7 +49,7 @@ declare enum StaticNodeName {
|
|||||||
type NonWeb3NodeConfigs = { [key in StaticNodeName]: StaticNodeConfig };
|
type NonWeb3NodeConfigs = { [key in StaticNodeName]: StaticNodeConfig };
|
||||||
|
|
||||||
interface Web3NodeConfig {
|
interface Web3NodeConfig {
|
||||||
web3?: StaticNodeConfig;
|
web3?: Web3NodeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeConfig = StaticNodesState[StaticNodeName] | CustomNodesState[string];
|
type NodeConfig = StaticNodesState[StaticNodeName] | CustomNodesState[string];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user