class RDPWebGateway { constructor() { this.ws = null; this.canvas = null; this.ctx = null; this.currentUser = null; this.currentTarget = null; this.credentials = null; this.init(); } init() { this.setupEventListeners(); } setupEventListeners() { // Login form const loginForm = document.getElementById('loginForm'); if (loginForm) { loginForm.addEventListener('submit', (e) => this.handleLogin(e)); } // Logout button const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', () => this.handleLogout()); } // Disconnect button const disconnectBtn = document.getElementById('disconnectBtn'); if (disconnectBtn) { disconnectBtn.addEventListener('click', () => this.handleDisconnect()); } // Fullscreen button const fullscreenBtn = document.getElementById('fullscreenBtn'); if (fullscreenBtn) { fullscreenBtn.addEventListener('click', () => this.toggleFullscreen()); } // Ctrl+Alt+Del button const ctrlAltDelBtn = document.getElementById('ctrlAltDelBtn'); if (ctrlAltDelBtn) { ctrlAltDelBtn.addEventListener('click', () => this.sendCtrlAltDel()); } } async handleLogin(e) { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; const loginBtn = document.getElementById('loginBtn'); const btnText = loginBtn.querySelector('.btn-text'); const spinner = loginBtn.querySelector('.spinner'); const errorMessage = document.getElementById('errorMessage'); // Show loading state loginBtn.disabled = true; btnText.style.display = 'none'; spinner.style.display = 'block'; errorMessage.style.display = 'none'; try { // Check if RdpBroker service is available const statusResponse = await fetch('/api/broker-status'); const statusData = await statusResponse.json(); if (!statusData.available) { this.showError(errorMessage, 'RDP service is currently unavailable. Please contact your administrator.'); return; } // Store credentials and authenticate via WebSocket this.currentUser = username; this.credentials = { username, password }; // Authenticate and get user-specific targets from RdpBroker await this.authenticateAndLoadTargets(); } catch (error) { console.error('Login error:', error); // Show specific error message if available const errorMsg = error.message || 'Connection error. Please check your network and try again.'; this.showError(errorMessage, errorMsg); loginBtn.disabled = false; btnText.style.display = 'block'; spinner.style.display = 'none'; } } authenticateAndLoadTargets() { return new Promise((resolve, reject) => { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/rdp`; // Create WebSocket connection for authentication this.ws = new WebSocket(wsUrl); this.ws.binaryType = 'arraybuffer'; const timeout = setTimeout(() => { if (this.ws) { this.ws.close(); reject(new Error('Authentication timeout')); } }, 10000); // 10 second timeout this.ws.onopen = () => { console.log('WebSocket connected for authentication'); // Send authentication request to RdpBroker this.ws.send(JSON.stringify({ type: 'authenticate', username: this.credentials.username, password: this.credentials.password })); }; this.ws.onmessage = (event) => { try { const message = JSON.parse(event.data); if (message.type === 'targets') { // Received user-specific targets from RdpBroker clearTimeout(timeout); console.log('Received targets from RdpBroker:', message.targets); this.showTargetsView(message.targets); // Reset login button const loginBtn = document.getElementById('loginBtn'); const btnText = loginBtn.querySelector('.btn-text'); const spinner = loginBtn.querySelector('.spinner'); loginBtn.disabled = false; btnText.style.display = 'block'; spinner.style.display = 'none'; resolve(); } else if (message.type === 'error') { clearTimeout(timeout); this.ws.close(); this.ws = null; reject(new Error(message.error || 'Authentication failed')); } } catch (e) { console.error('Error parsing WebSocket message:', e); } }; this.ws.onerror = (error) => { clearTimeout(timeout); console.error('WebSocket error:', error); reject(new Error('WebSocket connection failed')); }; this.ws.onclose = () => { clearTimeout(timeout); if (this.ws) { console.log('WebSocket closed during authentication'); } }; }); } showTargetsView(targets = null, errorMsg = null) { document.getElementById('loginCard').style.display = 'none'; document.getElementById('targetsCard').style.display = 'block'; document.getElementById('rdpViewer').style.display = 'none'; document.getElementById('currentUser').textContent = this.currentUser; if (errorMsg) { const targetsList = document.getElementById('targetsList'); targetsList.innerHTML = `

⚠️ ${this.escapeHtml(errorMsg)}

`; return; } this.displayTargets(targets); } displayTargets(targets) { const targetsList = document.getElementById('targetsList'); targetsList.innerHTML = ''; if (!targets || targets.length === 0) { targetsList.innerHTML = '

No remote desktops available

'; return; } targets.forEach(target => { const targetItem = document.createElement('div'); targetItem.className = 'target-item'; targetItem.innerHTML = `
${this.escapeHtml(target.name)}
${this.escapeHtml(target.description)}
${this.escapeHtml(target.host)}:${target.port}
`; targetItem.addEventListener('click', () => this.connectToTarget(target)); targetsList.appendChild(targetItem); }); } async connectToTarget(target) { this.currentTarget = target; this.showRDPViewer(); this.initializeRDPConnection(target); } initializeRDPConnection(target) { // WebSocket already connected from authentication if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { console.error('WebSocket not connected'); this.showConnectionError('Connection lost. Please login again.'); return; } this.canvas = document.getElementById('rdpCanvas'); this.ctx = this.canvas.getContext('2d'); // Update message handler for RDP session this.ws.onmessage = (event) => { this.handleWebSocketMessage(event); }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.showConnectionError('Connection error occurred'); }; this.ws.onclose = () => { console.log('WebSocket closed'); this.handleDisconnect(); }; // Send target selection to RdpBroker console.log('Connecting to target:', target.name); this.ws.send(JSON.stringify({ type: 'connect', target: target })); // Setup canvas input handlers this.setupCanvasInputHandlers(); } handleWebSocketMessage(event) { try { const message = JSON.parse(event.data); switch (message.type) { case 'connected': document.getElementById('loadingOverlay').style.display = 'none'; document.getElementById('connectionInfo').textContent = `Connected to ${this.currentTarget.name}`; console.log('RDP connection established'); break; case 'screen': // Render screen update (PNG image data) this.renderScreenUpdate(message); break; case 'disconnected': this.showConnectionError('RDP connection closed'); break; case 'error': this.showConnectionError(message.error); break; default: console.warn('Unknown message type:', message.type); } } catch (error) { console.error('Error handling WebSocket message:', error); } } renderScreenUpdate(update) { try { // Decode base64 PNG image const img = new Image(); img.onload = () => { // Draw image to canvas at specified position this.ctx.drawImage(img, update.x, update.y, update.width, update.height); }; img.onerror = (error) => { console.error('Failed to load screen update image:', error); }; img.src = 'data:image/png;base64,' + update.data; } catch (error) { console.error('Error rendering screen update:', error); } } setupCanvasInputHandlers() { const canvas = this.canvas; // Mouse events canvas.addEventListener('mousemove', (e) => { this.sendMouseEvent('move', e); }); canvas.addEventListener('mousedown', (e) => { this.sendMouseEvent('down', e); }); canvas.addEventListener('mouseup', (e) => { this.sendMouseEvent('up', e); }); canvas.addEventListener('wheel', (e) => { e.preventDefault(); this.sendMouseEvent('wheel', e); }); // Keyboard events window.addEventListener('keydown', (e) => { if (this.ws && this.ws.readyState === WebSocket.OPEN) { e.preventDefault(); this.sendKeyEvent('down', e); } }); window.addEventListener('keyup', (e) => { if (this.ws && this.ws.readyState === WebSocket.OPEN) { e.preventDefault(); this.sendKeyEvent('up', e); } }); // Prevent context menu canvas.addEventListener('contextmenu', (e) => e.preventDefault()); } sendMouseEvent(type, event) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; const rect = this.canvas.getBoundingClientRect(); const x = Math.floor((event.clientX - rect.left) * (this.canvas.width / rect.width)); const y = Math.floor((event.clientY - rect.top) * (this.canvas.height / rect.height)); // Map button: 0=left, 1=middle, 2=right let button = 0; if (type === 'down' || type === 'up') { button = event.button; } this.ws.send(JSON.stringify({ type: 'mouse', action: type, x: x, y: y, button: button })); } sendKeyEvent(type, event) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; this.ws.send(JSON.stringify({ type: 'keyboard', action: type, code: event.keyCode || event.which })); } keyCode: event.keyCode, ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, })); } sendCtrlAltDel() { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; this.ws.send(JSON.stringify({ type: 'special', action: 'ctrl-alt-del', })); } toggleFullscreen() { const viewer = document.getElementById('rdpViewer'); if (!document.fullscreenElement) { viewer.requestFullscreen().catch(err => { console.error('Error attempting to enable fullscreen:', err); }); } else { document.exitFullscreen(); } } handleDisconnect() { if (this.ws) { this.ws.close(); this.ws = null; } this.currentTarget = null; this.showTargetsView(); } handleLogout() { if (this.ws) { this.ws.close(); this.ws = null; } this.currentUser = null; this.currentTarget = null; this.credentials = null; this.showLoginView(); } showLoginView() { document.getElementById('loginCard').style.display = 'block'; document.getElementById('targetsCard').style.display = 'none'; document.getElementById('rdpViewer').style.display = 'none'; document.getElementById('username').value = ''; document.getElementById('password').value = ''; } showRDPViewer() { document.getElementById('loginCard').style.display = 'none'; document.getElementById('targetsCard').style.display = 'none'; document.getElementById('rdpViewer').style.display = 'block'; document.getElementById('loadingOverlay').style.display = 'flex'; } showError(element, message) { element.textContent = message; element.style.display = 'block'; } showConnectionError(message) { const overlay = document.getElementById('loadingOverlay'); overlay.innerHTML = `

Connection Failed

${this.escapeHtml(message)}

`; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } // Initialize the app const rdpGateway = new RDPWebGateway(); // v1.4