263 lines
6.6 KiB
JavaScript
263 lines
6.6 KiB
JavaScript
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
import { message } from 'antd';
|
|
import { useAuth } from './AuthContext';
|
|
|
|
// WebSocket Context
|
|
const WebSocketContext = createContext();
|
|
|
|
export const WebSocketProvider = ({ children }) => {
|
|
const { user, isAuthenticated } = useAuth();
|
|
const [connected, setConnected] = useState(false);
|
|
const [messages, setMessages] = useState([]);
|
|
const [analysisProgress, setAnalysisProgress] = useState({});
|
|
const wsRef = useRef(null);
|
|
const reconnectTimeoutRef = useRef(null);
|
|
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
|
const maxReconnectAttempts = 5;
|
|
|
|
// WebSocket 연결 함수
|
|
const connect = () => {
|
|
if (!isAuthenticated || !user) {
|
|
return;
|
|
}
|
|
|
|
const token = localStorage.getItem('access_token');
|
|
if (!token) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const wsUrl = `ws://localhost:8000/ws/trading-analysis/?token=${token}`;
|
|
wsRef.current = new WebSocket(wsUrl);
|
|
|
|
wsRef.current.onopen = () => {
|
|
console.log('WebSocket 연결됨');
|
|
setConnected(true);
|
|
setReconnectAttempts(0);
|
|
|
|
// 연결 상태 확인용 ping
|
|
sendMessage({ type: 'ping', timestamp: Date.now() });
|
|
};
|
|
|
|
wsRef.current.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
handleMessage(data);
|
|
} catch (error) {
|
|
console.error('WebSocket 메시지 파싱 오류:', error);
|
|
}
|
|
};
|
|
|
|
wsRef.current.onclose = (event) => {
|
|
console.log('WebSocket 연결 해제:', event.code, event.reason);
|
|
setConnected(false);
|
|
|
|
// 자동 재연결 시도 (정상적인 종료가 아닌 경우)
|
|
if (event.code !== 1000 && reconnectAttempts < maxReconnectAttempts) {
|
|
const delay = Math.pow(2, reconnectAttempts) * 1000; // 지수 백오프
|
|
|
|
reconnectTimeoutRef.current = setTimeout(() => {
|
|
setReconnectAttempts(prev => prev + 1);
|
|
connect();
|
|
}, delay);
|
|
}
|
|
};
|
|
|
|
wsRef.current.onerror = (error) => {
|
|
console.error('WebSocket 오류:', error);
|
|
setConnected(false);
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('WebSocket 연결 실패:', error);
|
|
setConnected(false);
|
|
}
|
|
};
|
|
|
|
// WebSocket 연결 해제 함수
|
|
const disconnect = () => {
|
|
if (reconnectTimeoutRef.current) {
|
|
clearTimeout(reconnectTimeoutRef.current);
|
|
}
|
|
|
|
if (wsRef.current) {
|
|
wsRef.current.close(1000, 'User disconnect');
|
|
wsRef.current = null;
|
|
}
|
|
|
|
setConnected(false);
|
|
setReconnectAttempts(0);
|
|
};
|
|
|
|
// 메시지 전송 함수
|
|
const sendMessage = (data) => {
|
|
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
|
wsRef.current.send(JSON.stringify(data));
|
|
} else {
|
|
console.warn('WebSocket이 연결되지 않음');
|
|
}
|
|
};
|
|
|
|
// 분석 세션 구독
|
|
const subscribeToAnalysis = (sessionId) => {
|
|
sendMessage({
|
|
type: 'subscribe_analysis',
|
|
session_id: sessionId
|
|
});
|
|
};
|
|
|
|
// 메시지 처리 함수
|
|
const handleMessage = (data) => {
|
|
console.log('WebSocket 메시지 수신:', data);
|
|
|
|
switch (data.type) {
|
|
case 'connection_established':
|
|
console.log('WebSocket 연결 설정됨:', data.message);
|
|
break;
|
|
|
|
case 'pong':
|
|
// ping에 대한 응답
|
|
break;
|
|
|
|
case 'analysis_started':
|
|
setAnalysisProgress(prev => ({
|
|
...prev,
|
|
[data.session_id]: {
|
|
status: 'running',
|
|
message: data.message,
|
|
progress: 0
|
|
}
|
|
}));
|
|
message.info(data.message);
|
|
break;
|
|
|
|
case 'analysis_progress':
|
|
setAnalysisProgress(prev => ({
|
|
...prev,
|
|
[data.session_id]: {
|
|
...prev[data.session_id],
|
|
status: 'running',
|
|
message: data.content,
|
|
agent: data.agent,
|
|
progress: data.progress,
|
|
}
|
|
}));
|
|
|
|
// 새로운 메시지 추가
|
|
setMessages(prev => [...prev.slice(-50), {
|
|
id: Date.now(),
|
|
timestamp: new Date(),
|
|
type: data.message_type,
|
|
content: data.content,
|
|
agent: data.agent,
|
|
sessionId: data.session_id
|
|
}]);
|
|
break;
|
|
|
|
case 'analysis_completed':
|
|
setAnalysisProgress(prev => ({
|
|
...prev,
|
|
[data.session_id]: {
|
|
status: 'completed',
|
|
message: data.message,
|
|
progress: 100,
|
|
result: data.result,
|
|
}
|
|
}));
|
|
message.success(data.message);
|
|
break;
|
|
|
|
case 'analysis_failed':
|
|
setAnalysisProgress(prev => ({
|
|
...prev,
|
|
[data.session_id]: {
|
|
status: 'failed',
|
|
message: data.message,
|
|
progress: 0,
|
|
error: data.message
|
|
}
|
|
}));
|
|
message.error(data.message);
|
|
break;
|
|
|
|
case 'subscription_confirmed':
|
|
console.log(`분석 세션 ${data.session_id} 구독 완료`);
|
|
break;
|
|
|
|
case 'subscription_failed':
|
|
message.error(data.message);
|
|
break;
|
|
|
|
case 'error':
|
|
message.error(data.message);
|
|
break;
|
|
|
|
default:
|
|
console.log('알 수 없는 메시지 타입:', data.type);
|
|
}
|
|
};
|
|
|
|
// 분석 진행 상황 초기화
|
|
const clearAnalysisProgress = (sessionId) => {
|
|
setAnalysisProgress(prev => {
|
|
const newProgress = { ...prev };
|
|
delete newProgress[sessionId];
|
|
return newProgress;
|
|
});
|
|
};
|
|
|
|
// 메시지 목록 초기화
|
|
const clearMessages = () => {
|
|
setMessages([]);
|
|
};
|
|
|
|
// 사용자 인증 상태 변경 시 WebSocket 연결/해제
|
|
useEffect(() => {
|
|
if (isAuthenticated && user) {
|
|
connect();
|
|
} else {
|
|
disconnect();
|
|
}
|
|
|
|
return () => {
|
|
disconnect();
|
|
};
|
|
}, [isAuthenticated, user]);
|
|
|
|
// 컴포넌트 언마운트 시 정리
|
|
useEffect(() => {
|
|
return () => {
|
|
disconnect();
|
|
};
|
|
}, []);
|
|
|
|
// Context 값
|
|
const value = {
|
|
connected,
|
|
messages,
|
|
analysisProgress,
|
|
sendMessage,
|
|
subscribeToAnalysis,
|
|
clearAnalysisProgress,
|
|
clearMessages,
|
|
reconnectAttempts,
|
|
maxReconnectAttempts
|
|
};
|
|
|
|
return (
|
|
<WebSocketContext.Provider value={value}>
|
|
{children}
|
|
</WebSocketContext.Provider>
|
|
);
|
|
};
|
|
|
|
// Custom Hook
|
|
export const useWebSocket = () => {
|
|
const context = useContext(WebSocketContext);
|
|
|
|
if (!context) {
|
|
throw new Error('useWebSocket must be used within a WebSocketProvider');
|
|
}
|
|
|
|
return context;
|
|
};
|