Skip to content

Commit e13a3c1

Browse files
committed
refactor: SSE 응답 수정
1 parent 999e86e commit e13a3c1

File tree

4 files changed

+91
-55
lines changed

4 files changed

+91
-55
lines changed

src/components/quiz/TodayQuiz/hooks/useQuizSubmission.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useRef, createElement } from 'react';
1+
import { useState, useRef, createElement, useCallback } from 'react';
22
import { quizAPI } from '../../../../utils/api';
33
import { QuizData, AnswerResult } from '../types';
44
import DuplicateAnswerModal from '../../../common/DuplicateAnswerModal';
@@ -271,6 +271,8 @@ export const useQuizSubmission = () => {
271271
// onError: 에러 처리
272272
(error: Event) => {
273273
console.error('AI 피드백 스트리밍 실패:', error);
274+
console.error('에러 타입:', error.type);
275+
console.error('에러 대상:', error.target);
274276

275277
const errorResult: AnswerResult = {
276278
isCorrect: false,
@@ -308,12 +310,12 @@ export const useQuizSubmission = () => {
308310
}
309311
};
310312

311-
const cleanup = () => {
312-
if (sseConnectionRef.current) {
313+
const cleanup = useCallback(() => {
314+
if (sseConnectionRef.current) {;
313315
sseConnectionRef.current.close();
314316
sseConnectionRef.current = null;
315317
}
316-
};
318+
}, []);
317319

318320
return {
319321
isSubmitted,

src/components/quiz/TodayQuiz/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,17 @@ const TodayQuizSection: React.FC = () => {
6666

6767
// SSE 연결 정리 - 컴포넌트 언마운트 시
6868
useEffect(() => {
69+
const handleBeforeUnload = () => {
70+
cleanup();
71+
};
72+
73+
window.addEventListener('beforeunload', handleBeforeUnload);
74+
6975
return () => {
76+
window.removeEventListener('beforeunload', handleBeforeUnload);
7077
cleanup();
7178
};
72-
}, [cleanup]);
79+
}, [cleanup]); // cleanup을 다시 dependency로 추가 (이제 useCallback으로 메모이제이션됨)
7380

7481
const handleSubmit = async (e: React.FormEvent) => {
7582
e.preventDefault();

src/components/sections/HeroSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const HeroSection: React.FC<HeroSectionProps> = ({ onSubscribeClick }) => {
4444
<div className="w-6 h-6 sm:w-8 sm:h-8 bg-gradient-to-r from-navy-400 to-navy-500 rounded-full border-2 border-white"></div>
4545
<div className="w-6 h-6 sm:w-8 sm:h-8 bg-gradient-to-r from-brand-500 to-navy-500 rounded-full border-2 border-white"></div>
4646
</div>
47-
<span className="text-center sm:text-left">이미 <strong className="text-gray-800">1,000+</strong> 취준생이 사용 중</span>
47+
<span className="text-center sm:text-left">이미 <strong className="text-gray-800">10+</strong> 취준생이 사용 중</span>
4848
</div>
4949
</div>
5050
</div>

src/utils/api.ts

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -164,60 +164,87 @@ export const quizAPI = {
164164
// AI 피드백 SSE 스트리밍 (주관식)
165165
streamAiFeedback: (answerId: string, onData: (data: string) => void, onComplete: () => void, onError: (error: Event) => void) => {
166166
let isCompleted = false;
167+
let eventSource: EventSource | null = null;
167168

168-
const eventSource = new EventSource(`${apiUrl}/quizzes/${answerId}/feedback`, {
169-
withCredentials: true
170-
// EventSource는 자동으로 Accept: text/event-stream 헤더 설정
171-
});
172-
173-
eventSource.onmessage = (event) => {
174-
if (isCompleted) return;
169+
try {
170+
eventSource = new EventSource(`${apiUrl}/quizzes/${answerId}/feedback`, {
171+
withCredentials: true
172+
// EventSource는 자동으로 Accept: text/event-stream 헤더 설정
173+
});
174+
175+
eventSource.onmessage = (event) => {
176+
if (isCompleted) return;
177+
178+
// [종료] 메시지를 받으면 정상 종료
179+
if (event.data === '[종료]' || event.data === '[완료]' || event.data === 'END') {
180+
isCompleted = true;
181+
if (eventSource) {
182+
eventSource.close();
183+
eventSource = null;
184+
}
185+
onComplete();
186+
return;
187+
}
188+
189+
onData(event.data);
190+
};
175191

176-
// [종료] 메시지를 받으면 정상 종료
177-
if (event.data === '[종료]' || event.data === '[완료]' || event.data === 'END') {
192+
eventSource.onerror = (error) => {
193+
if (isCompleted) return; // 이미 정상 완료된 경우 에러 무시
194+
195+
console.log('SSE 연결 에러 발생!', new Date().toISOString());
196+
console.log('SSE 연결 에러:', error);
178197
isCompleted = true;
179-
eventSource.close();
180-
onComplete();
181-
return;
182-
}
198+
if (eventSource) {
199+
eventSource.close();
200+
eventSource = null;
201+
}
202+
onError(error);
203+
};
183204

184-
onData(event.data);
185-
};
186-
187-
eventSource.onerror = (error) => {
188-
if (isCompleted) return; // 이미 정상 완료된 경우 에러 무시
205+
eventSource.addEventListener('complete', () => {
206+
if (!isCompleted) {
207+
isCompleted = true;
208+
if (eventSource) {
209+
eventSource.close();
210+
eventSource = null;
211+
}
212+
onComplete();
213+
}
214+
});
189215

190-
console.log('SSE 연결 에러:', error);
191-
eventSource.close();
192-
onError(error);
193-
};
194-
195-
eventSource.addEventListener('complete', () => {
196-
if (!isCompleted) {
197-
isCompleted = true;
198-
eventSource.close();
199-
onComplete();
200-
}
201-
});
202-
203-
// 타임아웃 설정 (선택사항 - 너무 오래 걸리면 자동 종료)
204-
const timeout = setTimeout(() => {
205-
if (!isCompleted) {
206-
console.log('SSE 스트리밍 타임아웃');
207-
isCompleted = true;
208-
eventSource.close();
209-
onError(new Event('timeout'));
210-
}
211-
}, 60000); // 60초 타임아웃
212-
213-
// 정리 함수 반환
214-
return {
215-
close: () => {
216-
isCompleted = true;
217-
clearTimeout(timeout);
218-
eventSource.close();
219-
}
220-
};
216+
// 타임아웃 설정 (선택사항 - 너무 오래 걸리면 자동 종료)
217+
const timeout = setTimeout(() => {
218+
if (!isCompleted) {
219+
console.log('SSE 스트리밍 타임아웃');
220+
isCompleted = true;
221+
if (eventSource) {
222+
eventSource.close();
223+
eventSource = null;
224+
}
225+
onError(new Event('timeout'));
226+
}
227+
}, 60000); // 60초 타임아웃
228+
229+
// 정리 함수 반환
230+
return {
231+
close: () => {
232+
isCompleted = true;
233+
clearTimeout(timeout);
234+
if (eventSource) {
235+
eventSource.close();
236+
eventSource = null;
237+
}
238+
}
239+
};
240+
} catch (error) {
241+
console.error('SSE 연결 생성 실패:', error);
242+
console.error('에러 상세:', error);
243+
onError(error as Event);
244+
return {
245+
close: () => {}
246+
};
247+
}
221248
},
222249
};
223250

0 commit comments

Comments
 (0)