Moved commands out of UI into preload

This commit is contained in:
Rémy Roy
2021-11-30 12:00:12 -05:00
parent d78de6edf0
commit d4298d5bdd
9 changed files with 109 additions and 161 deletions

View File

@@ -5,18 +5,28 @@
*
* @module
*/
const readdirProm = window.utilAPI.promisify(window.fsAPI.readdir);
import { promisify } from 'util';
import { constants, readdir } from 'fs';
import { access, stat } from 'fs/promises';
import path from "path";
import { fileSync } from "tmp";
const readdirProm = promisify(readdir);
/**
* Check for the existence of a file or a directory on the filesystem.
*
* @param filename The path to the file or directory.
*
* @returns Returns true if the file or directory exists. Returns false if not.
* @returns Returns a Promise<boolean> that includes a true value if file or directory exists.
*/
const doesFileExist = (filename: string): boolean => {
const doesFileExist = async (filename: string): Promise<boolean> => {
try {
window.fsAPI.accessSync(filename, window.fsAPI.constantsFOK);
await access(filename, constants.F_OK);
return true;
} catch (err) {
return false;
@@ -28,11 +38,11 @@ const doesFileExist = (filename: string): boolean => {
*
* @param directory The path to the directory.
*
* @returns Returns true if the directory exists. Returns false if not.
* @returns Returns a Promise<boolean> that includes a true value if the directory exists.
*/
const doesDirectoryExist = (directory: string): boolean => {
if (doesFileExist(directory)) {
return window.fsAPI.statSync(directory).isDirectory();
const doesDirectoryExist = async (directory: string): Promise<boolean> => {
if (await doesFileExist(directory)) {
return (await stat(directory)).isDirectory();
}
return false;
}
@@ -45,16 +55,16 @@ const doesDirectoryExist = (directory: string): boolean => {
* @returns Returns true if the directory is writable and if a file can be written in the
* directory. Returns false if not.
*/
const isDirectoryWritable = (directory: string): boolean => {
const isDirectoryWritable = async (directory: string): Promise<boolean> => {
let tempFile = null;
try {
window.fsAPI.accessSync(directory, window.fsAPI.constantsWOK);
await access(directory, constants.W_OK);
/**
* On Windows, checking for W_OK on a directory is not enough to tell if we can write a file in
* it. We need to actually write a temporary file to check.
*/
tempFile = window.tmpAPI.fileSync({ keep: false, tmpdir: directory });
tempFile = fileSync({ keep: false, tmpdir: directory });
return true;
} catch (err) {
@@ -80,7 +90,7 @@ const findFirstFile = async (directory: string, startsWith: string): Promise<str
for (const entry of entries) {
if (entry.isFile() && entry.name.startsWith(startsWith)) {
return window.pathAPI.join(directory, entry.name);
return path.join(directory, entry.name);
}
}

View File

@@ -17,13 +17,21 @@
* @module
*/
import { Network } from '../types'
import { doesFileExist } from "./BashUtils";
import { execFile } from 'child_process';
import { promisify } from 'util';
import { constants } from 'fs';
import { access, mkdir } from 'fs/promises';
import { cwd } from 'process';
import path from "path";
import process from "process";
import { doesFileExist } from './BashUtils';
/**
* A promise version of the execFile function from fs for CLI calls.
*/
const execFileProm = window.utilAPI.promisify(window.childProcessAPI.execFile);
const execFileProm = promisify(execFile);
const ETH2_DEPOSIT_DIR_NAME = "eth2.0-deposit-cli-1.2.0";
@@ -31,36 +39,36 @@ const ETH2_DEPOSIT_DIR_NAME = "eth2.0-deposit-cli-1.2.0";
* Paths needed to call the eth2deposit_proxy application using the Python 3 version installed on
* the current machine.
*/
const ETH2_DEPOSIT_CLI_PATH = window.pathAPI.join("src", "vendors", ETH2_DEPOSIT_DIR_NAME);
const SCRIPTS_PATH = window.pathAPI.join("src", "scripts");
const REQUIREMENTS_PATH = window.pathAPI.join(ETH2_DEPOSIT_CLI_PATH, "requirements.txt");
const WORD_LIST_PATH = window.pathAPI.join(ETH2_DEPOSIT_CLI_PATH, "eth2deposit", "key_handling",
const ETH2_DEPOSIT_CLI_PATH = path.join("src", "vendors", ETH2_DEPOSIT_DIR_NAME);
const SCRIPTS_PATH = path.join("src", "scripts");
const REQUIREMENTS_PATH = path.join(ETH2_DEPOSIT_CLI_PATH, "requirements.txt");
const WORD_LIST_PATH = path.join(ETH2_DEPOSIT_CLI_PATH, "eth2deposit", "key_handling",
"key_derivation", "word_lists");
const REQUIREMENT_PACKAGES_PATH = window.pathAPI.join("dist", "packages");
const ETH2DEPOSIT_PROXY_PATH = window.pathAPI.join(SCRIPTS_PATH, "eth2deposit_proxy.py");
const REQUIREMENT_PACKAGES_PATH = path.join("dist", "packages");
const ETH2DEPOSIT_PROXY_PATH = path.join(SCRIPTS_PATH, "eth2deposit_proxy.py");
/**
* Paths needed to call the eth2deposit_proxy application using a single file application (SFE)
* bundled with pyinstaller.
*/
const SFE_PATH = window.pathAPI.join("build", "bin", "eth2deposit_proxy" +
(window.processAPI.platform() == "win32" ? ".exe" : ""));
const DIST_WORD_LIST_PATH = window.pathAPI.join(window.processAPI.cwd(), "build", "word_lists");
const SFE_PATH = path.join("build", "bin", "eth2deposit_proxy" +
(process.platform == "win32" ? ".exe" : ""));
const DIST_WORD_LIST_PATH = path.join(cwd(), "build", "word_lists");
/**
* Paths needed to call the eth2deposit_proxy application from a bundled application.
*/
const BUNDLED_SFE_PATH = window.pathAPI.join(window.processAPI.resourcesPath(), "..", "build",
"bin", "eth2deposit_proxy" + (window.processAPI.platform() == "win32" ? ".exe" : ""));
const BUNDLED_DIST_WORD_LIST_PATH = window.pathAPI.join(window.processAPI.resourcesPath(), "..",
const BUNDLED_SFE_PATH = path.join(process.resourcesPath, "..", "build",
"bin", "eth2deposit_proxy" + (process.platform == "win32" ? ".exe" : ""));
const BUNDLED_DIST_WORD_LIST_PATH = path.join(process.resourcesPath, "..",
"build", "word_lists");
const CREATE_MNEMONIC_SUBCOMMAND = "create_mnemonic";
const GENERATE_KEYS_SUBCOMMAND = "generate_keys";
const VALIDATE_MNEMONIC_SUBCOMMAND = "validate_mnemonic";
const PYTHON_EXE = (window.processAPI.platform() == "win32" ? "python" : "python3");
const PATH_DELIM = (window.processAPI.platform() == "win32" ? ";" : ":");
const PYTHON_EXE = (process.platform == "win32" ? "python" : "python3");
const PATH_DELIM = (process.platform == "win32" ? ";" : ":");
/**
* Install the required Python packages needed to call the eth2deposit_proxy application using the
@@ -71,10 +79,10 @@ const PATH_DELIM = (window.processAPI.platform() == "win32" ? ";" : ":");
*/
const requireDepositPackages = async (): Promise<boolean> => {
if (!window.fsAPI.existsSync(REQUIREMENT_PACKAGES_PATH)) {
window.fsAPI.mkdir(REQUIREMENT_PACKAGES_PATH, { recursive: true }, (err) => {
if (err) throw err;
});
try {
await access(REQUIREMENT_PACKAGES_PATH, constants.F_OK);
} catch {
await mkdir(REQUIREMENT_PACKAGES_PATH, { recursive: true });
const executable = PYTHON_EXE;
const args = ["-m", "pip", "install", "-r", REQUIREMENTS_PATH, "--target",
@@ -82,6 +90,7 @@ const requireDepositPackages = async (): Promise<boolean> => {
await execFileProm(executable, args);
}
return true
}
@@ -115,12 +124,12 @@ const createMnemonic = async (language: string): Promise<string> => {
let executable:string = "";
let args:string[] = [];
let env = Object(window.processAPI.env());
let env = process.env;
if (doesFileExist(BUNDLED_SFE_PATH)) {
if (await doesFileExist(BUNDLED_SFE_PATH)) {
executable = BUNDLED_SFE_PATH;
args = [CREATE_MNEMONIC_SUBCOMMAND, BUNDLED_DIST_WORD_LIST_PATH, "--language", language];
} else if (doesFileExist(SFE_PATH)) {
} else if (await doesFileExist(SFE_PATH)) {
executable = SFE_PATH;
args = [CREATE_MNEMONIC_SUBCOMMAND, DIST_WORD_LIST_PATH, "--language", language]
} else {
@@ -163,7 +172,7 @@ const generateKeys = async (
mnemonic: string,
index: number,
count: number,
network: Network,
network: string,
password: string,
eth1_withdrawal_address: string,
folder: string,
@@ -171,9 +180,9 @@ const generateKeys = async (
let executable:string = "";
let args:string[] = [];
let env = Object(window.processAPI.env());
let env = process.env;
if (doesFileExist(BUNDLED_SFE_PATH)) {
if (await doesFileExist(BUNDLED_SFE_PATH)) {
executable = BUNDLED_SFE_PATH;
args = [GENERATE_KEYS_SUBCOMMAND];
if ( eth1_withdrawal_address != "" ) {
@@ -182,7 +191,7 @@ const generateKeys = async (
args = args.concat([BUNDLED_DIST_WORD_LIST_PATH, mnemonic, index.toString(), count.toString(),
folder, network.toLowerCase(), password]);
} else if (doesFileExist(SFE_PATH)) {
} else if (await doesFileExist(SFE_PATH)) {
executable = SFE_PATH;
args = [GENERATE_KEYS_SUBCOMMAND];
if ( eth1_withdrawal_address != "" ) {
@@ -224,12 +233,12 @@ const validateMnemonic = async (
let executable:string = "";
let args:string[] = [];
let env = Object(window.processAPI.env());
let env = process.env;
if (doesFileExist(BUNDLED_SFE_PATH)) {
if (await doesFileExist(BUNDLED_SFE_PATH)) {
executable = BUNDLED_SFE_PATH;
args = [VALIDATE_MNEMONIC_SUBCOMMAND, BUNDLED_DIST_WORD_LIST_PATH, mnemonic];
} else if (doesFileExist(SFE_PATH)) {
} else if (await doesFileExist(SFE_PATH)) {
executable = SFE_PATH;
args = [VALIDATE_MNEMONIC_SUBCOMMAND, DIST_WORD_LIST_PATH, mnemonic];
} else {

View File

@@ -12,13 +12,9 @@ import {
OpenDialogReturnValue
} from "electron";
import { accessSync, statSync, readdir, constants, mkdir, existsSync } from "fs";
import path from "path";
import { promisify } from "util";
import { execFile } from "child_process";
import { cwd, platform, resourcesPath, env } from "process"
import { createMnemonic, generateKeys, validateMnemonic } from './Eth2Deposit';
import { fileSync } from "tmp";
import { doesDirectoryExist, isDirectoryWritable, findFirstFile } from './BashUtils';
const ipcRendererSendClose = () => {
ipcRenderer.send('close');
@@ -28,18 +24,6 @@ const invokeShowOpenDialog = (options: OpenDialogOptions): Promise<OpenDialogRet
return ipcRenderer.invoke('showOpenDialog', options);
};
const getPlatform = (): string => {
return platform;
}
const getResourcesPath = (): string => {
return resourcesPath;
}
const getEnv = (): Object => {
return env;
}
contextBridge.exposeInMainWorld('electronAPI', {
'shellOpenExternal': shell.openExternal,
'shellShowItemInFolder': shell.showItemInFolder,
@@ -48,35 +32,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
'invokeShowOpenDialog': invokeShowOpenDialog
});
contextBridge.exposeInMainWorld('fsAPI', {
'accessSync': accessSync,
'statSync': statSync,
'readdir': readdir,
'constantsFOK': constants.F_OK,
'constantsWOK': constants.W_OK,
'mkdir': mkdir,
'existsSync': existsSync
contextBridge.exposeInMainWorld('eth2Deposit', {
'createMnemonic': createMnemonic,
'generateKeys': generateKeys,
'validateMnemonic': validateMnemonic
});
contextBridge.exposeInMainWorld('pathAPI', {
'join': path.join
})
contextBridge.exposeInMainWorld('utilAPI', {
'promisify': promisify
})
contextBridge.exposeInMainWorld('childProcessAPI', {
'execFile': execFile
})
contextBridge.exposeInMainWorld('processAPI', {
'cwd': cwd,
'platform': getPlatform,
'resourcesPath': getResourcesPath,
'env': getEnv
})
contextBridge.exposeInMainWorld('tmpAPI', {
'fileSync': fileSync
})
contextBridge.exposeInMainWorld('bashUtils', {
'doesDirectoryExist': doesDirectoryExist,
'isDirectoryWritable': isDirectoryWritable,
'findFirstFile': findFirstFile
});

View File

@@ -31,58 +31,23 @@ export interface IElectronAPI {
invokeShowOpenDialog: (options: OpenDialogOptions) => Promise<OpenDialogReturnValue>
}
export interface IFsAPI {
accessSync: (path: PathLike, mode?: number | undefined) => void,
statSync: (path: string | Buffer | URL, options?) => Stats,
readdir: (path: PathLike, options: {
encoding?: BufferEncoding | null;
withFileTypes?: true | undefined;
} | BufferEncoding | undefined | null,
callback: (err: NodeJS.ErrnoException | null, files: Dirent[]) => void) => void,
constantsFOK: number,
constantsWOK: number,
mkdir: (path: PathLike, options: MakeDirectoryOptions & {
recursive: true;
}, callback: (err: NodeJS.ErrnoException | null, path?: string | undefined) => void) => void,
existsSync: (path: PathLike) => boolean
export interface IEth2DepositAPI {
createMnemonic: (language: string) => Promise<string>,
generateKeys: (mnemonic: string, index: number, count: number, network: string,
password: string, eth1_withdrawal_address: string, folder: string) => Promise<void>,
validateMnemonic: (mnemonic: string) => Promise<void>
}
export interface IPathAPI {
join: (...paths: string[]) => string
}
export interface IUtilAPI {
promisify: (original: Function) => Function
}
export interface IUtilAPI {
promisify: (original: Function) => Function
}
export interface IChildProcessAPI {
execFile: (file: string, args: string[], options: Object,
callback: (error: Error, stdout: string | Buffer, stderr: string | Buffer) => void) => ChildProcess
}
export interface IProcessAPI {
cwd: () => string,
platform: () => string,
resourcesPath: () => string,
env: () => Object
}
export interface ITmpAPI {
fileSync: (options: FileOptions) => FileResult
export interface IBashUtilsAPI {
doesDirectoryExist: (directory: string) => Promise<boolean>,
isDirectoryWritable: (directory: string) => Promise<boolean>,
findFirstFile: (directory: string, startsWith: string) => Promise<string>
}
declare global {
interface Window {
electronAPI: IElectronAPI,
fsAPI: IFsAPI
pathAPI: IPathAPI,
utilAPI: IUtilAPI,
childProcessAPI: IChildProcessAPI,
processAPI: IProcessAPI,
tmpAPI: ITmpAPI
eth2Deposit: IEth2DepositAPI,
bashUtils: IBashUtilsAPI
}
}

View File

@@ -3,8 +3,6 @@ import React, { FC, ReactElement } from 'react';
import styled from 'styled-components';
import { Network } from '../../types';
import { findFirstFile } from "../../commands/BashUtils";
type KeysCreatedProps = {
folderPath: string,
network: Network
@@ -24,7 +22,7 @@ const LoudText = styled(Typography)`
const KeysCreated: FC<KeysCreatedProps> = (props): ReactElement => {
const openKeyLocation = () => {
findFirstFile(props.folderPath, "keystore")
window.bashUtils.findFirstFile(props.folderPath, "keystore")
.then((keystoreFile) => {
let fileToLocate = props.folderPath;
if (keystoreFile != "") {

View File

@@ -1,13 +1,11 @@
import { Grid, Typography } from '@material-ui/core';
import React, { FC, ReactElement, Dispatch, SetStateAction, useState } from 'react';
import styled from 'styled-components';
import { generateKeys } from '../commands/Eth2Deposit';
import SelectFolder from './KeyGeneratioinFlow/2-SelectFolder';
import CreatingKeys from './KeyGeneratioinFlow/3-CreatingKeys';
import KeysCreated from './KeyGeneratioinFlow/4-KeysCreated';
import StepNavigation from './StepNavigation';
import { Network } from '../types';
import { doesDirectoryExist, isDirectoryWritable } from '../commands/BashUtils';
import { errors } from '../constants';
const ContentGrid = styled(Grid)`
@@ -90,21 +88,25 @@ const KeyGenerationWizard: FC<Props> = (props): ReactElement => {
setFolderError(false);
setFolderErrorMsg("");
if (!doesDirectoryExist(props.folderPath)) {
setFolderErrorMsg(errors.FOLDER_DOES_NOT_EXISTS);
setFolderError(true);
break;
}
window.bashUtils.doesDirectoryExist(props.folderPath)
.then((exists) => {
if (!exists) {
setFolderErrorMsg(errors.FOLDER_DOES_NOT_EXISTS);
setFolderError(true);
} else {
if (!isDirectoryWritable(props.folderPath)) {
setFolderErrorMsg(errors.FOLDER_IS_NOT_WRITABLE);
setFolderError(true);
break;
}
setStep(step + 1);
handleKeyGeneration();
window.bashUtils.isDirectoryWritable(props.folderPath)
.then((writable) => {
if (!writable) {
setFolderErrorMsg(errors.FOLDER_IS_NOT_WRITABLE);
setFolderError(true);
} else {
setStep(step + 1);
handleKeyGeneration();
}
});
}
});
} else {
setFolderError(true);
@@ -132,7 +134,7 @@ const KeyGenerationWizard: FC<Props> = (props): ReactElement => {
const handleKeyGeneration = () => {
const eth1_withdrawal_address = "";
generateKeys(
window.eth2Deposit.generateKeys(
props.mnemonic,
props.keyGenerationStartIndex!,
props.numberOfKeys,

View File

@@ -1,7 +1,6 @@
import { Grid, Typography } from '@material-ui/core';
import React, { FC, ReactElement, useState, Dispatch, SetStateAction, useEffect } from 'react';
import styled from 'styled-components';
import { createMnemonic } from '../commands/Eth2Deposit';
import { Network } from '../types';
import GenerateMnemonic from './MnemonicGenerationFlow/0-GenerateMnemonic';
import ShowMnemonic from './MnemonicGenerationFlow/1-2-ShowMnemonic';
@@ -111,7 +110,7 @@ const MnemonicGenerationWizard: FC<Props> = (props): ReactElement => {
setGenerateError(false);
setGenerateErrorMsg("");
createMnemonic('english').then((mnemonic) => {
window.eth2Deposit.createMnemonic('english').then((mnemonic) => {
props.setMnemonic(mnemonic);
}).catch((error) => {
setStep(0);

View File

@@ -1,7 +1,6 @@
import { Grid, TextField, Typography } from "@material-ui/core";
import React, { FC, ReactElement, useState, Dispatch, SetStateAction } from "react";
import styled from "styled-components";
import { validateMnemonic } from '../commands/Eth2Deposit';
import ValidatingMnemonic from './MnemonicImportFlow/1-ValidatingMnemonic';
import { errors, MNEMONIC_LENGTH } from "../constants";
import StepNavigation from './StepNavigation';
@@ -48,7 +47,7 @@ const MnemonicImport: FC<Props> = (props): ReactElement => {
setStep(step + 1);
validateMnemonic(props.mnemonic).then(() => {
window.eth2Deposit.validateMnemonic(props.mnemonic).then(() => {
props.onStepForward();
}).catch((error) => {
setStep(0);

View File

@@ -54,6 +54,9 @@ module.exports = [
{ test: /\.tsx?$/, loader: 'ts-loader' }
]
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
target: 'electron-preload'
}
];