You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
1. App 로드 → AuthProvider 마운트
2. AuthContext.useEffect:
- POST /api/auth/refresh (Cookie의 Refresh Token 사용)
- 성공: Access Token 저장 → GET /api/auth/me → user 상태 설정
- 실패: user=null, 로그인 필요 상태
3. ProtectedRoute:
- isLoading: 로딩 스피너
- !user: /auth로 리다이렉트
- requireAdmin && !isAdmin: /로 리다이렉트
- user: children 렌더링
2. SSE 실시간 업데이트 흐름
1. 로그인 성공 → SseProvider 연결 시작
2. GET /api/notifications/stream (fetchEventSource)
- Authorization: Bearer {accessToken}
3. 이벤트 수신 시:
- notification: queryClient.invalidateQueries(notifications) + 토스트
- camera: queryClient.invalidateQueries(cameras)
- event: queryClient.invalidateQueries(events)
- event-deleted: queryClient.invalidateQueries(events)
- member: queryClient.invalidateQueries(users)
- action-update: 커스텀 이벤트 발생 (모달 갱신)
- action-pending: 알림 목록 갱신 + 커스텀 이벤트 발생
- action-resolved: 커스텀 이벤트 발생 (모달 갱신)
4. 연결 오류 시: 5초 후 자동 재연결
5. 로그아웃 시: AbortController로 연결 종료
3. WebRTC 스트리밍 흐름
1. CCTVGrid 렌더링 → setActiveGridCameras([카메라IDs])
2. WebRTCPlayer 마운트:
- subscribeToStream(cameraId) → 상태 구독
- connectStream(cameraId, accessToken, streamUrl) 호출
3. WebRTCContext.connectStream:
- RTCPeerConnection 생성
- addTransceiver('video', 'audio')
- createOffer() → POST /stream/{camera}/whep
- Authorization: Basic base64("_:" + accessToken)
- SDP Answer 수신 → setRemoteDescription
- ontrack → MediaStream 저장 → 구독자에게 알림
4. WebRTCPlayer:
- stream 수신 → video.srcObject = stream
- state=error → 에러 메시지 표시
5. 페이지 이동 시:
- setActiveGridCameras([새 카메라IDs])
- 이전 페이지 카메라 자동 disconnectStream
4. API 요청/응답 및 에러 처리 흐름
1. API 요청 (lib/axios.ts):
- 요청 인터셉터: Authorization 헤더 주입
- 응답 인터셉터:
- 401/403 에러 → refresh 시도 → 재요청
- refresh 실패 → /auth로 리다이렉트
2. React Query 패턴:
- useQuery: 데이터 조회 (캐싱, staleTime: 30초)
- useMutation: 데이터 변경 → onSuccess에서 invalidateQueries
3. 낙관적 업데이트 (카메라 설정 등):
- onMutate: 캐시 즉시 업데이트
- onError: 롤백
- onSettled: invalidateQueries로 서버 상태 동기화
핵심 컴포넌트 상세
AuthContext
interfaceAuthContextType{user: User|null;// 현재 로그인 사용자isLoading: boolean;// 인증 상태 로딩 중login: (email,password)=>Promise<{success,error?}>;signup: (email,password,name)=>Promise<{success,error?}>;logout: ()=>Promise<void>;isAdmin: boolean;// user?.role === 'admin'}
SseContext
연결 관리: 로그인 시 자동 연결, 로그아웃 시 자동 해제
이벤트 핸들링: 9가지 이벤트 타입별 React Query 캐시 무효화
재연결: 연결 오류 시 5초 후 자동 재연결
토스트: notification 이벤트 수신 시 자동 표시
eventId 포함 시 토스트 클릭으로 이벤트 모달 열기 가능 (X 버튼은 제외)
커스텀 이벤트 aegis:open-event-modal 발생 → GlobalEventModal 처리
GlobalEventModal
위치: Providers에서 전역으로 렌더링
기능: 어느 페이지에서든 토스트 클릭 시 이벤트 상세 모달 표시
동작: aegis:open-event-modal 이벤트 수신 → API로 이벤트 조회 → 모달 표시
WebRTCContext
interfaceWebRTCContextType{getStream: (cameraId)=>StreamInfo|null;connectStream: (cameraId,accessToken,streamUrl)=>Promise<void>;disconnectStream: (cameraId)=>void;subscribeToStream: (cameraId,callback)=>()=>void;// cleanup 함수 반환setActiveGridCameras: (cameraIds)=>void;// 페이지별 활성 카메라 설정cleanupAll: ()=>void;}interfaceStreamInfo{pc: RTCPeerConnection|null;stream: MediaStream|null;cameraId: string;state: 'connecting'|'playing'|'error';errorMessage?: string;}
setActiveGridCameras(): 활성 카메라 설정 (페이지 전환 시 자동 해제)
React Query
queryKeys.ts: 쿼리 키 중앙 관리
SSE 이벤트 수신 시 invalidateQueries()로 캐시 무효화
기본 설정: staleTime: 30초, retry: 1, refetchOnWindowFocus: false
API 클라이언트 (lib/api.ts)
authApi
메서드
설명
login(data)
로그인
signup(data)
회원가입
logout()
로그아웃
refresh()
토큰 갱신
me()
내 정보 조회
updateProfile(data)
프로필 수정
changePassword(data)
비밀번호 변경
deleteAccount()
회원 탈퇴
camerasApi
메서드
설명
getAll(page, size)
카메라 목록 (페이지네이션, 기본 size=6)
getAllList()
카메라 전체 목록 (멤버 관리용)
update(id, data)
카메라 정보 수정
eventsApi
메서드
설명
getAll(page, size, filters?)
이벤트 목록 (페이지네이션, 서버사이드 필터링)
getById(id)
이벤트 상세 조회
getClipUrl(id)
클립 재생용 presigned URL
downloadClip(id, filename)
클립 다운로드
resolveAction(eventId, actionId, approved)
액션 승인/거부 (Human-in-the-Loop)
EventFilters 인터페이스:
interfaceEventFilters{risks?: string[];// 위험도 필터 (suspicious, abnormal)types?: string[];// 이상행동 유형 (assault, burglary, dump, swoon, vandalism)statuses?: string[];// 분석 상태 (processing, analyzed)cameraIds?: string[];// 카메라 ID 목록startDate?: string;// 시작 날짜 (ISO 8601)endDate?: string;// 종료 날짜 (ISO 8601)}
notificationsApi
메서드
설명
getAll()
알림 목록
deleteAll()
전체 삭제
statsApi
메서드
설명
getDaily()
일별 통계 (주간)
getEventTypes()
유형별 통계
getMonthly()
월별 통계 (캘린더용)
usersApi (Admin)
메서드
설명
getApproved(page, size)
승인된 사용자 목록 (최신 가입순)
getPending(page, size)
미승인 사용자 목록 (최신 가입순)
getPendingCount()
미승인 사용자 수
update(id, data)
사용자 정보 수정
delete(id)
사용자 삭제
approve(id)
사용자 승인
인증 흐름
토큰 관리
Access Token: 메모리 저장 (lib/axios.ts의 accessToken 변수)
Refresh Token: HttpOnly Cookie (서버에서 설정)
Axios 인터셉터 (lib/axios.ts)
요청 인터셉터: Authorization: Bearer {accessToken} 헤더 자동 주입
응답 인터셉터: 401/403 에러 시 자동 토큰 갱신 후 재요청
갱신 실패 시 /auth 페이지로 리다이렉트
앱 로드 시 인증 복원
AuthContext에서 /api/auth/refresh 호출
성공 시 Access Token 저장 + 사용자 정보 조회
실패 시 로그인 필요 상태
WebRTC 스트리밍
WHEP 프로토콜
엔드포인트: /stream/{cameraName}/whep
인증: Basic Auth (_:{accessToken} base64 인코딩)
연결 흐름
WebRTCPlayer에서 connectStream() 호출
RTCPeerConnection 생성 + Offer 생성
WHEP 엔드포인트로 SDP Offer 전송
SDP Answer 수신 후 연결 완료
ontrack 이벤트로 MediaStream 수신
페이지 전환 시 스트림 관리
CCTVGrid에서 setActiveGridCameras() 호출
이전 페이지 카메라는 자동 연결 해제
현재 페이지 카메라만 연결 유지
타입 정의 (types/index.ts)
Camera
interfaceCamera{id: string;name: string;// 미디어서버 원본 이름connected: boolean;// 온라인/오프라인}interfaceManagedCameraextendsCamera{location: string;// 장소 (수정 가능)enabled: boolean;// 카메라 ON/OFFanalysisEnabled: boolean;// AI 분석 ON/OFFstreamUrl: string;// WebRTC WHEP URL}interfaceCameraUpdateRequest{location?: string;enabled?: boolean;analysisEnabled?: boolean;}
typeUserRole='user'|'admin';interfaceUser{id: string;email: string;name: string;role: UserRole;assignedCameras: string[];// 어드민은 자동으로 전체 카메라 접근 권한createdAt: string;approved: boolean;}interfaceUserUpdateRequest{name?: string;role?: UserRole;assignedCameras?: string[];// 어드민은 수정 불가 (전체 접근)}