From a0edd596679a96c50bdd935212e774aff37becf3 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Fri, 11 May 2018 15:58:00 +0200 Subject: [PATCH] Fix cli beta (#100) * Fix cli (fix #65) * Reorganize files * Change help messages * Add --no-run-parity * Small tweaks * Util->utils * Fix bug run parity * Fix bug no-run-parity --- electron/cli/helpMessage.js | 17 +++++ electron/cli/index.js | 52 ++++++--------- electron/index.js | 26 ++++---- electron/messages/index.js | 4 +- electron/operations/doesParityExist.js | 25 +++++++ .../index.js => operations/fetchParity.js} | 58 +++++++--------- electron/operations/handleError.js | 44 +++++++++++++ .../{messages => operations}/runParity.js | 66 +++++++++---------- .../signerNewToken.js | 12 ++-- electron/{util => utils}/parityPath.js | 0 src/Connection/connection.js | 13 ++-- 11 files changed, 197 insertions(+), 120 deletions(-) create mode 100644 electron/cli/helpMessage.js create mode 100644 electron/operations/doesParityExist.js rename electron/{fetchParity/index.js => operations/fetchParity.js} (51%) create mode 100644 electron/operations/handleError.js rename electron/{messages => operations}/runParity.js (58%) rename electron/{messages => operations}/signerNewToken.js (81%) rename electron/{util => utils}/parityPath.js (100%) diff --git a/electron/cli/helpMessage.js b/electron/cli/helpMessage.js new file mode 100644 index 000000000..9e82bf47d --- /dev/null +++ b/electron/cli/helpMessage.js @@ -0,0 +1,17 @@ +module.exports = ` +Parity UI. +Copyright 2015, 2016, 2017, 2018 Parity Technologies (UK) Ltd. + +Operating Options: + --no-run-parity + Parity UI will not attempt to run the locally installed parity. + + --ui-dev + Parity UI will load http://localhost:3000. WARNING: Only use this is you plan on developing on Parity UI. + + --ws-interface=[IP] + Specify the hostname portion of the WebSockets server Parity UI will connect to. IP should be an interface's IP address. (default: 127.0.0.1) + + --ws-port=[PORT] + Specify the port portion of the WebSockets server Parity UI will connect to. (default: 8546) +`; diff --git a/electron/cli/index.js b/electron/cli/index.js index 10f52ef2c..3cc5fe9b7 100644 --- a/electron/cli/index.js +++ b/electron/cli/index.js @@ -17,45 +17,23 @@ // eslint-disable-next-line const dynamicRequire = typeof __non_webpack_require__ === 'undefined' ? require : __non_webpack_require__; // Dynamic require https://github.com/yargs/yargs/issues/781 -const { app } = require('electron'); -const fs = require('fs'); const omit = require('lodash/omit'); -const { spawn } = require('child_process'); const argv = dynamicRequire('yargs').argv; -const parityPath = require('../util/parityPath'); +const helpMessage = require('./helpMessage'); const { version } = require('../../package.json'); let parityArgv = null; // Args to pass to `parity` command -/** - * Show output of `parity` command with args. The args are supposed to make - * parity stop, so that the output can be immediately shown on the terminal. - * - * @param {Array} args - The arguments to pass to `parity`. - */ -const showParityOutput = args => { - if (fs.existsSync(parityPath())) { - const parityHelp = spawn(parityPath(), args); - - parityHelp.stdout.on('data', data => console.log(data.toString())); - parityHelp.on('close', () => app.quit()); - } else { - console.log('Please run Parity UI once to install Parity Ethereum Client. This help message will then show all available commands.'); - app.quit(); - } - - return false; -}; - module.exports = () => { if (argv.help || argv.h) { - return showParityOutput(['--help']); + console.log(helpMessage); + return false; } if (argv.version || argv.v) { console.log(`Parity UI version ${version}.`); - return showParityOutput(['--version']); + return false; } // Used cached value if it exists @@ -64,11 +42,23 @@ module.exports = () => { } // Args to pass to `parity` command - parityArgv = omit(argv, '_', '$0'); - - // Delete all keys starting with --ui* from parityArgv. - // They will be handled directly by the UI. - Object.keys(parityArgv).forEach(key => key.startsWith('ui') && delete parityArgv[key]); + parityArgv = omit(argv, '_', '$0', 'help', 'version'); + + // Sanitize args to be easily used by parity + Object.keys(parityArgv).forEach(key => { + // Delete all keys starting with --ui* from parityArgv. + // They will be handled directly by the UI. + if (key.startsWith('ui')) { + delete parityArgv[key]; + } + + // yargs create camelCase keys for each arg, e.g. "--ws-origins all" will + // create { wsOrigins: 'all' }. For parity, we remove all those that have + // a capital letter + if (/[A-Z]/.test(key)) { + delete parityArgv[key]; + } + }); return [argv, parityArgv]; }; diff --git a/electron/index.js b/electron/index.js index 8d2573ea8..a0ec8064e 100644 --- a/electron/index.js +++ b/electron/index.js @@ -20,34 +20,36 @@ const url = require('url'); const addMenu = require('./menu'); const cli = require('./cli'); -const fetchParity = require('./fetchParity'); +const doesParityExist = require('./operations/doesParityExist'); +const fetchParity = require('./operations/fetchParity'); +const handleError = require('./operations/handleError'); const messages = require('./messages'); -const { killParity } = require('./messages/runParity'); +const { killParity } = require('./operations/runParity'); const { app, BrowserWindow, ipcMain, session } = electron; let mainWindow; // Get arguments from cli -const [argv] = cli(); - -// Will send these variables to renderers via IPC -global.dirName = __dirname; -global.wsInterface = argv['ws-interface']; -global.wsPort = argv['ws-port']; +const argv = cli()[0]; function createWindow () { // If cli() returns false, then it means that the arguments are stopping the // app (e.g. --help or --version). We don't do anything more in this case. - if (!argv) { return; } + if (!argv) { return app.quit(); } + + // Will send these variables to renderers via IPC + global.dirName = __dirname; + global.wsInterface = argv['ws-interface']; + global.wsPort = argv['ws-port']; mainWindow = new BrowserWindow({ height: 800, width: 1200 }); - // Fetch parity if not yet installed - fetchParity(mainWindow) - .then(() => { global.parityInstalled = true; }); + doesParityExist() + .catch(() => fetchParity(mainWindow)) // Install parity if not present + .catch(handleError); // Errors should be handled before, this is really just in case if (argv['ui-dev'] === true) { // Opens http://127.0.0.1:3000 in --ui-dev mode diff --git a/electron/messages/index.js b/electron/messages/index.js index 25b988601..b9ccac58b 100644 --- a/electron/messages/index.js +++ b/electron/messages/index.js @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -const { runParity } = require('./runParity'); -const signerNewToken = require('./signerNewToken'); +const { runParity } = require('../operations/runParity'); +const signerNewToken = require('../operations/signerNewToken'); /** * Handle all asynchronous messages from renderer to main. diff --git a/electron/operations/doesParityExist.js b/electron/operations/doesParityExist.js new file mode 100644 index 000000000..9a9e3e0d4 --- /dev/null +++ b/electron/operations/doesParityExist.js @@ -0,0 +1,25 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +const fs = require('fs'); +const util = require('util'); + +const parityPath = require('../utils/parityPath'); + +const fsExists = util.promisify(fs.stat); + +module.exports = () => fsExists(parityPath()) + .then(() => { global.isParityInstalled = true; }); diff --git a/electron/fetchParity/index.js b/electron/operations/fetchParity.js similarity index 51% rename from electron/fetchParity/index.js rename to electron/operations/fetchParity.js index bd2d82ccb..b7454205b 100644 --- a/electron/fetchParity/index.js +++ b/electron/operations/fetchParity.js @@ -14,16 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -const { app, dialog, webContents } = require('electron'); +const { app } = require('electron'); const axios = require('axios'); const { download } = require('electron-dl'); const fs = require('fs'); const util = require('util'); -const { parity: { channel } } = require('../../package.json'); -const parityPath = require('../util/parityPath'); +const handleError = require('./handleError'); +const { + parity: { channel } +} = require('../../package.json'); +const parityPath = require('../utils/parityPath'); -const fsExists = util.promisify(fs.stat); const fsChmod = util.promisify(fs.chmod); const getArch = () => { @@ -57,35 +59,25 @@ const getOs = () => { } }; -module.exports = (mainWindow) => { - // Download parity if not exist in userData - // Fetching from https://vanity-service.parity.io/parity-binaries - return fsExists(parityPath()) - .catch(() => axios.get(`https://vanity-service.parity.io/parity-binaries?version=${channel}&os=${getOs()}&architecture=${getArch()}`) - .then((response) => response.data[0].files.find(({ name }) => name === 'parity' || name === 'parity.exe')) - .then(({ downloadUrl }) => download( - mainWindow, - downloadUrl, - { - directory: app.getPath('userData'), - onProgress: (progress) => webContents.fromId(mainWindow.id).send('parity-download-progress', progress) - } - )) +// Fetch parity from https://vanity-service.parity.io/parity-binaries +module.exports = mainWindow => + axios + .get( + `https://vanity-service.parity.io/parity-binaries?version=${channel}&os=${getOs()}&architecture=${getArch()}` + ) + .then(response => + response.data[0].files.find( + ({ name }) => name === 'parity' || name === 'parity.exe' + ) + ) + .then(({ downloadUrl }) => + download(mainWindow, downloadUrl, { + directory: app.getPath('userData'), + onProgress: progress => + mainWindow.webContents.send('parity-download-progress', progress) // Notify the renderers + }) ) .then(() => fsChmod(parityPath(), '755')) - .then(() => parityPath()) - .catch((err) => { - console.error(err); - dialog.showMessageBox({ - buttons: ['OK'], - detail: `Please attach the following debugging info: -OS: ${process.platform} -Arch: ${process.arch} -Channel: ${channel} -Error: ${err.message}`, - message: 'An error occured while downloading parity. Please file an issue at https://github.com/parity-js/shell/issues.', - title: 'Parity Error', - type: 'error' - }, () => app.exit(1)); + .catch(err => { + handleError(err, 'An error occured while fetching parity.'); }); -}; diff --git a/electron/operations/handleError.js b/electron/operations/handleError.js new file mode 100644 index 000000000..c7593009a --- /dev/null +++ b/electron/operations/handleError.js @@ -0,0 +1,44 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +const { app, dialog } = require('electron'); + +const { + parity: { channel } +} = require('../../package.json'); +const parityPath = require('../utils/parityPath'); + +module.exports = (err, message = 'An error occurred.') => { + console.error(err); + dialog.showMessageBox( + { + buttons: ['Quit', 'Cancel'], + detail: `Please attach the following debugging info: +OS: ${process.platform} +Arch: ${process.arch} +Channel: ${channel} +Error: ${err.message} +Please also attach the contents of the following file: +${parityPath()}.log + +Please quit the app and retry again. If the error message persists, please file an issue at https://github.com/parity-js/shell/issues.`, + message: `${message}`, + title: 'Parity Error', + type: 'error' + }, + (buttonIndex) => { if (buttonIndex === 0) { app.exit(1); } } + ); +}; diff --git a/electron/messages/runParity.js b/electron/operations/runParity.js similarity index 58% rename from electron/messages/runParity.js rename to electron/operations/runParity.js index e2e4ad78e..7f5f98656 100644 --- a/electron/messages/runParity.js +++ b/electron/operations/runParity.js @@ -14,41 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -const { app, dialog } = require('electron'); const flatten = require('lodash/flatten'); const fs = require('fs'); const { spawn } = require('child_process'); const util = require('util'); const cli = require('../cli'); -const parityPath = require('../util/parityPath'); +const handleError = require('./handleError'); +const parityPath = require('../utils/parityPath'); -const [, parityArgv] = cli(); const fsExists = util.promisify(fs.stat); const fsReadFile = util.promisify(fs.readFile); const fsUnlink = util.promisify(fs.unlink); let parity = null; // Will hold the running parity instance -const handleError = (err) => { - console.error(err); - dialog.showMessageBox({ - buttons: ['OK'], - detail: `Please attach the following debugging info: -OS: ${process.platform} -Arch: ${process.arch} -Error: ${err.message} +module.exports = { + runParity (mainWindow) { + const argv = cli()[0]; + const parityArgv = cli()[1]; -Please also attach the contents of the following file: -${parityPath()}.log`, - message: 'An error occured while running parity. Please file an issue at https://github.com/parity-js/shell/issues.', - title: 'Parity Error', - type: 'error' - }, () => app.exit(1)); -}; + // Do not run parity with --no-run-parity + if (argv.runParity === false) { + return; + } -module.exports = { - runParity () { // Create a logStream to save logs const logFile = `${parityPath()}.log`; @@ -56,36 +46,46 @@ module.exports = { .then(() => fsUnlink(logFile)) .catch(() => { }) .then(() => { - var logStream = fs.createWriteStream(logFile, { flags: 'a' }); + const logStream = fs.createWriteStream(logFile, { flags: 'a' }); // Run an instance of parity if we receive the `run-parity` message parity = spawn( parityPath(), - ['--ws-origins', 'parity://*.ui.parity'] // Argument for retro-compatibility with <1.10 versions - .concat( - flatten(Object.keys(parityArgv).map(key => [`--${key}`, parityArgv[key]])) // Transform {arg: value} into [--arg, value] - .filter(value => value !== true) // --arg true is equivalent to --arg - ) + flatten( + Object.keys(parityArgv).map(key => [`--${key}`, parityArgv[key]]) // Transform {arg: value} into [--arg, value] + ) + .filter(value => value !== true) // --arg true is equivalent to --arg ); parity.stdout.pipe(logStream); parity.stderr.pipe(logStream); - parity.on('error', handleError); + parity.on('error', err => { + handleError(new Error(err), 'An error occured while running parity.'); + }); parity.on('close', (exitCode, signal) => { - if (exitCode === 0) { return; } + if (exitCode === 0) { + return; + } // If the exit code is not 0, then we show some error message if (Object.keys(parityArgv).length) { // If parity has been launched with some args, then most likely the // args are wrong, so we show the output of parity. - fsReadFile(logFile) - .then(data => console.log(data.toString())) - .catch(console.log) - .then(() => app.quit()); + return fsReadFile(logFile).then(data => + console.log(data.toString()) + ); } else { - handleError(new Error(`Exit code: ${exitCode} with signal: ${signal}.`)); + handleError(new Error(`Exit code ${exitCode}, with signal ${signal}.`), 'An error occured while running parity.'); } }); + }) + .then(() => { + // Notify the renderers + // mainWindow.webContents.send('parity-running', true); + global.isParityRunning = true; // Send this variable to renderes via IPC + }) + .catch(err => { + handleError(err, 'An error occured while running parity.'); }); }, killParity () { diff --git a/electron/messages/signerNewToken.js b/electron/operations/signerNewToken.js similarity index 81% rename from electron/messages/signerNewToken.js rename to electron/operations/signerNewToken.js index 93d0722d1..a070641b8 100644 --- a/electron/messages/signerNewToken.js +++ b/electron/operations/signerNewToken.js @@ -16,16 +16,20 @@ const { spawn } = require('child_process'); -const parityPath = require('../util/parityPath'); +const parityPath = require('../utils/parityPath'); -module.exports = (event) => { +module.exports = event => { // Generate a new token const paritySigner = spawn(parityPath(), ['signer', 'new-token']); // Listen to the output of the previous command - paritySigner.stdout.on('data', (data) => { + paritySigner.stdout.on('data', data => { // If the output line is xxxx-xxxx-xxxx-xxxx, then it's our token - const match = data.toString().match(/[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}/); + const match = data + .toString() + .match( + /[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}(-)?[a-zA-Z0-9]{4}/ + ); if (match) { const token = match[0]; diff --git a/electron/util/parityPath.js b/electron/utils/parityPath.js similarity index 100% rename from electron/util/parityPath.js rename to electron/utils/parityPath.js diff --git a/src/Connection/connection.js b/src/Connection/connection.js index 62ee8a255..563065c40 100644 --- a/src/Connection/connection.js +++ b/src/Connection/connection.js @@ -61,7 +61,7 @@ class Connection extends Component { componentDidMount () { if (!isElectron()) { return; } const { ipcRenderer, remote } = electron; - const parityInstalled = remote.getGlobal('parityInstalled'); + const parityInstalled = remote.getGlobal('isParityInstalled'); this.setState({ parityInstalled }); @@ -74,17 +74,20 @@ class Connection extends Component { this.setState({ progress }); }); - // Run parity if parityInstalled - if (!parityInstalled) { return; } + // Next step: if we're not connected, and parity is installed, then we + // run parity. + if (this.props.isConnected || !parityInstalled) { + return; + } // After 3s, check if ui is still isConnecting // If yes, then try to run `parity` // The reason why we do this after 3s, is that even when parity is - // running, isConnecting is true on componentWillMount (lag to ping the + // running, isConnecting is true on componentDidMount (lag to ping the // node). -Amaury 13.03.2018 // TODO Find a more reliable way to know if parity is running or not setTimeout(() => { - if (!this.props.isConnecting) { return; } + if (this.props.isConnected || !this.props.isConnecting) { return; } this.runParity(); }, 3000); }