update
This commit is contained in:
@@ -157,3 +157,6 @@ dist-scripts/
|
||||
|
||||
# Ignore specific generated file in root
|
||||
/tsc
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
@@ -16,6 +16,8 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: packages/backend/Dockerfile
|
||||
env_file:
|
||||
- .env # Load environment variables from .env file in the root
|
||||
container_name: nexus-terminal-backend
|
||||
ports:
|
||||
- "18112:3001"
|
||||
|
||||
Generated
+6
-5
@@ -623,6 +623,10 @@
|
||||
"resolved": "packages/frontend",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@nexus-terminal/rdp": {
|
||||
"resolved": "packages/rdp",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -2057,10 +2061,6 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/backend": {
|
||||
"resolved": "packages/rdp",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/bagpipe": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz",
|
||||
@@ -7611,6 +7611,7 @@
|
||||
"sqlite3": "^5.1.7",
|
||||
"ssh2": "^1.16.0",
|
||||
"uuid": "^11.1.0",
|
||||
"ws": "^8.18.1",
|
||||
"xterm": "^5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -7666,7 +7667,7 @@
|
||||
}
|
||||
},
|
||||
"packages/rdp": {
|
||||
"name": "backend",
|
||||
"name": "@nexus-terminal/rdp",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"sqlite3": "^5.1.7",
|
||||
"ssh2": "^1.16.0",
|
||||
"uuid": "^11.1.0",
|
||||
"xterm": "^5.3.0",
|
||||
"ws": "^8.18.1"
|
||||
"ws": "^8.18.1",
|
||||
"xterm": "^5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
|
||||
@@ -30,13 +30,36 @@ import './services/notification.dispatcher.service'; // 确保分发器被加载
|
||||
// --- 结束通知系统初始化 ---
|
||||
// --- 环境变量和密钥初始化 ---
|
||||
const initializeEnvironment = async () => {
|
||||
const rootEnvPath = path.resolve(__dirname, '../data/.env'); // 指向项目根目录的 .env
|
||||
// 1. 加载根目录的 .env 文件 (定义部署模式等)
|
||||
// 注意: __dirname 在 dist/src 中,所以需要回退三级到项目根目录
|
||||
const projectRootEnvPath = path.resolve(__dirname, '../../../.env');
|
||||
const rootConfigResult = dotenv.config({ path: projectRootEnvPath });
|
||||
// Use type assertion for error code checking
|
||||
if (rootConfigResult.error && (rootConfigResult.error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||
// 只在文件存在但无法加载时发出警告
|
||||
console.warn(`[ENV Init] Warning: Could not load root .env file from ${projectRootEnvPath}. Error: ${rootConfigResult.error.message}`);
|
||||
} else if (!rootConfigResult.error) {
|
||||
console.log(`[ENV Init] Loaded environment variables from root .env file: ${projectRootEnvPath}`);
|
||||
} else {
|
||||
console.log(`[ENV Init] Root .env file not found at ${projectRootEnvPath}, proceeding without it (expected in non-local deployments where env vars are injected).`);
|
||||
}
|
||||
|
||||
// 2. 加载 data/.env 文件 (定义密钥等)
|
||||
// 注意: 这个路径是相对于编译后的 dist/src/index.js
|
||||
const dataEnvPath = path.resolve(__dirname, '../data/.env');
|
||||
let keysGenerated = false;
|
||||
let keysToAppend = '';
|
||||
|
||||
// 1. 尝试加载根目录的 .env 文件 (如果存在)
|
||||
// dotenv.config 不会覆盖已存在的 process.env 变量
|
||||
dotenv.config({ path: rootEnvPath });
|
||||
// dotenv.config 默认不会覆盖已存在的 process.env 变量
|
||||
// 这意味着如果根 .env 和 data/.env 定义了相同的变量,先加载的(根 .env)的值会优先
|
||||
const dataConfigResult = dotenv.config({ path: dataEnvPath });
|
||||
// Use type assertion for error code checking
|
||||
if (dataConfigResult.error && (dataConfigResult.error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||
// 只在文件存在但无法加载时发出警告,文件不存在是正常情况
|
||||
console.warn(`[ENV Init] Warning: Could not load data .env file from ${dataEnvPath}. Error: ${dataConfigResult.error.message}`);
|
||||
} else if (!dataConfigResult.error) {
|
||||
console.log(`[ENV Init] Loaded environment variables from data .env file: ${dataEnvPath}`);
|
||||
}
|
||||
|
||||
// 2. 检查 ENCRYPTION_KEY
|
||||
if (!process.env.ENCRYPTION_KEY) {
|
||||
@@ -76,20 +99,20 @@ const initializeEnvironment = async () => {
|
||||
// 5. 如果生成了新密钥或添加了默认值,则追加到 .env 文件
|
||||
if (keysGenerated) {
|
||||
try {
|
||||
// 确保追加前有换行符 (如果文件非空)
|
||||
// 确保追加前有换行符 (如果文件非空) - Use dataEnvPath here
|
||||
let prefix = '';
|
||||
if (fs.existsSync(rootEnvPath)) {
|
||||
const content = fs.readFileSync(rootEnvPath, 'utf-8');
|
||||
if (fs.existsSync(dataEnvPath)) { // Use dataEnvPath
|
||||
const content = fs.readFileSync(dataEnvPath, 'utf-8'); // Use dataEnvPath
|
||||
if (content.trim().length > 0 && !content.endsWith('\n')) {
|
||||
prefix = '\n';
|
||||
}
|
||||
}
|
||||
fs.appendFileSync(rootEnvPath, prefix + keysToAppend.trim()); // trim() 移除开头的换行符
|
||||
console.warn(`[ENV Init] 已自动生成密钥并保存到 ${rootEnvPath}`);
|
||||
console.warn('[ENV Init] !!! 重要:请务必备份此 .env 文件,并在生产环境中妥善保管 !!!');
|
||||
fs.appendFileSync(dataEnvPath, prefix + keysToAppend.trim()); // Use dataEnvPath, trim() 移除开头的换行符
|
||||
console.warn(`[ENV Init] 已自动生成密钥并保存到 ${dataEnvPath}`); // Use dataEnvPath
|
||||
console.warn('[ENV Init] !!! 重要:请务必备份此 data/.env 文件,并在生产环境中妥善保管 !!!');
|
||||
} catch (error) {
|
||||
console.error(`[ENV Init] 无法写入密钥到 ${rootEnvPath}:`, error);
|
||||
console.error('[ENV Init] 请检查文件权限或手动创建 .env 文件并添加生成的密钥。');
|
||||
console.error(`[ENV Init] 无法写入密钥到 ${dataEnvPath}:`, error); // Use dataEnvPath
|
||||
console.error('[ENV Init] 请检查文件权限或手动创建 data/.env 文件并添加生成的密钥。');
|
||||
// 即使写入失败,密钥已在 process.env 中,程序可以继续运行本次
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import WebSocket, { WebSocketServer } from 'ws';
|
||||
import WebSocket, { WebSocketServer, RawData } from 'ws';
|
||||
import http from 'http';
|
||||
import url from 'url';
|
||||
// path and dotenv are no longer needed here as env vars are loaded in index.ts
|
||||
// import path from 'path';
|
||||
// import dotenv from 'dotenv';
|
||||
import { Request, RequestHandler } from 'express';
|
||||
import { Client, ClientChannel } from 'ssh2';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -373,6 +377,9 @@ const fetchRemoteDockerStatus = async (state: ClientState): Promise<{ available:
|
||||
|
||||
|
||||
export const initializeWebSocket = async (server: http.Server, sessionParser: RequestHandler): Promise<WebSocketServer> => {
|
||||
// Environment variables (including DEPLOYMENT_MODE and RDP URLs)
|
||||
// are now expected to be loaded by index.ts before this function is called.
|
||||
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
const db = await getDbInstance();
|
||||
const DOCKER_STATUS_INTERVAL = 2000;
|
||||
@@ -393,58 +400,183 @@ export const initializeWebSocket = async (server: http.Server, sessionParser: Re
|
||||
|
||||
// --- WebSocket 升级处理 (认证) ---
|
||||
server.on('upgrade', (request: Request, socket, head) => {
|
||||
const parsedUrl = url.parse(request.url || '', true); // Parse URL and query string
|
||||
const pathname = parsedUrl.pathname;
|
||||
const ipAddress = request.ip; // Get IP address early
|
||||
|
||||
console.log(`WebSocket: 升级请求来自 IP: ${ipAddress}, Path: ${pathname}`);
|
||||
|
||||
// @ts-ignore Express-session 类型问题
|
||||
sessionParser(request, {} as any, () => {
|
||||
// --- 认证检查 ---
|
||||
if (!request.session || !request.session.userId) {
|
||||
console.log('WebSocket 认证失败:未找到会话或用户未登录。');
|
||||
console.log(`WebSocket 认证失败 (Path: ${pathname}):未找到会话或用户未登录。`);
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
console.log(`WebSocket 认证成功:用户 ${request.session.username} (ID: ${request.session.userId})`);
|
||||
// 获取客户端 IP 地址
|
||||
const ipAddress = request.ip;
|
||||
console.log(`WebSocket: 升级请求来自 IP: ${ipAddress}`);
|
||||
console.log(`WebSocket 认证成功 (Path: ${pathname}):用户 ${request.session.username} (ID: ${request.session.userId})`);
|
||||
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
const extWs = ws as AuthenticatedWebSocket;
|
||||
extWs.userId = request.session.userId;
|
||||
extWs.username = request.session.username;
|
||||
// 将 IP 地址附加到 request 对象上传递给 connection 事件处理器,以便后续使用
|
||||
(request as any).clientIpAddress = ipAddress;
|
||||
wss.emit('connection', extWs, request);
|
||||
});
|
||||
// --- 根据路径处理升级 ---
|
||||
if (pathname === '/rdp-proxy') {
|
||||
// RDP 代理路径 - 直接处理升级,连接逻辑在 'connection' 事件中处理
|
||||
console.log(`WebSocket: Handling RDP proxy upgrade for user ${request.session.username}`);
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
const extWs = ws as AuthenticatedWebSocket;
|
||||
extWs.userId = request.session.userId;
|
||||
extWs.username = request.session.username;
|
||||
// 传递必要信息给 connection 事件
|
||||
(request as any).clientIpAddress = ipAddress;
|
||||
(request as any).isRdpProxy = true; // 标记为 RDP 代理连接
|
||||
(request as any).rdpToken = parsedUrl.query.token; // 传递 RDP token
|
||||
wss.emit('connection', extWs, request);
|
||||
});
|
||||
} else {
|
||||
// 默认路径 (SSH, SFTP, Docker etc.) - 按原逻辑处理
|
||||
console.log(`WebSocket: Handling standard upgrade for user ${request.session.username}`);
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
const extWs = ws as AuthenticatedWebSocket;
|
||||
extWs.userId = request.session.userId;
|
||||
extWs.username = request.session.username;
|
||||
(request as any).clientIpAddress = ipAddress;
|
||||
(request as any).isRdpProxy = false; // 标记为非 RDP 代理连接
|
||||
wss.emit('connection', extWs, request);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- WebSocket 连接处理 ---
|
||||
wss.on('connection', (ws: AuthenticatedWebSocket, request: Request) => {
|
||||
ws.isAlive = true;
|
||||
console.log(`WebSocket:客户端 ${ws.username} (ID: ${ws.userId}) 已连接。`);
|
||||
const isRdpProxy = (request as any).isRdpProxy;
|
||||
const clientIp = (request as any).clientIpAddress || 'unknown';
|
||||
|
||||
console.log(`WebSocket:客户端 ${ws.username} (ID: ${ws.userId}, IP: ${clientIp}, RDP Proxy: ${isRdpProxy}) 已连接。`);
|
||||
|
||||
ws.on('pong', () => { ws.isAlive = true; });
|
||||
|
||||
// --- 消息处理 ---
|
||||
ws.on('message', async (message) => {
|
||||
// console.log(`WebSocket:收到来自 ${ws.username} (会话: ${ws.sessionId}) 的消息: ${message.toString().substring(0, 100)}...`);
|
||||
let parsedMessage: any;
|
||||
try {
|
||||
parsedMessage = JSON.parse(message.toString());
|
||||
} catch (e) {
|
||||
console.error(`WebSocket:来自 ${ws.username} 的无效 JSON 消息:`, message.toString());
|
||||
ws.send(JSON.stringify({ type: 'error', payload: '无效的消息格式 (非 JSON)' }));
|
||||
// --- RDP 代理连接处理 ---
|
||||
if (isRdpProxy) {
|
||||
const rdpToken = (request as any).rdpToken;
|
||||
if (!rdpToken) {
|
||||
console.error(`WebSocket: RDP Proxy connection for ${ws.username} missing token.`);
|
||||
ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Missing RDP connection token.' }));
|
||||
ws.close(1008, 'Missing RDP token');
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, payload, requestId } = parsedMessage; // requestId 用于 SFTP 操作
|
||||
const sessionId = ws.sessionId; // 获取当前 WebSocket 的会话 ID
|
||||
const state = sessionId ? clientStates.get(sessionId) : undefined; // 获取当前会话状态
|
||||
// Determine RDP target URL based on deployment mode
|
||||
const deploymentMode = process.env.DEPLOYMENT_MODE || 'docker'; // Default to docker mode
|
||||
let rdpBaseUrl: string;
|
||||
if (deploymentMode === 'local') {
|
||||
rdpBaseUrl = process.env.RDP_SERVICE_URL_LOCAL || 'ws://localhost:18114'; // Default for local
|
||||
console.log(`[WebSocket RDP Proxy] Using LOCAL deployment mode. RDP Target Base: ${rdpBaseUrl}`);
|
||||
} else {
|
||||
rdpBaseUrl = process.env.RDP_SERVICE_URL_DOCKER || 'ws://rdp:8081'; // Default for docker
|
||||
console.log(`[WebSocket RDP Proxy] Using DOCKER deployment mode. RDP Target Base: ${rdpBaseUrl}`);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
// --- SSH 连接请求 ---
|
||||
case 'ssh:connect': {
|
||||
if (sessionId && state) {
|
||||
// Ensure base URL doesn't end with a slash before appending query params
|
||||
const cleanRdpBaseUrl = rdpBaseUrl.endsWith('/') ? rdpBaseUrl.slice(0, -1) : rdpBaseUrl;
|
||||
const rdpTargetUrl = `${cleanRdpBaseUrl}/?token=${rdpToken}`; // Append token query param
|
||||
|
||||
console.log(`WebSocket: RDP Proxy for ${ws.username} attempting to connect to ${rdpTargetUrl}`);
|
||||
|
||||
const rdpWs = new WebSocket(rdpTargetUrl);
|
||||
let clientWsClosed = false;
|
||||
let rdpWsClosed = false;
|
||||
|
||||
// --- 消息转发: Client -> RDP ---
|
||||
ws.on('message', (message: RawData) => {
|
||||
if (rdpWs.readyState === WebSocket.OPEN) {
|
||||
// console.log(`RDP Proxy (C->S): Forwarding message from ${ws.username}`);
|
||||
rdpWs.send(message);
|
||||
} else {
|
||||
console.warn(`RDP Proxy (C->S): RDP WS not open, dropping message from ${ws.username}`);
|
||||
}
|
||||
});
|
||||
|
||||
// --- 消息转发: RDP -> Client ---
|
||||
rdpWs.on('message', (message: RawData) => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
// 将 RawData (可能是 Buffer) 转换为 UTF-8 字符串再发送
|
||||
const messageString = message.toString('utf-8');
|
||||
// console.log(`RDP Proxy (S->C): Forwarding message to ${ws.username}: ${messageString.substring(0, 50)}...`);
|
||||
ws.send(messageString);
|
||||
} else {
|
||||
console.warn(`RDP Proxy (S->C): Client WS not open, dropping message for ${ws.username}`);
|
||||
}
|
||||
});
|
||||
|
||||
// --- 错误处理 ---
|
||||
ws.on('error', (error) => {
|
||||
console.error(`WebSocket: RDP Proxy Client WS Error for ${ws.username}:`, error);
|
||||
if (!rdpWsClosed && rdpWs.readyState !== WebSocket.CLOSED && rdpWs.readyState !== WebSocket.CLOSING) {
|
||||
console.log(`WebSocket: RDP Proxy closing RDP WS due to client WS error.`);
|
||||
rdpWs.close(1011, 'Client WS Error');
|
||||
rdpWsClosed = true;
|
||||
}
|
||||
clientWsClosed = true;
|
||||
});
|
||||
rdpWs.on('error', (error) => {
|
||||
console.error(`WebSocket: RDP Proxy RDP WS Error for ${ws.username}:`, error);
|
||||
if (!clientWsClosed && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
|
||||
console.log(`WebSocket: RDP Proxy closing Client WS due to RDP WS error.`);
|
||||
ws.close(1011, `RDP WS Error: ${error.message}`);
|
||||
clientWsClosed = true;
|
||||
}
|
||||
rdpWsClosed = true;
|
||||
});
|
||||
|
||||
// --- 关闭处理 ---
|
||||
ws.on('close', (code, reason) => {
|
||||
clientWsClosed = true;
|
||||
console.log(`WebSocket: RDP Proxy Client WS Closed for ${ws.username}. Code: ${code}, Reason: ${reason.toString()}`);
|
||||
if (!rdpWsClosed && rdpWs.readyState !== WebSocket.CLOSED && rdpWs.readyState !== WebSocket.CLOSING) {
|
||||
console.log(`WebSocket: RDP Proxy closing RDP WS due to client WS close.`);
|
||||
rdpWs.close(1000, 'Client WS Closed');
|
||||
rdpWsClosed = true;
|
||||
}
|
||||
});
|
||||
rdpWs.on('close', (code, reason) => {
|
||||
rdpWsClosed = true;
|
||||
console.log(`WebSocket: RDP Proxy RDP WS Closed for ${ws.username}. Code: ${code}, Reason: ${reason.toString()}`);
|
||||
if (!clientWsClosed && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
|
||||
console.log(`WebSocket: RDP Proxy closing Client WS due to RDP WS close.`);
|
||||
ws.close(1000, 'RDP WS Closed');
|
||||
clientWsClosed = true;
|
||||
}
|
||||
});
|
||||
|
||||
rdpWs.on('open', () => {
|
||||
console.log(`WebSocket: RDP Proxy connection to ${rdpTargetUrl} established for ${ws.username}. Forwarding messages.`);
|
||||
// Do not send custom message, let Guacamole protocol flow directly
|
||||
});
|
||||
|
||||
// --- 标准 (SSH/SFTP/Docker) 连接处理 ---
|
||||
} else {
|
||||
// --- 消息处理 (原有逻辑) ---
|
||||
ws.on('message', async (message) => {
|
||||
// console.log(`WebSocket:收到来自 ${ws.username} (会话: ${ws.sessionId}) 的消息: ${message.toString().substring(0, 100)}...`);
|
||||
let parsedMessage: any;
|
||||
try {
|
||||
parsedMessage = JSON.parse(message.toString());
|
||||
} catch (e) {
|
||||
console.error(`WebSocket:来自 ${ws.username} 的无效 JSON 消息:`, message.toString());
|
||||
ws.send(JSON.stringify({ type: 'error', payload: '无效的消息格式 (非 JSON)' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, payload, requestId } = parsedMessage; // requestId 用于 SFTP 操作
|
||||
const sessionId = ws.sessionId; // 获取当前 WebSocket 的会话 ID
|
||||
const state = sessionId ? clientStates.get(sessionId) : undefined; // 获取当前会话状态
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
// --- SSH 连接请求 ---
|
||||
case 'ssh:connect': {
|
||||
if (sessionId && state) {
|
||||
console.warn(`WebSocket: 用户 ${ws.username} (会话: ${sessionId}) 已有活动连接,忽略新的连接请求。`);
|
||||
ws.send(JSON.stringify({ type: 'ssh:error', payload: '已存在活动的 SSH 连接。' }));
|
||||
return;
|
||||
@@ -1016,6 +1148,7 @@ export const initializeWebSocket = async (server: http.Server, sessionParser: Re
|
||||
console.error(`WebSocket:客户端 ${ws.username} (会话: ${ws.sessionId}) 发生错误:`, error);
|
||||
cleanupClientConnection(ws.sessionId);
|
||||
});
|
||||
} // End of else block for non-RDP connections
|
||||
});
|
||||
|
||||
// --- WebSocket 服务器关闭处理 ---
|
||||
|
||||
@@ -38,13 +38,11 @@ const desiredModalHeight = ref(858); // User sets the desired TOTAL modal height
|
||||
const MIN_MODAL_WIDTH = 1024;
|
||||
const MIN_MODAL_HEIGHT = 768;
|
||||
|
||||
const RDP_BACKEND_API_BASE = 'http://localhost:9090'; // This might need adjustment too if API is accessed directly from browser, but currently it's proxied via backend
|
||||
|
||||
// Dynamically construct WebSocket URL
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsHost = window.location.hostname;
|
||||
const wsPort = import.meta.env.VITE_RDP_WEBSOCKET_PORT || '18114'; // Read from env var or use default
|
||||
const RDP_BACKEND_WEBSOCKET_URL = `${wsProtocol}//${wsHost}:${wsPort}`;
|
||||
const backendPort = '3001'; // Backend WebSocket port
|
||||
const BACKEND_WEBSOCKET_URL = `${wsProtocol}//${wsHost}:${backendPort}`; // URL for backend proxy
|
||||
// Removed localStorage keys
|
||||
|
||||
const connectRdp = async () => { // Removed useInputValues parameter
|
||||
@@ -93,7 +91,9 @@ const connectRdp = async () => { // Removed useInputValues parameter
|
||||
// Consider setting an error state or notifying the user
|
||||
}
|
||||
|
||||
const tunnelUrl = `${RDP_BACKEND_WEBSOCKET_URL}/?token=${encodeURIComponent(token)}&width=${widthToSend}&height=${heightToSend}&dpi=${dpiToSend}`;
|
||||
// Construct URL for the backend proxy endpoint
|
||||
const tunnelUrl = `${BACKEND_WEBSOCKET_URL}/rdp-proxy?token=${encodeURIComponent(token)}&width=${widthToSend}&height=${heightToSend}&dpi=${dpiToSend}`;
|
||||
console.log(`[RDP Modal] Connecting to tunnel: ${tunnelUrl}`); // Log the final URL
|
||||
// @ts-ignore
|
||||
const tunnel = new Guacamole.WebSocketTunnel(tunnelUrl);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user