chore: 🤖 add ipc handlers for rdp (#3031)
This commit is contained in:
1 parent
437d0e686d
commit
6554af1164
6 files changed
+205
-10
No files matched your search
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
const { path } = require('../cli/path.js');
|
||||
const { path: boundaryPath } = require('../cli/path.js');
|
||||
const { spawn, spawnSync } = require('child_process');
|
||||
const jsonify = require('../utils/jsonify.js');
|
||||
|
||||
@@ -34,7 +34,7 @@ module.exports = {
|
||||
*/
|
||||
spawnAsyncJSONPromise(command, token, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const childProcess = spawn(path, command, {
|
||||
const childProcess = spawn(boundaryPath, command, {
|
||||
env: {
|
||||
...process.env,
|
||||
BOUNDARY_TOKEN: token,
|
||||
@@ -78,8 +78,9 @@ module.exports = {
|
||||
* This function is intended for non-connection related tasks.
|
||||
* @param {string[]} args
|
||||
* @param {object} envVars
|
||||
* @param {string} path
|
||||
* @returns {{stdout: string | undefined, stderr: string | undefined}} */
|
||||
spawnSync(args, envVars = {}) {
|
||||
spawnSync(args, envVars = {}, path = boundaryPath) {
|
||||
const childProcess = spawnSync(path, args, {
|
||||
// Some of our outputs (namely cache daemon searching) can be very large.
|
||||
// This an undocumented hack to allow for an unlimited buffer size which
|
||||
@@ -103,8 +104,9 @@ module.exports = {
|
||||
* Resolves on any output from stdout or stderr.
|
||||
* @param command
|
||||
* @param options
|
||||
* @param path
|
||||
*/
|
||||
spawn(command, options) {
|
||||
spawn(command, options, path = boundaryPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const childProcess = spawn(path, command, options);
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
|
||||
@@ -26,6 +26,7 @@ const { generateCSPHeader } = require('./config/content-security-policy.js');
|
||||
const runtimeSettings = require('./services/runtime-settings.js');
|
||||
const sessionManager = require('./services/session-manager.js');
|
||||
const cacheDaemonManager = require('./services/cache-daemon-manager');
|
||||
const rdpClientManager = require('./services/rdp-client-manager');
|
||||
const store = require('./services/electron-store-manager');
|
||||
|
||||
const menu = require('./config/menu.js');
|
||||
@@ -297,6 +298,8 @@ app.on('before-quit', async (event) => {
|
||||
|
||||
app.on('quit', () => {
|
||||
cacheDaemonManager.stop();
|
||||
// we should stop any active RDP client processes
|
||||
rdpClientManager.stopAll();
|
||||
});
|
||||
|
||||
// Handle an unhandled error in the main thread
|
||||
|
||||
@@ -19,6 +19,7 @@ const cacheDaemonManager = require('../services/cache-daemon-manager');
|
||||
const clientAgentDaemonManager = require('../services/client-agent-daemon-manager');
|
||||
const { releaseVersion } = require('../../config/config.js');
|
||||
const store = require('../services/electron-store-manager');
|
||||
const rdpClientManager = require('../services/rdp-client-manager');
|
||||
|
||||
/**
|
||||
* Returns the current runtime clusterUrl, which is used by the main thread to
|
||||
@@ -272,24 +273,28 @@ handle('getLogPath', () => {
|
||||
/**
|
||||
* Returns the available RDP clients
|
||||
*/
|
||||
handle('getRdpClients', async () => []);
|
||||
handle('getRdpClients', async () => rdpClientManager.getAvailableRdpClients());
|
||||
|
||||
/**
|
||||
* Returns the preferred RDP client
|
||||
*/
|
||||
handle('getPreferredRdpClient', async () => 'none');
|
||||
handle('getPreferredRdpClient', async () =>
|
||||
rdpClientManager.getPreferredRdpClient(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the preferred RDP client
|
||||
*/
|
||||
handle('setPreferredRdpClient', async (rdpClient) => rdpClient);
|
||||
handle('setPreferredRdpClient', (preferredClient) =>
|
||||
rdpClientManager.setPreferredRdpClient(preferredClient),
|
||||
);
|
||||
|
||||
/**
|
||||
* Launches the RDP client with the provided session ID.
|
||||
*/
|
||||
handle('launchRdpClient', async (sessionId) => {
|
||||
return;
|
||||
});
|
||||
handle('launchRdpClient', async (sessionId) =>
|
||||
rdpClientManager.launchRdpClient(sessionId, sessionManager),
|
||||
);
|
||||
|
||||
/**
|
||||
* Handler to help create terminal windows. We don't use the helper `handle` method
|
||||
|
||||
@@ -46,6 +46,15 @@ class SessionManager {
|
||||
return session?.stop?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session by identifier.
|
||||
* @param {string} sessionId
|
||||
* @returns {Session} The session object
|
||||
*/
|
||||
getSessionById(sessionId) {
|
||||
return this.#sessions.find((session) => session.id === sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all active and pending target sessions
|
||||
* Returning Promise.all() ensures all sessions in the list have been
|
||||
|
||||
@@ -52,6 +52,14 @@ class Session {
|
||||
return this.#process && !this.#process.killed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proxy details for the session
|
||||
* @return {Object}
|
||||
*/
|
||||
get proxyDetails() {
|
||||
return this.#proxyDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cli command for session.
|
||||
* @returns {string[]}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
const { spawn, spawnSync } = require('../helpers/spawn-promise');
|
||||
const { shell } = require('electron');
|
||||
const fs = require('fs');
|
||||
const which = require('which');
|
||||
const { isMac, isWindows } = require('../helpers/platform.js');
|
||||
const store = require('./electron-store-manager');
|
||||
|
||||
// RDP Client Configuration
|
||||
const RDP_CLIENTS = [
|
||||
{
|
||||
value: 'mstsc',
|
||||
isAvailable: async () => {
|
||||
if (!isWindows()) return false;
|
||||
try {
|
||||
const mstscPath = await which('mstsc', { nothrow: true });
|
||||
return Boolean(mstscPath);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 'windows-app',
|
||||
isAvailable: () => {
|
||||
if (!isMac()) return false;
|
||||
try {
|
||||
// Check for Windows App
|
||||
if (fs.existsSync('/Applications/Windows App.app')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: Use mdfind for Microsoft Remote Desktop
|
||||
else {
|
||||
const result = spawnSync(
|
||||
['kMDItemCFBundleIdentifier == "com.microsoft.rdc.macos"'],
|
||||
{},
|
||||
'mdfind',
|
||||
);
|
||||
// `mdfind` returns an object with stdout containing the path of the app if found
|
||||
// Example: '/Applications/Windows App.app\n'
|
||||
return result.stdout && result.stdout.trim().length > 0;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
isAvailable: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
class RdpClientManager {
|
||||
// Track active RDP processes for cleanup
|
||||
#activeProcesses = [];
|
||||
/**
|
||||
* Gets all available RDP clients on the current system
|
||||
* @returns {Promise<Array>} Array of available RDP client values
|
||||
*/
|
||||
async getAvailableRdpClients() {
|
||||
const available = [];
|
||||
for (const client of RDP_CLIENTS) {
|
||||
if (await client.isAvailable()) {
|
||||
available.push(client.value);
|
||||
}
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the best default RDP client (first available that's not 'none')
|
||||
* @returns {Promise<string>} Best RDP client value or 'none'
|
||||
*/
|
||||
async getBestDefaultRdpClient() {
|
||||
const availableClients = await this.getAvailableRdpClients();
|
||||
const bestClient = availableClients.find((client) => client !== 'none');
|
||||
return bestClient ?? 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's preferred RDP client, auto-detecting if not set
|
||||
* @returns {Promise<string>} Preferred RDP client value
|
||||
*/
|
||||
async getPreferredRdpClient() {
|
||||
let preferredClient = store.get('preferredRdpClient');
|
||||
|
||||
if (!preferredClient) {
|
||||
// Auto-detect and set the best available client
|
||||
preferredClient = await this.getBestDefaultRdpClient();
|
||||
store.set('preferredRdpClient', preferredClient);
|
||||
}
|
||||
return preferredClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user's preferred RDP client
|
||||
* @param {string} preferredClient - The RDP client value to set as preferred
|
||||
*/
|
||||
setPreferredRdpClient(preferredClient) {
|
||||
if (!preferredClient) {
|
||||
store.set('preferredRdpClient', 'none');
|
||||
} else {
|
||||
store.set('preferredRdpClient', preferredClient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches RDP connection with the specified address and port
|
||||
* @param {string} address - Target address
|
||||
* @param {number} port - Target port
|
||||
*/
|
||||
async launchRdpConnection(address, port) {
|
||||
if (isWindows()) {
|
||||
// Launch Windows mstsc and track it for cleanup
|
||||
const mstscArgs = [`/v:${address}:${port}`];
|
||||
const { childProcess } = await spawn(mstscArgs, {}, 'mstsc');
|
||||
// Add to activeProcesses array for cleanup
|
||||
this.#activeProcesses.push(childProcess);
|
||||
} else if (isMac()) {
|
||||
// Launch macOS RDP URL - no process to track as it's handled by the system
|
||||
const fullAddress = `${address}:${port}`;
|
||||
const encoded = encodeURIComponent(`full address=s:${fullAddress}`);
|
||||
const rdpUrl = `rdp://${encoded}`;
|
||||
await shell.openExternal(rdpUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches RDP client using session ID
|
||||
* Retrieves session object from session manager and launches appropriate RDP client
|
||||
* @param {string} sessionId - The session ID to get session for
|
||||
* @param {Object} sessionManager - Session manager instance to get session from
|
||||
*/
|
||||
async launchRdpClient(sessionId, sessionManager) {
|
||||
// Get session object from session manager
|
||||
const session = sessionManager.getSessionById(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
proxyDetails: { address, port },
|
||||
} = session;
|
||||
// Launch RDP connection
|
||||
await this.launchRdpConnection(address, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all active RDP processes
|
||||
*/
|
||||
stopAll() {
|
||||
for (const process of this.#activeProcesses) {
|
||||
if (!process.killed) {
|
||||
process.kill();
|
||||
}
|
||||
}
|
||||
// Clear the active processes array after stopping all processes
|
||||
this.#activeProcesses = [];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new RdpClientManager();
|
||||
Reference in new issue
Block a user