ALwrity version 0.5.5
This commit is contained in:
232
frontend/src/hooks/useRealTimeData.ts
Normal file
232
frontend/src/hooks/useRealTimeData.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
interface RealTimeDataOptions {
|
||||
strategyId: number;
|
||||
reconnectInterval?: number;
|
||||
maxReconnectAttempts?: number;
|
||||
onConnect?: () => void;
|
||||
onDisconnect?: () => void;
|
||||
onError?: (error: Event) => void;
|
||||
onMessage?: (data: any) => void;
|
||||
}
|
||||
|
||||
interface RealTimeDataState {
|
||||
data: any;
|
||||
isConnected: boolean;
|
||||
isConnecting: boolean;
|
||||
error: string | null;
|
||||
reconnectAttempts: number;
|
||||
}
|
||||
|
||||
export const useRealTimeData = (options: RealTimeDataOptions) => {
|
||||
const {
|
||||
strategyId,
|
||||
reconnectInterval = 5000,
|
||||
maxReconnectAttempts = 5,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
onError,
|
||||
onMessage
|
||||
} = options;
|
||||
|
||||
const [state, setState] = useState<RealTimeDataState>({
|
||||
data: null,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
error: null,
|
||||
reconnectAttempts: 0
|
||||
});
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(prev => ({ ...prev, isConnecting: true, error: null }));
|
||||
|
||||
try {
|
||||
// For development, use a mock WebSocket connection
|
||||
// In production, this would be the actual WebSocket URL
|
||||
const wsUrl = process.env.NODE_ENV === 'development'
|
||||
? `ws://localhost:8000/ws/strategy/${strategyId}/live`
|
||||
: `wss://api.alwrity.com/ws/strategy/${strategyId}/live`;
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isConnected: true,
|
||||
isConnecting: false,
|
||||
error: null,
|
||||
reconnectAttempts: 0
|
||||
}));
|
||||
reconnectAttemptsRef.current = 0;
|
||||
onConnect?.();
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
setState(prev => ({ ...prev, data }));
|
||||
onMessage?.(data);
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isConnected: false,
|
||||
isConnecting: false
|
||||
}));
|
||||
onDisconnect?.();
|
||||
|
||||
// Attempt to reconnect if not a clean close
|
||||
if (event.code !== 1000 && reconnectAttemptsRef.current < maxReconnectAttempts) {
|
||||
reconnectAttemptsRef.current += 1;
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
reconnectAttempts: reconnectAttemptsRef.current
|
||||
}));
|
||||
|
||||
reconnectTimeoutRef.current = setTimeout(() => {
|
||||
connect();
|
||||
}, reconnectInterval);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'WebSocket connection error',
|
||||
isConnecting: false
|
||||
}));
|
||||
onError?.(error);
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'Failed to create WebSocket connection',
|
||||
isConnecting: false
|
||||
}));
|
||||
}
|
||||
}, [strategyId, reconnectInterval, maxReconnectAttempts, onConnect, onDisconnect, onError, onMessage]);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
if (reconnectTimeoutRef.current) {
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
reconnectTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close(1000); // Clean close
|
||||
wsRef.current = null;
|
||||
}
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
reconnectAttempts: 0
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const sendMessage = useCallback((message: any) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify(message));
|
||||
} else {
|
||||
console.warn('WebSocket is not connected');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Connect on mount and when strategyId changes
|
||||
useEffect(() => {
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
}, [connect, disconnect]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
}, [disconnect]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
connect,
|
||||
disconnect,
|
||||
sendMessage
|
||||
};
|
||||
};
|
||||
|
||||
// Mock real-time data for development
|
||||
export const useMockRealTimeData = (strategyId: number) => {
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate real-time data updates
|
||||
const interval = setInterval(() => {
|
||||
const mockData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
strategyId,
|
||||
metrics: {
|
||||
traffic_growth: Math.random() * 20 + 5, // 5-25%
|
||||
engagement_rate: Math.random() * 10 + 5, // 5-15%
|
||||
conversion_rate: Math.random() * 3 + 1, // 1-4%
|
||||
content_quality_score: Math.random() * 20 + 80, // 80-100%
|
||||
strategy_adoption_rate: Math.random() * 20 + 80, // 80-100%
|
||||
roi_ratio: Math.random() * 2 + 2, // 2-4x
|
||||
competitive_position_rank: Math.floor(Math.random() * 5) + 1, // 1-5
|
||||
audience_growth_percentage: Math.random() * 15 + 8, // 8-23%
|
||||
confidence_score: Math.random() * 20 + 80 // 80-100%
|
||||
},
|
||||
alerts: Math.random() > 0.8 ? [
|
||||
{
|
||||
type: 'warning',
|
||||
message: 'Engagement rate dropped below target',
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
] : [],
|
||||
trends: {
|
||||
daily: Array.from({ length: 7 }, (_, i) => ({
|
||||
date: new Date(Date.now() - (6 - i) * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||||
traffic_growth: Math.random() * 20 + 5,
|
||||
engagement_rate: Math.random() * 10 + 5,
|
||||
conversion_rate: Math.random() * 3 + 1,
|
||||
content_quality_score: Math.random() * 20 + 80
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
setData(mockData);
|
||||
}, 5000); // Update every 5 seconds
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [strategyId]);
|
||||
|
||||
return {
|
||||
data,
|
||||
isConnected: true,
|
||||
isConnecting: false,
|
||||
error: null,
|
||||
reconnectAttempts: 0,
|
||||
connect: () => {},
|
||||
disconnect: () => {},
|
||||
sendMessage: () => {}
|
||||
};
|
||||
};
|
||||
|
||||
// Export both hooks
|
||||
export default useRealTimeData;
|
||||
Reference in New Issue
Block a user