Skip to content

Commit 81a395b

Browse files
authored
Merge pull request #192 from Sysone-Final/dev
Dev
2 parents a8e5e58 + c44d55c commit 81a395b

3 files changed

Lines changed: 122 additions & 72 deletions

File tree

src/domains/serverView/rack/components/RackView.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,11 @@ function RackView({ rackName, serverRoomId, onClose }: RackViewProps) {
6666
);
6767

6868
const backgroundSSEEquipmentIds = useMemo(() => {
69-
const shouldStart =
70-
!rackManager.isLoading && selectableEquipmentIds.length > 0;
71-
72-
if (shouldStart) {
73-
return selectableEquipmentIds;
69+
if (rackManager.isLoading || selectableEquipmentIds.length === 0) {
70+
return [];
7471
}
75-
return [];
76-
}, [rackManager.isLoading, selectableEquipmentIds]);
72+
return selectableEquipmentIds.filter((id) => id !== selectedDevice?.id);
73+
}, [rackManager.isLoading, selectableEquipmentIds, selectedDevice?.id]);
7774

7875
const backgroundCallbacks = useCallback(
7976
() => ({

src/domains/serverView/serverDashboard/hooks/useAllEquipmentBackgroundSSE.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export const useAllEquipmentBackgroundSSE = (
4949
equipmentIds: number[],
5050
callbacks: BackgroundSSECallbacks
5151
) => {
52-
// ✅ useRef로 콜백 참조 유지 (매번 새로 생성되어도 dependency에 영향 없음)
5352
const callbacksRef = useRef(callbacks);
5453

5554
useEffect(() => {
@@ -185,6 +184,5 @@ export const useAllEquipmentBackgroundSSE = (
185184
});
186185
eventSourceMap.clear();
187186
};
188-
// ✅ equipmentIds만 dependency에 포함 (callbacks은 제외)
189187
}, [equipmentIds]);
190188
};
Lines changed: 118 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
// ============================================================================
2-
// useEquipmentSSE.ts - 콜백 패턴 (안정적 버전)
3-
// ============================================================================
4-
1+
// useEquipmentSSE.ts - 타입 수정
52
import { useEffect, useRef } from "react";
63
import { EventSourcePolyfill } from "event-source-polyfill";
74
import { getAccessToken, BASE_URL } from "@/api/client";
@@ -23,79 +20,137 @@ export const useEquipmentSSE = (
2320
callbacks: SSECallbacks,
2421
enabled: boolean = true
2522
) => {
26-
// ✅ useRef로 콜백 참조 유지 (매번 새로 생성되어도 dependency에 영향 없음)
2723
const callbacksRef = useRef(callbacks);
24+
const reconnectAttemptsRef = useRef(0);
25+
const MAX_RECONNECT_ATTEMPTS = 5;
26+
const RECONNECT_DELAY = 3000;
2827

2928
useEffect(() => {
3029
callbacksRef.current = callbacks;
3130
}, [callbacks]);
3231

3332
useEffect(() => {
34-
if (!enabled || !equipmentId) return;
33+
if (!enabled || !equipmentId) {
34+
reconnectAttemptsRef.current = 0;
35+
return;
36+
}
3537

3638
const token = getAccessToken();
3739
if (!token) return;
3840

3941
const url = `${BASE_URL}/monitoring/subscribe/equipment/${equipmentId}`;
42+
let eventSource: EventSource | null = null;
43+
let reconnectTimeout: number | null = null; // 🔥 여기 수정
4044

41-
try {
42-
const eventSource = new EventSourcePolyfill(url, {
43-
headers: {
44-
Authorization: `Bearer ${token}`,
45-
},
46-
withCredentials: true,
47-
});
48-
49-
eventSource.addEventListener("system", (event) => {
50-
try {
51-
const data: SystemMonitoringData = JSON.parse(event.data);
52-
callbacksRef.current.onSystemData?.(data);
53-
} catch (error) {
54-
console.error(
55-
`[Equipment ${equipmentId}] System data parse error:`,
56-
error
57-
);
58-
}
59-
});
60-
61-
eventSource.addEventListener("disk", (event) => {
62-
try {
63-
const data: DiskMonitoringData = JSON.parse(event.data);
64-
callbacksRef.current.onDiskData?.(data);
65-
} catch (error) {
66-
console.error(
67-
`[Equipment ${equipmentId}] Disk data parse error:`,
68-
error
69-
);
70-
}
71-
});
72-
73-
eventSource.addEventListener("network", (event) => {
74-
try {
75-
const data: NetworkMonitoringData[] = JSON.parse(event.data);
76-
callbacksRef.current.onNetworkData?.(data);
77-
} catch (error) {
78-
console.error(
79-
`[Equipment ${equipmentId}] Network data parse error:`,
80-
error
81-
);
82-
}
83-
});
84-
85-
eventSource.onerror = (error) => {
86-
console.error(`[Equipment ${equipmentId}] SSE Error:`, error);
87-
eventSource.close();
88-
};
45+
const connect = () => {
46+
try {
47+
console.log(
48+
`[Equipment ${equipmentId}] Establishing SSE connection...`
49+
);
50+
51+
eventSource = new EventSourcePolyfill(url, {
52+
headers: {
53+
Authorization: `Bearer ${token}`,
54+
},
55+
withCredentials: true,
56+
heartbeatTimeout: 120000,
57+
}) as EventSource;
58+
59+
eventSource.addEventListener("system", (event) => {
60+
try {
61+
const data: SystemMonitoringData = JSON.parse(event.data);
62+
reconnectAttemptsRef.current = 0;
63+
callbacksRef.current.onSystemData?.(data);
64+
} catch (error) {
65+
console.error(
66+
`[Equipment ${equipmentId}] System data parse error:`,
67+
error
68+
);
69+
}
70+
});
71+
72+
eventSource.addEventListener("disk", (event) => {
73+
try {
74+
const data: DiskMonitoringData = JSON.parse(event.data);
75+
callbacksRef.current.onDiskData?.(data);
76+
} catch (error) {
77+
console.error(
78+
`[Equipment ${equipmentId}] Disk data parse error:`,
79+
error
80+
);
81+
}
82+
});
83+
84+
eventSource.addEventListener("network", (event) => {
85+
try {
86+
const data: NetworkMonitoringData[] = JSON.parse(event.data);
87+
callbacksRef.current.onNetworkData?.(data);
88+
} catch (error) {
89+
console.error(
90+
`[Equipment ${equipmentId}] Network data parse error:`,
91+
error
92+
);
93+
}
94+
});
95+
96+
eventSource.onerror = (error) => {
97+
console.error(`[Equipment ${equipmentId}] SSE Error:`, error);
98+
99+
if (eventSource) {
100+
eventSource.close();
101+
eventSource = null;
102+
}
103+
104+
callbacksRef.current.onError?.(error);
89105

90-
return () => {
106+
if (reconnectAttemptsRef.current < MAX_RECONNECT_ATTEMPTS) {
107+
reconnectAttemptsRef.current++;
108+
const delay =
109+
RECONNECT_DELAY * Math.pow(2, reconnectAttemptsRef.current - 1);
110+
111+
console.log(
112+
`[Equipment ${equipmentId}] Reconnecting in ${delay}ms... (attempt ${reconnectAttemptsRef.current}/${MAX_RECONNECT_ATTEMPTS})`
113+
);
114+
115+
reconnectTimeout = window.setTimeout(() => {
116+
// 🔥 window.setTimeout으로 명시
117+
if (enabled) {
118+
connect();
119+
}
120+
}, delay);
121+
} else {
122+
console.error(
123+
`[Equipment ${equipmentId}] Max reconnection attempts reached`
124+
);
125+
}
126+
};
127+
128+
eventSource.onopen = () => {
129+
console.log(`[Equipment ${equipmentId}] SSE connection established`);
130+
reconnectAttemptsRef.current = 0;
131+
};
132+
} catch (error) {
133+
console.error(
134+
`[Equipment ${equipmentId}] Failed to create SSE connection:`,
135+
error
136+
);
137+
}
138+
};
139+
140+
connect();
141+
142+
return () => {
143+
console.log(`[Equipment ${equipmentId}] Closing SSE connection`);
144+
145+
if (reconnectTimeout !== null) {
146+
window.clearTimeout(reconnectTimeout); // 🔥 window.clearTimeout으로 명시
147+
}
148+
149+
if (eventSource) {
91150
eventSource.close();
92-
};
93-
} catch (error) {
94-
console.error(
95-
`[Equipment ${equipmentId}] Failed to create SSE connection:`,
96-
error
97-
);
98-
}
99-
// ✅ equipmentId와 enabled만 dependency에 포함 (callbacks는 제외)
151+
}
152+
153+
reconnectAttemptsRef.current = 0;
154+
};
100155
}, [equipmentId, enabled]);
101156
};

0 commit comments

Comments
 (0)