diff --git a/src/server/InputHandler.ts b/src/server/InputHandler.ts index 77ffe46..1c6fc9c 100644 --- a/src/server/InputHandler.ts +++ b/src/server/InputHandler.ts @@ -14,26 +14,88 @@ export interface InputMessage { } export class InputHandler { + private lastMoveTime = 0; + private lastScrollTime = 0; + private pendingMove: InputMessage | null = null; + private pendingScroll: InputMessage | null = null; + private moveTimer: ReturnType | null = null; + private scrollTimer: ReturnType | null = null; + constructor() { mouse.config.mouseSpeed = 1000; } async handleMessage(msg: InputMessage) { + // Validation: Text length sanitation + if (msg.text && msg.text.length > 500) { + msg.text = msg.text.substring(0, 500); + } + + // Validation: Sane bounds for coordinates + const MAX_COORD = 2000; + if (typeof msg.dx === 'number' && Number.isFinite(msg.dx)) { + msg.dx = Math.max(-MAX_COORD, Math.min(MAX_COORD, msg.dx)); + } + if (typeof msg.dy === 'number' && Number.isFinite(msg.dy)) { + msg.dy = Math.max(-MAX_COORD, Math.min(MAX_COORD, msg.dy)); + } + + // Throttling: Limit high-frequency events to ~60fps (16ms) + if (msg.type === 'move') { + const now = Date.now(); + if (now - this.lastMoveTime < 16) { + this.pendingMove = msg; + if (!this.moveTimer) { + this.moveTimer = setTimeout(() => { + this.moveTimer = null; + if (this.pendingMove) { + const pending = this.pendingMove; + this.pendingMove = null; + this.handleMessage(pending); + } + }, 16); + } + return; + } + this.lastMoveTime = now; + } else if (msg.type === 'scroll') { + const now = Date.now(); + if (now - this.lastScrollTime < 16) { + this.pendingScroll = msg; + if (!this.scrollTimer) { + this.scrollTimer = setTimeout(() => { + this.scrollTimer = null; + if (this.pendingScroll) { + const pending = this.pendingScroll; + this.pendingScroll = null; + this.handleMessage(pending); + } + }, 16); + } + return; + } + this.lastScrollTime = now; + } + switch (msg.type) { case 'move': if (msg.dx !== undefined && msg.dy !== undefined) { const currentPos = await mouse.getPosition(); - - await mouse.setPosition(new Point( - currentPos.x + msg.dx, - currentPos.y + msg.dy - )); + await mouse.setPosition( + new Point(currentPos.x + msg.dx, currentPos.y + msg.dy) + ); } break; case 'click': if (msg.button) { - const btn = msg.button === 'left' ? Button.LEFT : msg.button === 'right' ? Button.RIGHT : Button.MIDDLE; + const btn = + msg.button === 'left' + ? Button.LEFT + : msg.button === 'right' + ? Button.RIGHT + : Button.MIDDLE; + if (msg.press) { await mouse.pressButton(btn); } else { @@ -70,15 +132,18 @@ export class InputHandler { case 'zoom': if (msg.delta !== undefined && msg.delta !== 0) { - const sensitivityFactor = 0.5; + const sensitivityFactor = 0.5; const MAX_ZOOM_STEP = 5; const scaledDelta = Math.sign(msg.delta) * - Math.min(Math.abs(msg.delta) * sensitivityFactor, MAX_ZOOM_STEP); + Math.min( + Math.abs(msg.delta) * sensitivityFactor, + MAX_ZOOM_STEP + ); const amount = -scaledDelta; - + await keyboard.pressKey(Key.LeftControl); try { await mouse.scrollDown(amount); @@ -92,6 +157,7 @@ export class InputHandler { if (msg.key) { console.log(`Processing key: ${msg.key}`); const nutKey = KEY_MAP[msg.key.toLowerCase()]; + if (nutKey !== undefined) { await keyboard.type(nutKey); } else if (msg.key.length === 1) { @@ -105,9 +171,11 @@ export class InputHandler { case 'combo': if (msg.keys && msg.keys.length > 0) { const nutKeys: (Key | string)[] = []; + for (const k of msg.keys) { const lowerKey = k.toLowerCase(); const nutKey = KEY_MAP[lowerKey]; + if (nutKey !== undefined) { nutKeys.push(nutKey); } else if (lowerKey.length === 1) { @@ -127,7 +195,7 @@ export class InputHandler { try { for (const k of nutKeys) { - if (typeof k === "string") { + if (typeof k === 'string') { await keyboard.type(k); } else { await keyboard.pressKey(k); @@ -135,7 +203,9 @@ export class InputHandler { } } - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise(resolve => + setTimeout(resolve, 10) + ); } finally { for (const k of pressedKeys.reverse()) { await keyboard.releaseKey(k);