const logger = require('../utils/logger'); const MessageTypes = require('./messageTypes'); const { clipboardService } = require('../services/clipboard'); class InputHandler { constructor(inputService) { this.inputService = inputService; this.clipboardService = clipboardService; this.eventQueue = []; this.isProcessingQueue = false; this.lastMouseMove = null; this.mouseMovePending = false; } handleMessage(message, ws) { const { type, ...data } = message; switch (type) { case MessageTypes.MOUSE_MOVE: this.queueMouseMove(data.x, data.y); break; case MessageTypes.MOUSE_DOWN: this.queueEvent('mouseDown', data.button || 'left'); break; case MessageTypes.MOUSE_UP: this.queueEvent('mouseUp', data.button || 'left'); break; case MessageTypes.MOUSE_WHEEL: this.queueEvent('mouseWheel', data.delta); break; case MessageTypes.KEY_DOWN: this.queueEvent('keyDown', data.key); break; case MessageTypes.KEY_UP: this.queueEvent('keyUp', data.key); break; case MessageTypes.CLIPBOARD_GET: this.handleClipboardGet(ws); break; case MessageTypes.CLIPBOARD_SET: this.handleClipboardSet(ws, data); break; default: logger.debug('Unknown message type', { type }); } } async handleClipboardGet(ws) { try { const content = await this.clipboardService.read(); if (this.clipboardService.isSmallContent(content.size)) { ws.send(JSON.stringify({ type: MessageTypes.CLIPBOARD_DATA, contentType: content.type, data: content.data, size: content.size })); } else { ws.send(JSON.stringify({ type: MessageTypes.CLIPBOARD_TOO_LARGE, size: content.size })); } } catch (error) { logger.error('Failed to get clipboard', { error: error.message }); ws.send(JSON.stringify({ type: MessageTypes.CLIPBOARD_RESULT, success: false })); } } async handleClipboardSet(ws, data) { try { const success = await this.clipboardService.set({ type: data.contentType, data: data.data }); ws.send(JSON.stringify({ type: MessageTypes.CLIPBOARD_RESULT, success })); } catch (error) { logger.error('Failed to set clipboard', { error: error.message }); ws.send(JSON.stringify({ type: MessageTypes.CLIPBOARD_RESULT, success: false })); } } queueMouseMove(x, y) { this.lastMouseMove = { x, y }; if (!this.mouseMovePending) { this.mouseMovePending = true; setImmediate(() => { if (this.lastMouseMove) { this.eventQueue.push({ type: 'mouseMove', x: this.lastMouseMove.x, y: this.lastMouseMove.y }); this.mouseMovePending = false; this.processQueue(); } }); } } queueEvent(type, data) { this.eventQueue.push({ type, data }); this.processQueue(); } async processQueue() { if (this.isProcessingQueue || this.eventQueue.length === 0) { return; } this.isProcessingQueue = true; while (this.eventQueue.length > 0) { const event = this.eventQueue.shift(); await this.executeEvent(event); } this.isProcessingQueue = false; } async executeEvent(event) { try { switch (event.type) { case 'mouseMove': await this.inputService.mouseMove(event.x, event.y); break; case 'mouseDown': await this.inputService.mouseDown(event.data); break; case 'mouseUp': await this.inputService.mouseUp(event.data); break; case 'mouseWheel': await this.inputService.mouseWheel(event.data); break; case 'keyDown': await this.inputService.keyDown(event.data); break; case 'keyUp': await this.inputService.keyUp(event.data); break; } } catch (error) { logger.error('Failed to execute event', { type: event.type, error: error.message }); } } stop() { logger.info('Stopping InputHandler'); this.eventQueue = []; this.isProcessingQueue = false; this.lastMouseMove = null; this.mouseMovePending = false; } } module.exports = InputHandler;