From 0e8dee05068d720e7c814a738dc2698410279007 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Wed, 29 Mar 2017 11:26:34 -0400 Subject: [PATCH] fix(GUI): provide a friendly message when no polkit agent is available (#1221) `sudo-prompt`, the module we use to provide elevation for GNU/Linux, relies on polkit to show the elevation dialog. Polkit implements a "backend" to control privileges, but relies on different packages to provide a GUI frontend. If no GUI frontend can be found on the system, then `sudo-prompt` will fail with a `No polkit authentication agent found.` In this case, we shouldn't present a scary stack trace to the user, and we should also not send this error to TrackJS. As a solution, we intercept such error in `lib/child-writer/writer-proxy.js`, and convert it into a human readable user error using `errors.createUserError()`, which is passed to the Etcher GUI using the "robot" mechanism. Fixes: https://github.com/resin-io/etcher/issues/1179 Change-Type: patch Changelog-Entry: Provide a user friendly error message when no polkit authentication agent is available on the system. Signed-off-by: Juan Cruz Viotti --- lib/child-writer/index.js | 50 ++++++++++++++++++++++---------- lib/child-writer/writer-proxy.js | 16 +++++++++- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/lib/child-writer/index.js b/lib/child-writer/index.js index 2b348201..514edb14 100644 --- a/lib/child-writer/index.js +++ b/lib/child-writer/index.js @@ -113,25 +113,43 @@ exports.write = (image, drive, options) => { emitter.emit('error', error); }; - ipc.server.on('error', emitError); - ipc.server.on('message', (data) => { - let message = null; - + /** + * @summary Bridge robot message to the child writer caller + * @function + * @private + * + * @param {String} message - robot message + * + * @example + * bridgeRobotMessage(robot.buildMessage('foo', { + * bar: 'baz' + * })); + */ + const bridgeRobotMessage = (message) => { try { - message = robot.parseMessage(data); + const parsedMessage = robot.parseMessage(message); + + // These are lighweight accessor methods for + // the properties of the parsed message + const messageCommand = robot.getCommand(parsedMessage); + const messageData = robot.getData(parsedMessage); + + // The error object is decomposed by the CLI for serialisation + // purposes. We compose it back to an `Error` here in order + // to provide better encapsulation. + if (messageCommand === 'error') { + emitError(robot.recomposeErrorMessage(parsedMessage)); + } else { + emitter.emit(messageCommand, messageData); + } + } catch (error) { - return emitError(error); + emitError(error); } + }; - // The error object is decomposed by the CLI for serialisation - // purposes. We compose it back to an `Error` here in order - // to provide better encapsulation. - if (robot.getCommand(message) === 'error') { - return emitError(robot.recomposeErrorMessage(message)); - } - - return emitter.emit(robot.getCommand(message), robot.getData(message)); - }); + ipc.server.on('error', emitError); + ipc.server.on('message', bridgeRobotMessage); ipc.server.on('start', () => { const child = childProcess.fork(CONSTANTS.WRITER_PROXY_SCRIPT, argv, { @@ -144,7 +162,7 @@ exports.write = (image, drive, options) => { }); child.stderr.on('data', (data) => { - emitError(new Error(data.toString())); + bridgeRobotMessage(data.toString()); // This function causes the `close` event to be emitted child.kill(); diff --git a/lib/child-writer/writer-proxy.js b/lib/child-writer/writer-proxy.js index 7ab3e4a0..be3c9d26 100644 --- a/lib/child-writer/writer-proxy.js +++ b/lib/child-writer/writer-proxy.js @@ -28,6 +28,7 @@ const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt')); const utils = require('./utils'); const EXIT_CODES = require('../shared/exit-codes'); const errors = require('../shared/errors'); +const robot = require('../shared/robot'); const packageJSON = require('../../package.json'); // This script is in charge of spawning the writer process and @@ -153,10 +154,23 @@ return isElevated().then((elevated) => { if (!_.isEmpty(stderr)) { throw errors.createError(stderr); } + + // We're hardcoding internal error messages declared by `sudo-prompt`. + // There doesn't seem to be a better way to handle these errors, so + // for now, we should make sure we double check if the error messages + // have changed every time we upgrade `sudo-prompt`. + }).catch({ message: 'User did not grant permission.' }, () => { process.exit(EXIT_CODES.CANCELLED); + }).catch({ + message: 'No polkit authentication agent found.' + }, () => { + throw errors.createUserError( + 'No polkit authentication agent found', + 'Please install a polkit authentication agent for your desktop environment of choice to continue' + ); }); } @@ -226,6 +240,6 @@ return isElevated().then((elevated) => { process.exit(exitCode); }); }).catch((error) => { - console.error(error); + robot.printError(error); process.exit(EXIT_CODES.GENERAL_ERROR); });