class RDPWebGateway { constructor() { this.ws = null; this.canvas = null; this.ctx = null; this.currentUser = null; this.currentTarget = null; this.sessionId = 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 { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password }), }); const data = await response.json(); if (response.ok) { this.currentUser = username; this.sessionId = data.sessionId; await this.loadTargets(); this.showTargetsView(); } else { this.showError(errorMessage, data.error || 'Authentication failed'); } } catch (error) { console.error('Login error:', error); this.showError(errorMessage, 'Connection error. Please try again.'); } finally { loginBtn.disabled = false; btnText.style.display = 'block'; spinner.style.display = 'none'; } } async loadTargets() { try { const response = await fetch('/api/targets', { headers: { 'X-Session-ID': this.sessionId, }, }); if (!response.ok) { throw new Error('Failed to load targets'); } const data = await response.json(); this.displayTargets(data.targets); } catch (error) { console.error('Load targets error:', error); const targetsError = document.getElementById('targetsError'); this.showError(targetsError, 'Failed to load available desktops'); } } 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) { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/rdp`; this.canvas = document.getElementById('rdpCanvas'); this.ctx = this.canvas.getContext('2d'); // Connect WebSocket this.ws = new WebSocket(wsUrl); this.ws.binaryType = 'arraybuffer'; this.ws.onopen = () => { console.log('WebSocket connected'); // Send connection request this.ws.send(JSON.stringify({ type: 'connect', sessionId: this.sessionId, target: target, })); }; 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(); }; // Setup canvas input handlers this.setupCanvasInputHandlers(); } handleWebSocketMessage(event) { if (typeof event.data === 'string') { 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}`; break; case 'error': this.showConnectionError(message.error); break; case 'resize': this.canvas.width = message.width; this.canvas.height = message.height; break; } } else { // Binary data - frame update this.renderFrame(event.data); } } renderFrame(data) { // This is a simplified version // In production, you'd decode the RDP frame data properly const imageData = new Uint8ClampedArray(data); if (imageData.length === this.canvas.width * this.canvas.height * 4) { const imgData = this.ctx.createImageData(this.canvas.width, this.canvas.height); imgData.data.set(imageData); this.ctx.putImageData(imgData, 0, 0); } } 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)); this.ws.send(JSON.stringify({ type: 'mouse', action: type, x: x, y: y, button: event.button, deltaY: event.deltaY || 0, })); } sendKeyEvent(type, event) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; this.ws.send(JSON.stringify({ type: 'keyboard', action: type, key: event.key, code: event.code, 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.sessionId = 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 = ''; } showTargetsView() { document.getElementById('loginCard').style.display = 'none'; document.getElementById('targetsCard').style.display = 'block'; document.getElementById('rdpViewer').style.display = 'none'; document.getElementById('currentUser').textContent = this.currentUser; } 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();