diff --git a/src/process/server.js b/src/process/server.js index f9cef82..f99aed1 100644 --- a/src/process/server.js +++ b/src/process/server.js @@ -14,14 +14,28 @@ app.get("/", (_, res) => { }); const sshConnections = new Map(); +const MAX_CONNECTIONS = 100; // Limit concurrent connections + +// Precompile regex for better performance +const ansiRegex = /\x1b\[[0-9;?]*[a-zA-Z]/g; // Function to clean ANSI escape sequences function cleanAnsiSequences(text) { // Remove ANSI escape sequences (like [?2004h, [?2004l, colors, etc.) - return text.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, ''); + // Reset lastIndex for reusable global regex + ansiRegex.lastIndex = 0; + return text.replace(ansiRegex, ''); } wss.on("connection", (ws) => { + // Enforce connection limit + if (sshConnections.size >= MAX_CONNECTIONS) { + console.warn(`Connection limit reached (${sshConnections.size}), rejecting new connection`); + ws.send('Server at capacity. Please try again later.\n'); + ws.close(); + return; + } + console.log("New client connected"); ws.on("message", (message) => { @@ -104,14 +118,19 @@ function connectSSH(ws, hostname, password) { stream.on('data', (data) => { const output = data.toString(); const cleanedOutput = cleanAnsiSequences(output); - console.log('SSH output:', cleanedOutput); + // Reduce console.log overhead in production + if (process.env.NODE_ENV !== 'production') { + console.log('SSH output:', cleanedOutput); + } ws.send(cleanedOutput); }); stream.stderr.on('data', (data) => { const error = data.toString(); const cleanedError = cleanAnsiSequences(error); - console.error('SSH stderr:', cleanedError); + if (process.env.NODE_ENV !== 'production') { + console.error('SSH stderr:', cleanedError); + } ws.send(`ERROR: ${cleanedError}`); }); }); diff --git a/src/renderer/react-wrapper/src/App.tsx b/src/renderer/react-wrapper/src/App.tsx index ef39cf8..6ec2082 100644 --- a/src/renderer/react-wrapper/src/App.tsx +++ b/src/renderer/react-wrapper/src/App.tsx @@ -1,8 +1,10 @@ -import { useEffect, useState, useRef, useCallback } from "react"; +import { useEffect, useState, useRef, useCallback, useMemo } from "react"; import './App.css'; import { addProcessListener, sendToProcess } from "./nexus-bridge"; const WS_URL = "ws://localhost:3230"; +const MAX_MESSAGE_HISTORY = 1000; +const TRIM_TO_SIZE = 900; function App() { const [messages, setMessages] = useState([]); @@ -113,18 +115,19 @@ function App() { websocket.onmessage = (event) => { const message = event.data; console.log('Received:', message); - setMessages((prev) => [...prev, message]); - // Check if connection was successful - if (message.includes('Connected to') || message.includes('SSH Client ready')) { + // Use callback form for better performance + setMessages((prev) => { + // Limit message history to prevent memory issues + const newMessages = prev.length > MAX_MESSAGE_HISTORY ? prev.slice(-TRIM_TO_SIZE) : prev; + return [...newMessages, message]; + }); + + // Check if connection was successful (optimized checks) + if (message.includes('Connected to')) { setIsConnecting(false); setIsConnected(true); - } - - if (message.includes('Connection failed') || - message.includes('ERROR:') || - message.includes('Connection closed') || - message.includes('SSH stream closed')) { + } else if (message.includes('Connection failed') || message.includes('Connection closed')) { setIsConnecting(false); setIsConnected(false); } @@ -146,7 +149,7 @@ function App() { }; }, []); - const handleDisconnect = () => { + const handleDisconnect = useCallback(() => { if (ws && ws.readyState === WebSocket.OPEN) { const disconnectData = { action: 'disconnect' @@ -155,9 +158,9 @@ function App() { } setIsConnected(false); setIsConnecting(false); - }; + }, [ws]); - const handleTerminalInput = (e: React.KeyboardEvent) => { + const handleTerminalInput = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' && isConnected && ws && ws.readyState === WebSocket.OPEN) { const input = (e.target as HTMLInputElement).value; @@ -169,19 +172,19 @@ function App() { ws.send(JSON.stringify(inputData)); (e.target as HTMLInputElement).value = ''; } - }; + }, [isConnected, ws]); - const getButtonText = () => { + const getButtonText = useMemo(() => { if (isConnecting) return 'Connecting...'; if (isConnected) return 'Disconnect'; return 'Connect'; - }; + }, [isConnecting, isConnected]); - const getButtonClass = () => { + const getButtonClass = useMemo(() => { if (isConnecting) return 'connect'; if (isConnected) return 'disconnect'; return 'connect'; - }; + }, [isConnecting, isConnected]); return ( <> @@ -215,10 +218,10 @@ function App() {
diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 12a45c5..bb1fcce 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -56,8 +56,9 @@ const observer = new MutationObserver(() => { } }); - +// Use more specific observation to reduce overhead observer.observe(document.documentElement, { attributes: true, - attributeFilter: ['style'] + attributeFilter: ['style'], + attributeOldValue: false // Don't track old values to save memory });