1→/** 2→ * WebSocket Hook 3→ * ============== 4→ * 5→ * Custom React hook for managing WebSocket connections with auto-reconnect. 6→ */ 7→ 8→import { useState, useEffect, useRef, useCallback } from 'react'; 9→ 10→export type WebSocketStatus = 'connecting' | 'connected' | 'disconnected' | 'reconnecting'; 11→ 12→export interface WebSocketMessage { 13→ type: string; 14→ [key: string]: unknown; 15→} 16→ 17→export interface UseWebSocketOptions { 18→ /** Project name to connect to */ 19→ projectName: string | null; 20→ /** Called when a message is received */ 21→ onMessage?: (message: WebSocketMessage) => void; 22→ /** Called when connection status changes */ 23→ onStatusChange?: (status: WebSocketStatus) => void; 24→ /** Enable auto-reconnect (default: true) */ 25→ autoReconnect?: boolean; 26→ /** Initial reconnect delay in ms (default: 1000) */ 27→ reconnectDelay?: number; 28→ /** Maximum reconnect delay in ms (default: 30000) */ 29→ maxReconnectDelay?: number; 30→ /** Reconnect delay multiplier (default: 2) */ 31→ reconnectMultiplier?: number; 32→ /** Maximum number of reconnect attempts (default: 10, 0 = infinite) */ 33→ maxReconnectAttempts?: number; 34→ /** Heartbeat interval in ms (default: 25000) */ 35→ heartbeatInterval?: number; 36→} 37→ 38→export interface UseWebSocketReturn { 39→ /** Current connection status */ 40→ status: WebSocketStatus; 41→ /** Send a message through the WebSocket */ 42→ sendMessage: (message: WebSocketMessage) => void; 43→ /** Manually disconnect */ 44→ disconnect: () => void; 45→ /** Manually reconnect */ 46→ reconnect: () => void; 47→ /** Last received message */ 48→ lastMessage: WebSocketMessage | null; 49→ /** Number of reconnect attempts */ 50→ reconnectAttempts: number; 51→ /** Last error message */ 52→ error: string | null; 53→} 54→ 55→export function useWebSocket(options: UseWebSocketOptions): UseWebSocketReturn { 56→ const { 57→ projectName, 58→ onMessage, 59→ onStatusChange, 60→ autoReconnect = true, 61→ reconnectDelay = 1000, 62→ maxReconnectDelay = 30000, 63→ reconnectMultiplier = 2, 64→ maxReconnectAttempts = 10, 65→ heartbeatInterval = 25000, 66→ } = options; 67→ 68→ const [status, setStatus] = useState('disconnected'); 69→ const [lastMessage, setLastMessage] = useState(null); 70→ const [reconnectAttempts, setReconnectAttempts] = useState(0); 71→ const [error, setError] = useState(null); 72→ 73→ const wsRef = useRef(null); 74→ const reconnectTimeoutRef = useRef | null>(null); 75→ const heartbeatIntervalRef = useRef | null>(null); 76→ const currentDelayRef = useRef(reconnectDelay); 77→ const shouldReconnectRef = useRef(true); 78→ const isManualDisconnectRef = useRef(false); 79→ 80→ // Update status and notify callback 81→ const updateStatus = useCallback((newStatus: WebSocketStatus) => { 82→ setStatus(newStatus); 83→ onStatusChange?.(newStatus); 84→ }, [onStatusChange]); 85→ 86→ // Clear all timers 87→ const clearTimers = useCallback(() => { 88→ if (reconnectTimeoutRef.current) { 89→ clearTimeout(reconnectTimeoutRef.current); 90→ reconnectTimeoutRef.current = null; 91→ } 92→ if (heartbeatIntervalRef.current) { 93→ clearInterval(heartbeatIntervalRef.current); 94→ heartbeatIntervalRef.current = null; 95→ } 96→ }, []); 97→ 98→ // Start heartbeat ping 99→ const startHeartbeat = useCallback(() => { 100→ if (heartbeatIntervalRef.current) { 101→ clearInterval(heartbeatIntervalRef.current); 102→ } 103→ heartbeatIntervalRef.current = setInterval(() => { 104→ if (wsRef.current?.readyState === WebSocket.OPEN) { 105→ wsRef.current.send(JSON.stringify({ type: 'ping' })); 106→ } 107→ }, heartbeatInterval); 108→ }, [heartbeatInterval]); 109→ 110→ // Connect to WebSocket 111→ const connect = useCallback(() => { 112→ if (!projectName) { 113→ return; 114→ } 115→ 116→ // Don't connect if already connected or connecting 117→ if (wsRef.current?.readyState === WebSocket.OPEN || 118→ wsRef.current?.readyState === WebSocket.CONNECTING) { 119→ return; 120→ } 121→ 122→ clearTimers(); 123→ setError(null); 124→ 125→ // Determine WebSocket URL based on current location 126→ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; 127→ const wsUrl = `${protocol}//${window.location.host}/ws/projects/${projectName}`; 128→ 129→ updateStatus('connecting'); 130→ 131→ try { 132→ const ws = new WebSocket(wsUrl); 133→ wsRef.current = ws; 134→ 135→ ws.onopen = () => { 136→ updateStatus('connected'); 137→ setReconnectAttempts(0); 138→ currentDelayRef.current = reconnectDelay; 139→ setError(null); 140→ startHeartbeat(); 141→ }; 142→ 143→ ws.onmessage = (event) => { 144→ try { 145→ const message = JSON.parse(event.data) as WebSocketMessage; 146→ setLastMessage(message); 147→ 148→ // Don't call onMessage for heartbeat/pong - those are internal 149→ if (message.type !== 'heartbeat' && message.type !== 'pong') { 150→ onMessage?.(message); 151→ } 152→ } catch (err) { 153→ console.error('Failed to parse WebSocket message:', err); 154→ } 155→ }; 156→ 157→ ws.onerror = () => { 158→ setError('WebSocket connection error'); 159→ }; 160→ 161→ ws.onclose = (event) => { 162→ clearTimers(); 163→ wsRef.current = null; 164→ 165→ // Don't reconnect if manually disconnected 166→ if (isManualDisconnectRef.current) { 167→ updateStatus('disconnected'); 168→ isManualDisconnectRef.current = false; 169→ return; 170→ } 171→ 172→ // Check if we should attempt to reconnect 173→ if (autoReconnect && shouldReconnectRef.current) { 174→ if (maxReconnectAttempts === 0 || reconnectAttempts < maxReconnectAttempts) { 175→ updateStatus('reconnecting'); 176→ 177→ // Schedule reconnection with exponential backoff 178→ const delay = Math.min(currentDelayRef.current, maxReconnectDelay); 179→ 180→ reconnectTimeoutRef.current = setTimeout(() => { 181→ setReconnectAttempts(prev => prev + 1); 182→ currentDelayRef.current = Math.min( 183→ currentDelayRef.current * reconnectMultiplier, 184→ maxReconnectDelay 185→ ); 186→ connect(); 187→ }, delay); 188→ } else { 189→ setError(`Max reconnect attempts (${maxReconnectAttempts}) reached`); 190→ updateStatus('disconnected'); 191→ } 192→ } else { 193→ updateStatus('disconnected'); 194→ } 195→ 196→ // Include close code in error if abnormal closure 197→ if (event.code !== 1000 && event.code !== 1001) { 198→ setError(`WebSocket closed: ${event.reason || 'Unknown reason'} (code: ${event.code})`); 199→ } 200→ }; 201→ } catch (err) { 202→ setError(`Failed to create WebSocket: ${err}`); 203→ updateStatus('disconnected'); 204→ } 205→ }, [ 206→ projectName, 207→ autoReconnect, 208→ reconnectDelay, 209→ maxReconnectDelay, 210→ reconnectMultiplier, 211→ maxReconnectAttempts, 212→ reconnectAttempts, 213→ onMessage, 214→ updateStatus, 215→ clearTimers, 216→ startHeartbeat 217→ ]); 218→ 219→ // Send message through WebSocket 220→ const sendMessage = useCallback((message: WebSocketMessage) => { 221→ if (wsRef.current?.readyState === WebSocket.OPEN) { 222→ wsRef.current.send(JSON.stringify(message)); 223→ } else { 224→ console.warn('Cannot send message: WebSocket is not connected'); 225→ } 226→ }, []); 227→ 228→ // Manual disconnect 229→ const disconnect = useCallback(() => { 230→ shouldReconnectRef.current = false; 231→ isManualDisconnectRef.current = true; 232→ clearTimers(); 233→ if (wsRef.current) { 234→ wsRef.current.close(1000, 'Client disconnected'); 235→ wsRef.current = null; 236→ } 237→ setReconnectAttempts(0); 238→ currentDelayRef.current = reconnectDelay; 239→ updateStatus('disconnected'); 240→ }, [clearTimers, reconnectDelay, updateStatus]); 241→ 242→ // Manual reconnect 243→ const reconnect = useCallback(() => { 244→ shouldReconnectRef.current = true; 245→ isManualDisconnectRef.current = false; 246→ setReconnectAttempts(0); 247→ currentDelayRef.current = reconnectDelay; 248→ 249→ // Close existing connection if any 250→ if (wsRef.current) { 251→ wsRef.current.close(); 252→ wsRef.current = null; 253→ } 254→ 255→ connect(); 256→ }, [connect, reconnectDelay]); 257→ 258→ // Connect when projectName changes 259→ useEffect(() => { 260→ if (projectName) { 261→ shouldReconnectRef.current = true; 262→ isManualDisconnectRef.current = false; 263→ connect(); 264→ } else { 265→ disconnect(); 266→ } 267→ 268→ return () => { 269→ // Cleanup on unmount 270→ shouldReconnectRef.current = false; 271→ clearTimers(); 272→ if (wsRef.current) { 273→ wsRef.current.close(1000, 'Component unmounted'); 274→ wsRef.current = null; 275→ } 276→ }; 277→ }, [projectName]); // Only depend on projectName to avoid reconnect loops 278→ 279→ return { 280→ status, 281→ sendMessage, 282→ disconnect, 283→ reconnect, 284→ lastMessage, 285→ reconnectAttempts, 286→ error, 287→ }; 288→} 289→ 290→export default useWebSocket; 291→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.