import React, { createContext, useContext, useEffect, useState, useCallback, ReactNode } from 'react'; import modbusService from '@/services/modbusService'; import logger from '@/Logger'; export type WsStatus = 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED' | 'ERROR' | 'RECONNECTING'; interface WebSocketContextType { isConnected: boolean; isConnecting: boolean; wsStatus: WsStatus; apiUrl: string; setApiUrl: (url: string) => void; connectToServer: (url?: string) => Promise; disconnectFromServer: () => void; abortConnectionAttempt: () => void; } const WebSocketContext = createContext(undefined); export const useWebSocket = () => { const context = useContext(WebSocketContext); if (context === undefined) { throw new Error('useWebSocket must be used within a WebSocketProvider'); } return context; }; interface WebSocketProviderProps { children: ReactNode; url?: string; } export const WebSocketProvider: React.FC = ({ children, url: initialUrl = '' }) => { const [apiUrl, setApiUrl] = useState(initialUrl); const [isConnected, setIsConnected] = useState(false); const [isConnecting, setConnecting] = useState(false); const [wsStatus, setWsStatus] = useState('DISCONNECTED'); const [connectionAborted, setConnectionAborted] = useState(false); // Use a ref or simply the variable to track initialization to prevent double connect in strict mode if needed, // but simpler logic is often better. const handleWsStatusChange = useCallback((status: WsStatus) => { setWsStatus(status); setIsConnected(status === 'CONNECTED'); if (status === 'CONNECTED') { setConnecting(false); } else { if (status === 'ERROR') { setConnecting(false); } else if (status === 'RECONNECTING') { setConnecting(true); } else if (status === 'DISCONNECTED') { setConnecting(false); } } }, []); const disconnectWebSocket = useCallback((intentional: boolean = true) => { modbusService.disconnect(intentional); }, []); const connectWebSocket = useCallback(async (urlToConnect: string): Promise => { if (!urlToConnect) { logger.error('API URL not set'); return false; } let wsUrl: string; try { const url = new URL(urlToConnect); // If the providing URL is http/https, switch to ws/wss. If it's already ws/wss, use as is. // The previous logic assumed http input. Let's be robust. if (url.protocol === 'http:') { url.protocol = 'ws:'; } else if (url.protocol === 'https:') { url.protocol = 'wss:'; } // Ensure /ws path if (!url.pathname.endsWith('/ws')) { url.pathname = url.pathname.replace(/\/+$/, '') + '/ws'; } wsUrl = url.toString(); } catch (error) { logger.logError(error, 'Invalid API URL'); return false; } try { const newConnectionEstablished = await modbusService.connect( wsUrl, handleWsStatusChange, ); return newConnectionEstablished; } catch (error) { logger.logError(error, '[ModbusContext] modbusService.connect() failed'); return false; } }, [handleWsStatusChange]); const abortConnectionAttempt = useCallback(() => { setConnectionAborted(true); setConnecting(false); disconnectWebSocket(true); }, [disconnectWebSocket]); const connectToServer = useCallback(async (urlToUse?: string): Promise => { const targetUrl = urlToUse || apiUrl; if (urlToUse && urlToUse !== apiUrl) { setApiUrl(targetUrl); } setConnectionAborted(false); setConnecting(true); try { const didConnect = await connectWebSocket(targetUrl); if (didConnect) { } else { if (modbusService.getConnectionStatus() === 'CONNECTED') { } else { } } const isNowConnected = modbusService.getConnectionStatus() === 'CONNECTED'; setConnecting(false); return isNowConnected; } catch (error: any) { logger.logError(error, '[ModbusContext] Error in connectToServer'); setIsConnected(false); setConnecting(false); return false; } }, [apiUrl, connectWebSocket]); const disconnectFromServer = useCallback((): void => { setConnectionAborted(true); setIsConnected(false); disconnectWebSocket(true); }, [disconnectWebSocket]); // Initial connection effect useEffect(() => { if (initialUrl && !isConnected && !isConnecting && !connectionAborted) { connectToServer(initialUrl); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialUrl]); // We strictly want this only on mount or if initialUrl changes significantly and we want to reconnect. // Including dependencies like 'connectToServer' might cause loops if not careful. return ( {children} ); };