From cb1ee20bc5d98fbaec912395877508cee69f233c Mon Sep 17 00:00:00 2001 From: th-shivam Date: Tue, 10 Feb 2026 22:38:50 +0530 Subject: [PATCH 1/4] fix: implement real-time network IP auto-update via server polling --- src/routes/settings.tsx | 22 ++++++++++++---------- src/server/websocket.ts | 30 ++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx index dde409f..0f454ec 100644 --- a/src/routes/settings.tsx +++ b/src/routes/settings.tsx @@ -46,33 +46,35 @@ function SettingsPage() { if (typeof window === 'undefined') return; if (window.location.hostname !== 'localhost') return; - console.log('Attempting to auto-detect IP...'); + console.log('Starting IP auto-detection stream...'); const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; const socket = new WebSocket(wsUrl); socket.onopen = () => { - console.log('Connected to local server for IP detection'); + console.log('Connected to local server for live IP tracking'); socket.send(JSON.stringify({ type: 'get-ip' })); }; socket.onmessage = (event) => { try { const data = JSON.parse(event.data); - if (data.type === 'server-ip' && data.ip) { - console.log('Auto-detected IP:', data.ip); - setIp(data.ip); - socket.close(); + // Handle both initial response and subsequent broadcasts + if ((data.type === 'server-ip' || data.type === 'connected') && (data.ip || data.serverIp)) { + const newIp = data.ip || data.serverIp; + console.log('Received IP Update:', newIp); + setIp(newIp); } } catch (e) { - console.error(e); + console.error('WS Message Error:', e); } }; return () => { + console.log('Closing IP detection socket'); if (socket.readyState === WebSocket.OPEN) socket.close(); } - }, []); // Run once + }, []); // Run once on mount, stays open for updates // Helper for display URL const displayUrl = typeof window !== 'undefined' @@ -100,12 +102,12 @@ function SettingsPage() { - {/* SENSITIVITY SLIDER SECTION */} + {/* SENSITIVITY SLIDER SECTION */}
diff --git a/src/server/websocket.ts b/src/server/websocket.ts index f06ad7a..f84dfdb 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -21,10 +21,30 @@ function getLocalIp() { export function createWsServer(server: Server) { const wss = new WebSocketServer({ noServer: true }); const inputHandler = new InputHandler(); - const LAN_IP = getLocalIp(); + + let currentIp = getLocalIp(); console.log(`WebSocket Server initialized (Upgrade mode)`); - console.log(`WS LAN IP: ${LAN_IP}`); + console.log(`Initial WS LAN IP: ${currentIp}`); + + // Frequency for network interface polling (ms) + const POLLING_INTERVAL = 5000; + + setInterval(() => { + const newIp = getLocalIp(); + if (newIp !== currentIp) { + console.log(`Network Change Detected! IP: ${currentIp} -> ${newIp}`); + currentIp = newIp; + + // Broadcast the new IP to all connected clients + const updateMsg = JSON.stringify({ type: 'server-ip', ip: currentIp }); + wss.clients.forEach((client) => { + if (client.readyState === WebSocket.OPEN) { + client.send(updateMsg); + } + }); + } + }, POLLING_INTERVAL); server.on('upgrade', (request: IncomingMessage, socket: Socket, head: Buffer) => { const pathname = request.url; @@ -39,7 +59,8 @@ export function createWsServer(server: Server) { wss.on('connection', (ws: WebSocket) => { console.log('Client connected to /ws'); - ws.send(JSON.stringify({ type: 'connected', serverIp: LAN_IP })); + // Send current IP immediately on connection + ws.send(JSON.stringify({ type: 'connected', serverIp: currentIp })); ws.on('message', async (data: string) => { try { @@ -47,9 +68,10 @@ export function createWsServer(server: Server) { const msg = JSON.parse(raw); if (msg.type === 'get-ip') { - ws.send(JSON.stringify({ type: 'server-ip', ip: LAN_IP })); + ws.send(JSON.stringify({ type: 'server-ip', ip: currentIp })); return; } + // ... existing update-config logic ... if (msg.type === 'update-config') { console.log('Updating config:', msg.config); From ff4cae89be1155097ba667bae8d8c25b3ccc07f4 Mon Sep 17 00:00:00 2001 From: th-shivam Date: Tue, 10 Feb 2026 22:56:20 +0530 Subject: [PATCH 2/4] docs: add jsdoc comments and refine socket cleanup & resource management --- src/routes/settings.tsx | 12 ++++++++++-- src/server/InputHandler.ts | 12 +++++++++--- src/server/websocket.ts | 27 ++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx index 0f454ec..a4cbfe0 100644 --- a/src/routes/settings.tsx +++ b/src/routes/settings.tsx @@ -7,6 +7,12 @@ export const Route = createFileRoute('/settings')({ component: SettingsPage, }) +/** + * SettingsPage component for configuring server IP, ports, and mouse sensitivity. + * Also handles real-time IP detection and QR code generation. + * + * @returns {JSX.Element} The rendered settings interface. + */ function SettingsPage() { const [ip, setIp] = useState(''); const [frontendPort, setFrontendPort] = useState(String(CONFIG.FRONTEND_PORT)); @@ -71,8 +77,10 @@ function SettingsPage() { }; return () => { - console.log('Closing IP detection socket'); - if (socket.readyState === WebSocket.OPEN) socket.close(); + if (socket.readyState !== WebSocket.CLOSING && socket.readyState !== WebSocket.CLOSED) { + console.log('Closing IP detection socket'); + socket.close(); + } } }, []); // Run once on mount, stays open for updates diff --git a/src/server/InputHandler.ts b/src/server/InputHandler.ts index 012222c..69c027a 100644 --- a/src/server/InputHandler.ts +++ b/src/server/InputHandler.ts @@ -18,6 +18,12 @@ export class InputHandler { mouse.config.mouseSpeed = 1000; } + /** + * Processes incoming input messages and performs corresponding system actions. + * Supports mouse movement, clicking, scrolling, zooming, keyboard input, and workspace switching. + * + * @param {InputMessage} msg - The input message containing the action type and parameters. + */ async handleMessage(msg: InputMessage) { switch (msg.type) { case 'move': @@ -25,9 +31,9 @@ export class InputHandler { const currentPos = await mouse.getPosition(); // Apply sensitivity multiplier const sensitivity = CONFIG.MOUSE_SENSITIVITY ?? 1.0; - + await mouse.setPosition(new Point( - currentPos.x + (msg.dx * sensitivity), + currentPos.x + (msg.dx * sensitivity), currentPos.y + (msg.dy * sensitivity) )); } @@ -57,7 +63,7 @@ export class InputHandler { const MAX_ZOOM_STEP = 5; const scaledDelta = Math.sign(msg.delta) * Math.min(Math.abs(msg.delta) * sensitivityFactor, MAX_ZOOM_STEP); const amount = -scaledDelta * invertMultiplier; - + await keyboard.pressKey(Key.LeftControl); try { await mouse.scrollDown(amount); diff --git a/src/server/websocket.ts b/src/server/websocket.ts index f84dfdb..2ae590f 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -5,7 +5,12 @@ import fs from 'fs'; import { Server, IncomingMessage } from 'http'; import { Socket } from 'net'; -// Helper to find LAN IP +/** + * Retrieves the local IPv4 address of the host machine. + * Filters for non-internal (non-loopback) IPv4 interfaces. + * + * @returns {string} The local IPv4 address or 'localhost' if no interface is found. + */ function getLocalIp() { const nets = os.networkInterfaces(); for (const name of Object.keys(nets)) { @@ -18,6 +23,12 @@ function getLocalIp() { return 'localhost'; } +/** + * Initializes the WebSocket server and sets up network interface polling. + * Handles WebSocket upgrades, client connections, and real-time IP broadcasts. + * + * @param {Server} server - The HTTP server instance to attach the WebSocket server to. + */ export function createWsServer(server: Server) { const wss = new WebSocketServer({ noServer: true }); const inputHandler = new InputHandler(); @@ -30,7 +41,7 @@ export function createWsServer(server: Server) { // Frequency for network interface polling (ms) const POLLING_INTERVAL = 5000; - setInterval(() => { + const pollingIntervalId = setInterval(() => { const newIp = getLocalIp(); if (newIp !== currentIp) { console.log(`Network Change Detected! IP: ${currentIp} -> ${newIp}`); @@ -46,6 +57,16 @@ export function createWsServer(server: Server) { } }, POLLING_INTERVAL); + // Cleanup interval when the WebSocket server closes + wss.on('close', () => { + console.log('Clearing network polling interval'); + clearInterval(pollingIntervalId); + }); + + // Also handle process exit to ensure cleanup + process.on('SIGTERM', () => clearInterval(pollingIntervalId)); + process.on('SIGINT', () => clearInterval(pollingIntervalId)); + server.on('upgrade', (request: IncomingMessage, socket: Socket, head: Buffer) => { const pathname = request.url; @@ -60,7 +81,7 @@ export function createWsServer(server: Server) { console.log('Client connected to /ws'); // Send current IP immediately on connection - ws.send(JSON.stringify({ type: 'connected', serverIp: currentIp })); + ws.send(JSON.stringify({ type: 'connected', ip: currentIp })); ws.on('message', async (data: string) => { try { From e71756d3dc57674956c6f8060e3aa6f7e3a18e38 Mon Sep 17 00:00:00 2001 From: th-shivam Date: Tue, 17 Feb 2026 18:55:33 +0530 Subject: [PATCH 3/4] feat: implement configurable network polling interval --- src/config.tsx | 4 +++- src/routes/settings.tsx | 25 +++++++++++++++++++++++++ src/server-config.json | 5 ++++- src/server/websocket.ts | 15 ++++++++++++++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/config.tsx b/src/config.tsx index 9aac39b..f095029 100644 --- a/src/config.tsx +++ b/src/config.tsx @@ -15,5 +15,7 @@ export const THEMES = { export const CONFIG = { // Port for the Vite Frontend - FRONTEND_PORT: serverConfig.frontendPort + FRONTEND_PORT: serverConfig.frontendPort, + MOUSE_SENSITIVITY: serverConfig.mouseSensitivity ?? 1.0, + NETWORK_POLLING_INTERVAL: serverConfig.networkPollingInterval ?? 5000 }; diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx index 87d4720..48fe8c3 100644 --- a/src/routes/settings.tsx +++ b/src/routes/settings.tsx @@ -16,6 +16,7 @@ export const Route = createFileRoute('/settings')({ function SettingsPage() { const [ip, setIp] = useState(''); const [frontendPort, setFrontendPort] = useState(String(CONFIG.FRONTEND_PORT)); + const [pingDelay, setPingDelay] = useState(CONFIG.NETWORK_POLLING_INTERVAL || 5000); // Client Side Settings (LocalStorage) const [invertScroll, setInvertScroll] = useState(() => { @@ -191,6 +192,27 @@ function SettingsPage() { />
+
+ + setPingDelay(parseInt(e.target.value))} + /> +
+ 1s (Fast) + 5s (Default) + 30s (Slow) +
+
+
Warning @@ -213,6 +235,9 @@ function SettingsPage() { type: 'update-config', config: { frontendPort: parseInt(frontendPort), + networkPollingInterval: pingDelay, + mouseInvert: invertScroll, + mouseSensitivity: sensitivity, } })); diff --git a/src/server-config.json b/src/server-config.json index ec9ca9e..2c4fc5d 100644 --- a/src/server-config.json +++ b/src/server-config.json @@ -1,5 +1,8 @@ { "host": "0.0.0.0", "frontendPort": 3000, - "address": "rein.local" + "address": "rein.local", + "mouseInvert": true, + "mouseSensitivity": 1.0, + "networkPollingInterval": 5000 } \ No newline at end of file diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 2ae590f..6bdb5af 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -39,7 +39,20 @@ export function createWsServer(server: Server) { console.log(`Initial WS LAN IP: ${currentIp}`); // Frequency for network interface polling (ms) - const POLLING_INTERVAL = 5000; + // Read directly from config file + let pollingInterval = 5000; + try { + const configPath = './src/server-config.json'; + if (fs.existsSync(configPath)) { + const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + if (config.networkPollingInterval) { + pollingInterval = config.networkPollingInterval; + } + } + } catch (e) { + console.error('Failed to read initial polling interval:', e); + } + const POLLING_INTERVAL = pollingInterval; const pollingIntervalId = setInterval(() => { const newIp = getLocalIp(); From 0a72b19ee46f50c9cb6e62bc5ad14add814f9dfd Mon Sep 17 00:00:00 2001 From: th-shivam Date: Tue, 17 Feb 2026 19:19:19 +0530 Subject: [PATCH 4/4] refactor: improve robustness and safety of websocket and settings --- src/routes/settings.tsx | 72 +++++++++++++++++++++++++++------- src/server/websocket.ts | 87 ++++++++++++++++++++++++++++++----------- 2 files changed, 122 insertions(+), 37 deletions(-) diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx index a5b9394..13a3183 100644 --- a/src/routes/settings.tsx +++ b/src/routes/settings.tsx @@ -1,5 +1,5 @@ import { createFileRoute } from '@tanstack/react-router' -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef, useCallback } from 'react' import QRCode from 'qrcode'; import { CONFIG, APP_CONFIG, THEMES } from '../config'; @@ -17,7 +17,7 @@ function SettingsPage() { const [ip, setIp] = useState(''); const [frontendPort, setFrontendPort] = useState(String(CONFIG.FRONTEND_PORT)); - const [pingDelay, setPingDelay] = useState(CONFIG.NETWORK_POLLING_INTERVAL || 5000); + const [pingDelay, setPingDelay] = useState(CONFIG.NETWORK_POLLING_INTERVAL ?? 5000); // Client Side Settings (LocalStorage) const [invertScroll, setInvertScroll] = useState(() => { @@ -82,18 +82,30 @@ function SettingsPage() { .catch((e) => console.error('QR Error:', e)); }, [ip]); - // Effect: Auto-detect LAN IP from Server (only if on localhost) - useEffect(() => { + // WebSocket Management for IP Auto-detection + const wsRef = useRef(null); + const reconnectTimeoutRef = useRef(null); + const retryCountRef = useRef(0); + const MAX_RETRIES = 5; + + const connectWebSocket = useCallback(() => { if (typeof window === 'undefined') return; if (window.location.hostname !== 'localhost') return; - console.log('Starting IP auto-detection stream...'); + // Clean up existing socket if any + if (wsRef.current) { + wsRef.current.close(); + } + + console.log(`Starting IP auto-detection stream... (Attempt ${retryCountRef.current + 1})`); const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws`; const socket = new WebSocket(wsUrl); + wsRef.current = socket; socket.onopen = () => { console.log('Connected to local server for live IP tracking'); + retryCountRef.current = 0; // Reset retry count on success socket.send(JSON.stringify({ type: 'get-ip' })); }; @@ -101,23 +113,53 @@ function SettingsPage() { try { const data = JSON.parse(event.data); // Handle both initial response and subsequent broadcasts - if ((data.type === 'server-ip' || data.type === 'connected') && (data.ip || data.serverIp)) { - const newIp = data.ip || data.serverIp; - console.log('Received IP Update:', newIp); - setIp(newIp); + if ((data.type === 'server-ip' || data.type === 'connected') && data.ip) { + console.log('Received IP Update:', data.ip); + setIp(data.ip); } } catch (e) { console.error('WS Message Error:', e); } }; + socket.onclose = () => { + console.log('IP detection socket closed.'); + wsRef.current = null; + + // Reconnect logic with exponential backoff + if (retryCountRef.current < MAX_RETRIES) { + const timeout = Math.min(1000 * Math.pow(2, retryCountRef.current), 10000); + console.log(`Reconnecting in ${timeout}ms...`); + reconnectTimeoutRef.current = setTimeout(() => { + retryCountRef.current++; + connectWebSocket(); + }, timeout); + } else { + console.log('Max WebSocket retries reached. Stopping auto-detection.'); + } + }; + + socket.onerror = (err) => { + console.error('WebSocket Error:', err); + // Verify if closing triggers onclose, usually it does. + socket.close(); + }; + + }, []); + + useEffect(() => { + connectWebSocket(); + return () => { - if (socket.readyState !== WebSocket.CLOSING && socket.readyState !== WebSocket.CLOSED) { - console.log('Closing IP detection socket'); - socket.close(); + if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current); + if (wsRef.current) { + // Prevent auto-reconnection on unmount + wsRef.current.onclose = null; + wsRef.current.close(); } }; - }, []); // Run once on mount, stays open for updates + }, [connectWebSocket]); + const displayUrl = typeof window !== 'undefined' ? `${window.location.protocol}//${ip}:${CONFIG.FRONTEND_PORT}/trackpad` @@ -221,12 +263,13 @@ function SettingsPage() {
-