This commit is contained in:
Baobhan Sith
2025-05-08 00:45:42 +08:00
parent f4a5d3cb1c
commit 81b8853932
13 changed files with 370 additions and 2024 deletions
@@ -1,131 +1,93 @@
import axios from 'axios';
import { ConnectionWithTags } from '../types/connection.types';
// RDP 后端服务的 Base URL
const RDP_BACKEND_API_BASE = process.env.DEPLOYMENT_MODE === 'local'
? (process.env.RDP_BACKEND_API_BASE_LOCAL || 'http://localhost:9090')
: (process.env.RDP_BACKEND_API_BASE_DOCKER || 'http://nexus-rdp:9090');
// 统一远程桌面网关服务的 Base URL
const REMOTE_GATEWAY_API_BASE = process.env.DEPLOYMENT_MODE === 'local'
? process.env.REMOTE_GATEWAY_API_BASE_LOCAL || 'http://localhost:9090'
: process.env.REMOTE_GATEWAY_API_BASE_DOCKER || 'http://remote-gateway:9090';
console.log(`[GuacamoleService] DEPLOYMENT_MODE: ${process.env.DEPLOYMENT_MODE}`);
console.log(`[GuacamoleService] RDP_BACKEND_API_BASE_LOCAL: ${process.env.RDP_BACKEND_API_BASE_LOCAL}`);
console.log(`[GuacamoleService] RDP_BACKEND_API_BASE_DOCKER: ${process.env.RDP_BACKEND_API_BASE_DOCKER}`);
console.log(`[GuacamoleService] Using RDP Backend API Base: ${RDP_BACKEND_API_BASE}`);
console.log(`[GuacamoleService] Using Remote Gateway API Base (Local): ${process.env.REMOTE_GATEWAY_API_BASE_LOCAL}`);
console.log(`[GuacamoleService] Using Remote Gateway API Base (Docker): ${process.env.REMOTE_GATEWAY_API_BASE_DOCKER}`);
console.log(`[GuacamoleService] Effective Remote Gateway API Base: ${REMOTE_GATEWAY_API_BASE}`);
// VNC 后端服务的 Base URL
const VNC_BACKEND_API_BASE = process.env.DEPLOYMENT_MODE === 'local'
? (process.env.VNC_BACKEND_API_BASE_LOCAL || 'http://localhost:9091')
: (process.env.VNC_BACKEND_API_BASE_DOCKER || 'http://nexus-vnc:9091');
console.log(`[GuacamoleService] VNC_BACKEND_API_BASE_LOCAL: ${process.env.VNC_BACKEND_API_BASE_LOCAL}`);
console.log(`[GuacamoleService] VNC_BACKEND_API_BASE_DOCKER: ${process.env.VNC_BACKEND_API_BASE_DOCKER}`);
console.log(`[GuacamoleService] Using VNC Backend API Base: ${VNC_BACKEND_API_BASE}`);
interface TokenResponse {
token: string;
}
/**
* 从 RDP 后端服务获取 Guacamole 令牌
* 从统一远程桌面网关服务获取 Guacamole 令牌
* @param protocol 'rdp' 或 'vnc'
* @param connection 连接对象
* @param decryptedPassword 解密后的密码
* @param width 宽度
* @param height 高度
* @param dpi DPI (主要用于 RDP)
* @returns Guacamole 令牌
*/
export const getRdpToken = async (connection: ConnectionWithTags, decryptedPassword?: string): Promise<string> => {
if (connection.type !== 'RDP') {
throw new Error('连接类型必须是 RDP。');
export const getRemoteDesktopToken = async (
protocol: 'rdp' | 'vnc',
connection: ConnectionWithTags,
decryptedPassword?: string,
width?: number,
height?: number,
dpi?: string // DPI 主要用于 RDP
): Promise<string> => {
if ((protocol === 'rdp' || protocol === 'vnc') && connection.auth_method === 'password' && !decryptedPassword) {
console.warn(`[GuacamoleService:getRemoteDesktopToken] ${protocol.toUpperCase()} connection ${connection.id} uses password auth but password decryption failed or password not provided.`);
throw new Error(`${protocol.toUpperCase()} 连接使用密码认证,但密码解密失败或未提供密码。`);
}
if (connection.auth_method !== 'password' || !decryptedPassword) {
console.warn(`[GuacamoleService:getRdpToken] RDP connection ${connection.id} does not use password auth or password decryption failed.`);
throw new Error('RDP 连接需要使用密码认证,或密码解密失败。');
}
const rdpApiParams = new URLSearchParams({
const connectionConfig: any = {
hostname: connection.host,
port: connection.port.toString(),
username: connection.username,
password: decryptedPassword,
// 确保传递 RDP 特定的参数,如果存在的话
security: (connection as any).rdp_security || 'any', // 从连接对象中获取,如果存在
ignoreCert: String((connection as any).rdp_ignore_cert ?? true), // 从连接对象中获取,如果存在
// 可以根据需要添加更多参数,例如 domain, width, height, dpi 等
});
const rdpTokenUrl = `${RDP_BACKEND_API_BASE}/api/get-token?${rdpApiParams.toString()}`;
width: String(width || 1024), // 提供默认值
height: String(height || 768), // 提供默认值
};
console.log(`[GuacamoleService:getRdpToken] Calling RDP backend API: ${RDP_BACKEND_API_BASE}/api/get-token?... for connection ${connection.id}`);
if (protocol === 'rdp') {
if (!connection.username) {
console.warn(`[GuacamoleService:getRemoteDesktopToken] RDP connection ${connection.id} is missing username.`);
// 对于RDP,用户名通常是必需的,但让网关决定是否可以为空
}
connectionConfig.username = connection.username || ''; // RDP 通常需要用户名
connectionConfig.password = decryptedPassword || ''; // RDP 通常需要密码
connectionConfig.dpi = dpi || '96';
connectionConfig.security = (connection as any).rdp_security || 'any';
connectionConfig.ignoreCert = String((connection as any).rdp_ignore_cert ?? true);
} else if (protocol === 'vnc') {
connectionConfig.password = decryptedPassword || ''; // VNC 通常需要密码
if (connection.username) { // VNC 用户名是可选的
connectionConfig.username = connection.username;
}
// 其他 VNC 特定参数可以从 connection.extras 获取
// 例如: if (connection.extras?.enableAudio) connectionConfig.enableAudio = connection.extras.enableAudio;
}
const requestBody = {
protocol,
connectionConfig
};
const tokenUrl = `${REMOTE_GATEWAY_API_BASE}/api/remote-desktop/token`;
console.log(`[GuacamoleService:getRemoteDesktopToken] Calling Remote Gateway API: ${tokenUrl} for protocol ${protocol}, connection ${connection.id}`);
try {
const rdpResponse = await axios.get<{ token: string }>(rdpTokenUrl, {
const response = await axios.post<TokenResponse>(tokenUrl, requestBody, {
timeout: 10000 // 10 秒超时
});
if (rdpResponse.status !== 200 || !rdpResponse.data?.token) {
console.error(`[GuacamoleService:getRdpToken] RDP backend API call failed or returned invalid data. Status: ${rdpResponse.status}`, rdpResponse.data);
throw new Error('从 RDP 后端获取令牌失败。');
if (response.status !== 200 || !response.data?.token) {
console.error(`[GuacamoleService:getRemoteDesktopToken] ${protocol.toUpperCase()} backend API call failed or returned invalid data. Status: ${response.status}`, response.data);
throw new Error(`${protocol.toUpperCase()} 后端获取令牌失败。`);
}
console.log(`[GuacamoleService:getRdpToken] Received Guacamole token from RDP backend for connection ${connection.id}`);
return rdpResponse.data.token;
console.log(`[GuacamoleService:getRemoteDesktopToken] Received Guacamole token from ${protocol.toUpperCase()} backend for connection ${connection.id}`);
return response.data.token;
} catch (error: any) {
console.error(`[GuacamoleService:getRdpToken] Error calling RDP backend for connection ${connection.id}:`, error.message);
console.error(`[GuacamoleService:getRemoteDesktopToken] Error calling ${protocol.toUpperCase()} backend for connection ${connection.id}:`, error.message);
if (axios.isAxiosError(error) && error.response) {
throw new Error(`调用 RDP 后端服务失败 (状态: ${error.response.status}): ${error.response.data?.message || error.message}`);
throw new Error(`调用 ${protocol.toUpperCase()} 后端服务失败 (状态: ${error.response.status}): ${error.response.data?.message || error.message}`);
}
throw new Error(`调用 RDP 后端服务时发生错误: ${error.message}`);
}
};
/**
* 从 VNC 后端服务获取 Guacamole 令牌
* @param connection 连接对象
* @param decryptedPassword 解密后的密码 (VNC 通常需要密码)
* @returns Guacamole 令牌
*/
export const getVncToken = async (connection: ConnectionWithTags, decryptedPassword?: string, width?: number, height?: number): Promise<string> => {
if (connection.type !== 'VNC') {
throw new Error('连接类型必须是 VNC。');
}
// VNC 通常总是需要密码,并且 auth_method 应该被设置为 'password'
if (connection.auth_method !== 'password' || !decryptedPassword) {
console.warn(`[GuacamoleService:getVncToken] VNC connection ${connection.id} does not use password auth or password decryption failed.`);
throw new Error('VNC 连接需要使用密码认证,或密码解密失败。');
}
const vncApiParams = new URLSearchParams({
hostname: connection.host,
port: connection.port.toString(),
password: decryptedPassword, // VNC 通常只需要密码
// VNC 特有的参数可以根据 @nexus-terminal/vnc 的 API 进行添加
// 例如: username (如果 VNC 服务器需要), colorDepth, etc.
// username: connection.username, // 如果 VNC 服务支持用户名
});
if (width !== undefined) {
vncApiParams.append('width', String(width));
}
if (height !== undefined) {
vncApiParams.append('height', String(height));
}
// 如果 VNC 服务也支持用户名,可以取消注释上面的 username 参数
// 注意:标准的 VNC 协议主要通过密码进行认证,用户名不是标准部分,但某些实现可能支持。
// 这里假设 @nexus-terminal/vnc 的 /api/get-vnc-token 接受这些参数。
const vncTokenUrl = `${VNC_BACKEND_API_BASE}/api/get-vnc-token?${vncApiParams.toString()}`;
console.log(`[GuacamoleService:getVncToken] Calling VNC backend API: ${VNC_BACKEND_API_BASE}/api/get-vnc-token?... for connection ${connection.id}`);
try {
const vncResponse = await axios.get<{ token: string }>(vncTokenUrl, {
timeout: 10000 // 10 秒超时
});
if (vncResponse.status !== 200 || !vncResponse.data?.token) {
console.error(`[GuacamoleService:getVncToken] VNC backend API call failed or returned invalid data. Status: ${vncResponse.status}`, vncResponse.data);
throw new Error('从 VNC 后端获取令牌失败。');
}
console.log(`[GuacamoleService:getVncToken] Received Guacamole token from VNC backend for connection ${connection.id}`);
return vncResponse.data.token;
} catch (error: any) {
console.error(`[GuacamoleService:getVncToken] Error calling VNC backend for connection ${connection.id}:`, error.message);
if (axios.isAxiosError(error) && error.response) {
throw new Error(`调用 VNC 后端服务失败 (状态: ${error.response.status}): ${error.response.data?.message || error.message}`);
}
throw new Error(`调用 VNC 后端服务时发生错误: ${error.message}`);
throw new Error(`调用 ${protocol.toUpperCase()} 后端服务时发生错误: ${error.message}`);
}
};