175 lines
5.1 KiB
TypeScript
175 lines
5.1 KiB
TypeScript
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<boolean>;
|
|
disconnectFromServer: () => void;
|
|
abortConnectionAttempt: () => void;
|
|
}
|
|
|
|
const WebSocketContext = createContext<WebSocketContextType | undefined>(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<WebSocketProviderProps> = ({ children, url: initialUrl = '' }) => {
|
|
const [apiUrl, setApiUrl] = useState<string>(initialUrl);
|
|
const [isConnected, setIsConnected] = useState<boolean>(false);
|
|
const [isConnecting, setConnecting] = useState<boolean>(false);
|
|
const [wsStatus, setWsStatus] = useState<WsStatus>('DISCONNECTED');
|
|
const [connectionAborted, setConnectionAborted] = useState<boolean>(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<boolean> => {
|
|
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<boolean> => {
|
|
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 (
|
|
<WebSocketContext.Provider
|
|
value={{
|
|
isConnected,
|
|
isConnecting,
|
|
wsStatus,
|
|
apiUrl,
|
|
setApiUrl,
|
|
connectToServer,
|
|
disconnectFromServer,
|
|
abortConnectionAttempt,
|
|
}}
|
|
>
|
|
{children}
|
|
</WebSocketContext.Provider>
|
|
);
|
|
}; |