diff --git a/assets/metamaskLogo.svg b/assets/metamaskLogo.svg new file mode 100644 index 000000000..c6953e6d4 --- /dev/null +++ b/assets/metamaskLogo.svg @@ -0,0 +1 @@ + diff --git a/src/main/geth.ts b/src/main/geth.ts index 2bf2b5c98..2b2efeb9d 100644 --- a/src/main/geth.ts +++ b/src/main/geth.ts @@ -19,13 +19,11 @@ import { gethBuildNameForPlatformAndArch, } from './gethDownload'; import { isWindows } from './platform'; -import { - getIsStartOnLogin, - getNodeConfig as storeGetNodeConfig, - setNodeConfig, -} from './store'; +import { getIsStartOnLogin } from './state/store'; import { registerChildProcess } from './processExit'; import { httpGet } from './httpReq'; +// eslint-disable-next-line import/no-cycle +import { getNodeConfigAsCliInput } from './state/nodeConfig'; const streamPipeline = promisify(pipeline); @@ -43,30 +41,6 @@ const checkAndOrCreateGethDataDir = async () => { } }; -export const getDefaultNodeConfig = () => { - const gethDataPath = gethDataDir(); - const defaultConfig = [ - '--http', - '--http.corsdomain', - 'nice-node://', - '--datadir', - gethDataPath, - ]; - return defaultConfig; -}; - -export const getNodeConfig = () => { - const storeConfig: string[] = storeGetNodeConfig(); - if (storeConfig !== undefined) { - return storeConfig; - } - return getDefaultNodeConfig(); -}; - -export const setToDefaultNodeConfig = () => { - setNodeConfig(getDefaultNodeConfig()); -}; - export const downloadGeth = async () => { logger.info('initializing geth'); status = NODE_STATUS.initializing; @@ -156,7 +130,7 @@ export const startGeth = async () => { await checkAndOrCreateGethDataDir(); - const gethInput = getNodeConfig(); + const gethInput = getNodeConfigAsCliInput('geth'); logger.info(`Starting geth with input: ${gethInput}`); let execFileAbsolutePath = path.join( getNNDirPath(), diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 8f1f43731..1b9258bf6 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -7,15 +7,16 @@ import { getSystemFreeDiskSpace, deleteGethDisk, } from './files'; +import { getStatus, startGeth, stopGeth } from './geth'; import { getDefaultNodeConfig, setToDefaultNodeConfig, getNodeConfig, - getStatus, - startGeth, - stopGeth, -} from './geth'; -import { store } from './store'; + NodeConfig, + setDirectInputNodeConfig, + changeNodeConfig, +} from './state/nodeConfig'; +import store from './state/store'; import logger from './logger'; import { checkSystemHardware, @@ -44,9 +45,30 @@ export const initialize = () => { }); ipcMain.handle('getGethLogs', getGethLogs); ipcMain.handle('getGethErrorLogs', getGethErrorLogs); - ipcMain.handle('getNodeConfig', getNodeConfig); - ipcMain.handle('getDefaultNodeConfig', getDefaultNodeConfig); - ipcMain.handle('setToDefaultNodeConfig', setToDefaultNodeConfig); + ipcMain.handle('getNodeConfig', (_event, node: string) => { + console.log('ipc main handle getNodeConfig: ', node); + return getNodeConfig(node); + }); + ipcMain.handle( + 'changeNodeConfig', + (_event, node: string, nodeConfig: NodeConfig) => { + return changeNodeConfig(node, nodeConfig); + } + ); + ipcMain.handle('getDefaultNodeConfig', (_event, node: string) => { + console.log('main handle getDefaultNodeConfig'); + return getDefaultNodeConfig(node); + }); + ipcMain.handle('setToDefaultNodeConfig', (_event, node: string) => { + console.log('main node: ', node); + return setToDefaultNodeConfig(node); + }); + ipcMain.handle( + 'setDirectInputNodeConfig', + (_event, node: string, directInput: string[]) => { + return setDirectInputNodeConfig(node, directInput); + } + ); ipcMain.handle('getNodeUsage', getNodeUsage); ipcMain.handle('getMainProcessUsage', getMainProcessUsage); ipcMain.handle('checkSystemHardware', checkSystemHardware); diff --git a/src/main/power.ts b/src/main/power.ts index 7562fc959..7766d5db0 100644 --- a/src/main/power.ts +++ b/src/main/power.ts @@ -1,6 +1,6 @@ import { app, powerSaveBlocker, powerMonitor } from 'electron'; -import { getIsStartOnLogin, watchIsStartOnLogin } from './store'; +import { getIsStartOnLogin, watchIsStartOnLogin } from './state/store'; import logger from './logger'; let id: number | undefined; diff --git a/src/main/preload.ts b/src/main/preload.ts index de68a171d..bb11fc39a 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -1,4 +1,5 @@ import { contextBridge, ipcRenderer } from 'electron'; +import { NodeConfig } from './state/nodeConfig'; contextBridge.exposeInMainWorld('electron', { SENTRY_DSN: process.env.SENTRY_DSN, @@ -32,8 +33,16 @@ contextBridge.exposeInMainWorld('electron', { return { memory, cpu }; }, getNodeUsage: () => ipcRenderer.invoke('getNodeUsage'), - getNodeConfig: () => ipcRenderer.invoke('getNodeConfig'), - getDefaultNodeConfig: () => ipcRenderer.invoke('getDefaultNodeConfig'), - setToDefaultNodeConfig: () => ipcRenderer.invoke('setToDefaultNodeConfig'), + getNodeConfig: (node: string) => ipcRenderer.invoke('getNodeConfig', node), + changeNodeConfig: (node: string, config: NodeConfig) => + ipcRenderer.invoke('changeNodeConfig', node, config), + getDefaultNodeConfig: (node: string) => { + ipcRenderer.invoke('getDefaultNodeConfig', node); + }, + setToDefaultNodeConfig: (node: string) => + ipcRenderer.invoke('setToDefaultNodeConfig', node), + setDirectInputNodeConfig: (node: string, directInput: string[]) => { + ipcRenderer.invoke('setDirectInputNodeConfig', node, directInput); + }, checkSystemHardware: () => ipcRenderer.invoke('checkSystemHardware'), }); diff --git a/src/main/state/nodeConfig.ts b/src/main/state/nodeConfig.ts new file mode 100644 index 000000000..3da73625e --- /dev/null +++ b/src/main/state/nodeConfig.ts @@ -0,0 +1,114 @@ +// eslint-disable-next-line import/no-cycle +import { gethDataDir } from '../files'; +import logger from '../logger'; +import store from './store'; + +export type NodeConfig = { + http?: boolean; + httpAllowedDomains?: string[]; + dataDirectory?: string; + useDirectInput?: boolean; + directInputConfig?: string[]; + asCliInput?: string[]; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getSavedNodeConfig = (node: string): any => { + return store.get(`${node}.nodeConfig`); +}; + +// function from NodeConfig to node specific CLI input flags +export const nodeConfigToCliInput = ( + node: string, + config: NodeConfig +): string[] => { + const cliInput = []; + if (node === 'geth') { + if (config.http) { + cliInput.push('--http'); + } + if (config.httpAllowedDomains) { + cliInput.push('--http.corsdomain'); + cliInput.push(config.httpAllowedDomains.join(',')); + } + if (config.dataDirectory) { + cliInput.push('--datadir'); + cliInput.push(config.dataDirectory); + } + } else { + logger.error(`Unknown config to CLI input for node ${node}.`); + } + return cliInput; +}; + +export const getDefaultNodeConfig = (node: string) => { + const gethDataPath = gethDataDir(); + const defaultConfig: NodeConfig = { + http: true, + httpAllowedDomains: ['nice-node://'], + dataDirectory: gethDataPath, + }; + defaultConfig.asCliInput = nodeConfigToCliInput(node, defaultConfig); + console.log('getDefaultNodeConfig', node, defaultConfig); + return defaultConfig; +}; + +// function to apply operations NodeConfig +export const getNodeConfig = (node: string) => { + console.log('getNodeConfig: ', node); + let nodeConfig: NodeConfig = getSavedNodeConfig(node); + if (nodeConfig === undefined) { + nodeConfig = getDefaultNodeConfig(node); + } + return nodeConfig; +}; + +export const getNodeConfigAsCliInput = (node: string): string[] => { + const nodeConfig = getNodeConfig(node); + const cliInput = nodeConfig.asCliInput ? nodeConfig.asCliInput : []; + return cliInput; +}; + +export const saveNodeConfig = (node: string, nodeConfig: NodeConfig): void => { + console.log(`store.set(${node}.nodeConfig`, nodeConfig); + store.set(`${node}.nodeConfig`, nodeConfig); +}; + +/** + * Set node's config to default NiceNode compatible config + */ +export const setToDefaultNodeConfig = async (node: string) => { + const defaultConfig = getDefaultNodeConfig(node); + await saveNodeConfig(node, defaultConfig); +}; + +/** + * function from node specific CLI input flags to NodeConfig (raw user input) + */ +export const setDirectInputNodeConfig = async ( + node: string, + directInput: string[] +) => { + const nodeConfig: NodeConfig = { + useDirectInput: true, + directInputConfig: directInput, + asCliInput: directInput, + }; + // todo: parse out config key & values from direct input + await saveNodeConfig(node, nodeConfig); +}; + +export const changeNodeConfig = (node: string, configChange: NodeConfig) => { + const nodeConfig: NodeConfig = getNodeConfig(node); + nodeConfig.useDirectInput = false; + // Delete directInput if it should be overwritten when controls are used + // delete nodeConfig.directInputConfig; + Object.entries(configChange).forEach(([configKey, configValue]) => { + console.log([configKey, configValue]); + const nodeConfigKey = configKey as keyof NodeConfig; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + nodeConfig[nodeConfigKey] = configValue as any; + }); + nodeConfig.asCliInput = nodeConfigToCliInput(node, nodeConfig); + saveNodeConfig(node, nodeConfig); +}; diff --git a/src/main/store.ts b/src/main/state/store.ts similarity index 64% rename from src/main/store.ts rename to src/main/state/store.ts index 869e397cd..54628a6f6 100644 --- a/src/main/store.ts +++ b/src/main/state/store.ts @@ -1,6 +1,6 @@ const Store = require('electron-store'); -export const store = new Store(); +const store = new Store(); export const setIsStartOnLogin = (isStartOnLogin: boolean): void => { store.set('isStartOnLogin', isStartOnLogin); @@ -16,10 +16,4 @@ export const watchIsStartOnLogin = ( store.onDidChange('isStartOnLogin', handler); }; -export const setNodeConfig = (nodeConfig: string[]): void => { - store.set('nodeConfig', nodeConfig); -}; - -export const getNodeConfig = (): string[] => { - return store.get('nodeConfig'); -}; +export default store; diff --git a/src/renderer/App.css b/src/renderer/App.css index 22274a350..ddc433feb 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -44,10 +44,8 @@ li { } a { - text-decoration: none; - height: fit-content; - width: fit-content; margin: 10px; + color: white; } a:hover { diff --git a/src/renderer/Footer/NodeConfig.tsx b/src/renderer/Footer/NodeConfig.tsx index 73bf727ec..a0994d7d0 100644 --- a/src/renderer/Footer/NodeConfig.tsx +++ b/src/renderer/Footer/NodeConfig.tsx @@ -1,22 +1,89 @@ import { useEffect, useState } from 'react'; -import electron from '../electronGlobal'; +import { FiExternalLink } from 'react-icons/fi'; +import { NodeConfig } from '../../main/state/nodeConfig'; +import electron from '../electronGlobal'; import { NODE_STATUS } from '../messages'; import { useAppSelector } from '../state/hooks'; import { selectNodeStatus } from '../state/node'; +import metamaskLogo from '../../../assets/metamaskLogo.svg'; + +const METAMASK_CHROME_EXTENSION_ID = + 'chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn'; +const METAMASK_FIREFOX_EXTENSION_ID = + 'moz-extension://e9845626-1402-4ea2-9534-f54a65cf958f'; + +const NODE = 'geth'; -const NodeConfig = () => { +const NodeConfiguration = () => { + const [sNodeConfig, setNodeConfig] = useState(); const [sNodeConfigRaw, setNodeConfigRaw] = useState(); + const [sMetaMaskEnabled, setMetaMaskEnabled] = useState(); const [sParseError, setParseError] = useState(); const sNodeStatus = useAppSelector(selectNodeStatus); const getNodeConfig = async () => { - const nodeConfig = await electron.getNodeConfig(); - setNodeConfigRaw(JSON.stringify(nodeConfig, null, 2)); + console.log('NodeConfig.tsx: getNodeConfig: '); + const nodeConfig = await electron.getNodeConfig(NODE); + setNodeConfig(nodeConfig); + const strRepresentation = nodeConfig.useDirectInput + ? nodeConfig.directInputConfig + : nodeConfig.asCliInput; + setNodeConfigRaw(JSON.stringify(strRepresentation, null, 2)); }; + useEffect(() => { + if (sNodeConfig && Array.isArray(sNodeConfig.httpAllowedDomains)) { + if ( + sNodeConfig.httpAllowedDomains.includes(METAMASK_CHROME_EXTENSION_ID) || + sNodeConfig.httpAllowedDomains.includes(METAMASK_FIREFOX_EXTENSION_ID) + ) { + setMetaMaskEnabled(true); + return; + } + } + setMetaMaskEnabled(false); + }, [sNodeConfig]); + const setToDefaultNodeConfig = async () => { - await electron.setToDefaultNodeConfig(); + await electron.setToDefaultNodeConfig(NODE); + await getNodeConfig(); + }; + + const toggleMetamaskConnections = async () => { + const currAllowedDomains = sNodeConfig?.httpAllowedDomains; + + // add metamask extension IDs to allowed http origins + let newAllowedDomains: string[] = []; + if (Array.isArray(currAllowedDomains)) { + newAllowedDomains = currAllowedDomains; + } + if (sMetaMaskEnabled) { + // disable metamask + newAllowedDomains = newAllowedDomains.filter((domain) => { + return ( + domain !== METAMASK_CHROME_EXTENSION_ID && + domain !== METAMASK_FIREFOX_EXTENSION_ID + ); + }); + } else { + // enable metamask + if (!newAllowedDomains.includes(METAMASK_CHROME_EXTENSION_ID)) { + newAllowedDomains = newAllowedDomains.concat( + METAMASK_CHROME_EXTENSION_ID + ); + } + if (!newAllowedDomains.includes(METAMASK_FIREFOX_EXTENSION_ID)) { + newAllowedDomains = newAllowedDomains.concat( + METAMASK_FIREFOX_EXTENSION_ID + ); + } + } + + await electron.changeNodeConfig(NODE, { + http: true, + httpAllowedDomains: newAllowedDomains, + }); await getNodeConfig(); }; @@ -24,10 +91,12 @@ const NodeConfig = () => { getNodeConfig(); }, []); - useEffect(() => { - if (sNodeConfigRaw) { + const onChangeRawInput = (newRawInput: string) => { + setNodeConfigRaw(newRawInput); + + if (newRawInput) { try { - const nodeConfigInput = JSON.parse(sNodeConfigRaw); + const nodeConfigInput = JSON.parse(newRawInput); if (Array.isArray(nodeConfigInput)) { const isAllStrings = nodeConfigInput.every( (i) => typeof i === 'string' @@ -38,7 +107,7 @@ const NodeConfig = () => { ); return; } - electron.setStoreValue('nodeConfig', nodeConfigInput); + electron.setDirectInputNodeConfig(NODE, nodeConfigInput); setParseError(undefined); } else { setParseError('Input is not an array.'); @@ -52,7 +121,7 @@ const NodeConfig = () => { 'Input an array of strings or just [] if no input desired.' ); } - }, [sNodeConfigRaw]); + }; const isConfigDisabled = !( sNodeStatus === NODE_STATUS.readyToStart || @@ -66,6 +135,46 @@ const NodeConfig = () => { {isConfigDisabled && (
The node must be stopped to make configuration changes.
)} + {sNodeConfig?.useDirectInput && ( +
Detected advanced usage from direct text input
+ )} +
+ + + {`MetaMask's local node guide`} + + +
+
+ +

Runtime input passed directly to the node. Enter an array of strings.

@@ -82,21 +191,10 @@ const NodeConfig = () => { minHeight: 150, }} value={sNodeConfigRaw} - onChange={(e) => setNodeConfigRaw(e.target.value)} + onChange={(e) => onChangeRawInput(e.target.value)} disabled={isConfigDisabled} /> -
- -
{/*