From dfbb54a1eec9edc22205e8e6018eb5d7ba824f45 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Tue, 11 May 2021 16:38:26 -0400 Subject: [PATCH] refactor: Add SessionManager and Session classes to electron --- ui/desktop/electron-app/src/boundary-cli.js | 36 +------ ui/desktop/electron-app/src/handlers.js | 11 ++- ui/desktop/electron-app/src/session/index.js | 99 +++++++++++++++++++ .../electron-app/src/session/manager.js | 39 ++++++++ ui/desktop/electron-app/src/spawn-promise.js | 21 ++-- 5 files changed, 157 insertions(+), 49 deletions(-) create mode 100644 ui/desktop/electron-app/src/session/index.js create mode 100644 ui/desktop/electron-app/src/session/manager.js diff --git a/ui/desktop/electron-app/src/boundary-cli.js b/ui/desktop/electron-app/src/boundary-cli.js index eeb5050f8..9eeaa4f8d 100644 --- a/ui/desktop/electron-app/src/boundary-cli.js +++ b/ui/desktop/electron-app/src/boundary-cli.js @@ -1,46 +1,12 @@ const path = require('path'); -const { spawnAsyncJSONPromise, spawnSync } = require('./spawn-promise'); -const origin = require('./origin.js'); +const { spawnSync } = require('./spawn-promise'); const cliPath = async () => path.resolve(__dirname, '..', 'cli', 'boundary'); -/** - * Super paranoid shell quote/escape and validation. Input must be base62. - * @param {string} str - */ -const escapeAndValidateBase62 = (str) => { - const candidate = str.toString(); - if (candidate.match(/^[A-Za-z0-9_]*$/)) return candidate; - throw new Error(` - Could not invoke command: - input contained unsafe characters. - `); -}; - module.exports = { // Check boundary cli existence exists: () => Boolean(cliPath()), - // Initiate connection and return output - connect: (target_id, token, host_id) => { - const addr = origin.origin; - const sanitized = { - target_id: escapeAndValidateBase62(target_id), - token: escapeAndValidateBase62(token), - }; - if (host_id) sanitized.host_id = escapeAndValidateBase62(host_id); - const command = [ - 'connect', - `-target-id=${sanitized.target_id}`, - `-token=${sanitized.token}`, - `-addr=${addr}`, - '-format=json', - ]; - - if (host_id) command.push(`-host-id=${sanitized.host_id}`); - return spawnAsyncJSONPromise(command); - }, - // Returns JSON-formatted version information from the CLI version: () => { const command = ['-v']; diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index 04343c5ae..7127c1f12 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -3,7 +3,7 @@ const isDev = require('electron-is-dev'); const handle = require('./ipc-handler'); const boundaryCli = require('./boundary-cli'); const origin = require('./origin'); -const spawnSession = require('./spawn-session'); +const sessionManager = require('./session/manager'); /** * Returns the current runtime origin, which is used by the main thread to @@ -57,12 +57,13 @@ handle('cliExists', () => boundaryCli.exists()); * Establishes a boundary session and returns session details. */ handle('connect', ({ target_id, token, host_id }) => - boundaryCli.connect(target_id, token, host_id) + sessionManager.start(origin.origin, target_id, token, host_id) ); /** * Cancel an established boundary session spawned process. */ -handle('cancel', ({ session_id }) => { - spawnSession.cancelSession(session_id); -}); +handle('cancel', ({ session_id }) => + // spawnSession.cancelSession(session_id); + sessionManager.stop(session_id) +); diff --git a/ui/desktop/electron-app/src/session/index.js b/ui/desktop/electron-app/src/session/index.js new file mode 100644 index 000000000..3d5563f1d --- /dev/null +++ b/ui/desktop/electron-app/src/session/index.js @@ -0,0 +1,99 @@ +const { spawnAsyncJSONPromise } = require('../spawn-promise'); + +// FIXME create structure 'util' folder for spawn-promise + +/** + * Super paranoid shell quote/escape and validation. Input must be base62. + * @param {string} str + */ +const escapeAndValidateBase62 = (str) => { + const candidate = str.toString(); + if (candidate.match(/^[A-Za-z0-9_]*$/)) return candidate; + throw new Error(` + Could not invoke command: + input contained unsafe characters. + `); +}; + +class Session { + #id; + #pid; + #addr; + #token; + #hostId; + #targetId; + #proxyDetails; + + /** + * Initialize a session to a controller address + * using target details. + * @param {string} addr + * @param {string} targetId + * @param {string} token + * @param {string} hostId + */ + constructor(addr, targetId, token, hostId) { + this.#addr = addr; + this.#targetId = targetId; + this.#token = token; + this.#hostId = hostId; + } + + /** + * @return {boolean} + */ + get isActive() { + return Boolean(this.#pid); + } + + /** + * Returns session proxy details. + * Only available after successful start of a session. + * @return {object} + */ + get proxyDetails() { + return this.#proxyDetails; + } + + // Spawn process + // Track spawned process this.pid = pid; + /** + * Using cli, initialize a session to a target. + * Tracks local proxy details if successful. + */ + start() { + const command = this.cliCommand(); + return spawnAsyncJSONPromise(command).then((spawnedSession) => { + this.#id = this.#proxyDetails.session_id; + this.#pid = spawnedSession.childProcess.pid; + this.#proxyDetails = spawnedSession.response; + }); + } + + /** + * Generate cli command for session. + * @returns {[]} + */ + cliCommand() { + const sanitized = { + target_id: escapeAndValidateBase62(this.#targetId), + token: escapeAndValidateBase62(this.#token), + }; + + const command = [ + 'connect', + `-target-id=${sanitized.target_id}`, + `-token=${sanitized.token}`, + `-addr=${this.#addr}`, + '-format=json', + ]; + + if (this.#hostId) { + sanitized.host_id = escapeAndValidateBase62(this.#hostId); + command.push(`-host-id=${sanitized.host_id}`); + } + return command; + } +} + +module.exports = Session; diff --git a/ui/desktop/electron-app/src/session/manager.js b/ui/desktop/electron-app/src/session/manager.js new file mode 100644 index 000000000..60be77204 --- /dev/null +++ b/ui/desktop/electron-app/src/session/manager.js @@ -0,0 +1,39 @@ +const Session = require('./index.js'); + +class SessionManager { + #sessions; + + constructor() { + this.#sessions = []; + } + + /** + * Start a session and track it. + * Returns session proxy details if successfully started. + * Only successfully started sessions will be tracked. + * @param {string} addr + * @param {string} target_id + * @param {string} token + * @param {string} host_id + */ + start(addr, target_id, token, host_id) { + const session = new Session(addr, target_id, token, host_id); + return session.start().then(() => { + this.#sessions.push(session); + return session.proxyDetails; + }); + } + + stopById() { + } + + stopAll() { + } + + get sessions() { + return this.#sessions; + } +} + +const sessionManager = new SessionManager(); +module.exports = sessionManager; diff --git a/ui/desktop/electron-app/src/spawn-promise.js b/ui/desktop/electron-app/src/spawn-promise.js index d96b08953..10467ffc6 100644 --- a/ui/desktop/electron-app/src/spawn-promise.js +++ b/ui/desktop/electron-app/src/spawn-promise.js @@ -47,25 +47,28 @@ module.exports = { outputStream += data.toString(); const jsonData = jsonify(outputStream); if (jsonData) { - if (jsonData.termination_reason) { - spawnSession.cancelProcess(childProcess.pid); - } else { - // Track only successfully launched child processes - spawnSession.add({ childProcess, data: jsonData }); - } - outputStream = ''; - resolve(jsonData); + // if (jsonData.termination_reason) { + // spawnSession.cancelProcess(childProcess.pid); + // } else { + // // Track only successfully launched child processes + // spawnSession.add({ childProcess, data: jsonData }); + // } + // FIXME + // outputStream = ''; + resolve({ childProcess, response: jsonData }); } }); // @todo Cancel spawned session childprocess on error? Does it get automatically cancelled? + // @todo on error - should childprocess be closed? // Test on connection limit errors especially childProcess.stderr.on('data', (data) => { errorStream += data.toString(); const jsonData = jsonify(errorStream); if (jsonData) { const error = jsonData.api_error || jsonData.error; - errorStream = ''; + // FIXME + // errorStream = ''; reject(new Error(error.message)); } });