[FE-06] STOMP 실시간 소켓 훅 (useChatSocket)
작업 내용 (설계 의도)
변경 사항
근거: 20260704-채팅시스템고도화-design-fe-app.md “방안 비교 — 실시간 상태”·“재연결/백필”·BE TDD “STOMP 계약”.
WebSocket/STOMP 실시간을 훅으로 캡슐화한다(컴포넌트는 소켓을 직접 다루지 않음). 신규 파일 lib/useChatSocket.ts + @stomp/stompjs 의존성 추가(package.json). RN 전역 WebSocket을 webSocketFactory로 사용, brokerURL은 /ws, CONNECT 헤더에 Authorization: Bearer <accessToken>(useAuthStore).
- 구독:
SUBSCRIBE /topic/rooms/{roomId}·/typing·/read. 수신BroadcastMessage를ChatMessage로 정규화(messageId→id,userId→senderId,createdAt→sentAt)해queryClient.setQueryData(messagesQueryKey(roomId))에 append(dedup by id). 서버 상태 스토어 복사 금지. - 발화:
send(roomId, content)→SEND /app/rooms/{roomId}/send. 미연결 시 RESTPOST /rooms/{id}/messages폴백. - 타이핑/읽음:
sendTyping,sendRead. 수신 타이핑/읽음은 콜백/상태로 노출. - 재연결: 지수 백오프(
reconnectDelay증가), 3회 연속 실패 시pollingFallback=true노출(방 화면이 REST refetchInterval 활성).@react-native-community/netinfo로 오프라인 즉시 감지. - 재연결 성공 시 마지막 수신 id 이후를
backfillMessages(FE-05)로 채우고 dedup. backfill은 FR-10(2단계) 기능 —chat.realtime.enabled게이트 하위에서만 동작하며, 실시간(1단계 핵심 송수신)과 backfill(2단계 보정)의 경계를 코드 주석으로 명시한다. - 기능 플래그
chat.realtime.enabledOFF면 연결하지 않음(REST 폴백만). 실시간 송수신·타이핑·읽음(FR-6/7/8, 1단계)과 재연결 backfill(FR-10, 2단계)이 모두 이 플래그 하위지만, backfill은 2단계 배포 전까지 비활성 가능하도록 별도 조건 분기.
롤백: 플래그 EXPO_PUBLIC_CHAT_REALTIME_ENABLED=false로 즉시 실시간 비활성(REST 폴링 유지).
의존
- FE-02 (chat-types)
다이어그램
처리 흐름
sequenceDiagram participant Screen participant Hook as useChatSocket participant Client as stompjs participant Cache as queryClient Screen->>Hook: subscribe(roomId) Hook->>Client: SUBSCRIBE /topic/rooms/{id} Client-->>Hook: BroadcastMessage Hook->>Hook: normalize → ChatMessage Hook->>Cache: setQueryData(append, dedup) Screen->>Hook: send(content) Hook->>Client: SEND /app/rooms/{id}/send
테스트 케이스
- 수신
BroadcastMessage를ChatMessage로 정규화해 메시지 캐시에 append한다 - 동일 messageId 중복 수신 시 캐시에 한 번만 존재한다(멱등 dedup)
- 미연결 상태에서
send호출 시 REST 폴백으로 전송한다 - 재연결 3회 연속 실패 시
pollingFallback이 true가 된다 - 재연결 성공 시 마지막 수신 id 이후 구간을 backfill로 채운다
- CONNECT 시 Authorization 헤더에 accessToken이 포함된다
- 플래그 OFF면 소켓 연결을 시도하지 않는다