Skip to content

feat: 채팅 소켓 토큰 만료 재연결 및 끊김 안내 개선#293

Open
khkim6040 wants to merge 10 commits into
mainfrom
feat/websocket-token-refresh
Open

feat: 채팅 소켓 토큰 만료 재연결 및 끊김 안내 개선#293
khkim6040 wants to merge 10 commits into
mainfrom
feat/websocket-token-refresh

Conversation

@khkim6040

Copy link
Copy Markdown
Member

#️⃣ 연관된 이슈

X (서버: PoApper/paxi-popo-nest-api#154 와 세트로 동작)

📝 작업 내용

Paxi 채팅방(NewChatScreen) 소켓 연결의 토큰 만료 복구와 끊김 안내를 개선합니다.
src/screens/paxi/HANDOFF.md "채팅 소켓 재연결 안내 개선" 섹션의 #1~#5 중 보안(토큰 query string 제거)을 제외한 전부를 처리했습니다.

기존

  • 토큰 만료 시 복구 불가: 토큰이 소켓 생성 시 URL/auth에 박혀, 자동 재연결이 만료된 토큰을 그대로 재사용 → 5초마다 영원히 실패. accessTokenExpired 핸들러는 TODO만 있고 미구현.
  • 부정확한 안내: "재연결 시도 N회"의 N은 disconnect/error 양쪽에서 증가하는 끊김 누적 수일 뿐 실제 재연결 시도 횟수가 아니며, 숫자가 커질수록 불안만 유발.
  • 취약한 재조회: 놓친 메시지 재조회가 useEffect([reconnectAttempt])의 "0으로 리셋되는 부수효과"에 의존하는 비직관적 구조.
  • 고정 5초 재시도(백오프 없음), 끊김 배너 위치 top: 60 하드코딩.

작업 후

  • 토큰 만료 → 갱신 → 소켓 재생성: accessTokenExpired 수신 시 refreshAccessToken()으로 토큰을 갱신하고 새 토큰으로 소켓을 재생성. 오케스트레이션은 socket-reauth.ts(reconnectWithFreshToken)로 분리하고 중복 진입/반복 실패(최대 2회) 가드 추가. 갱신 실패 시 기존 로그아웃 흐름에 위임.
  • 안내 단순화: reconnectAttempt(number) → hasDisconnected(boolean), 문구 "연결이 끊어졌습니다. 재연결 중…"으로 변경(이중 카운팅 문제 제거).
  • 재조회 명시화: 재연결 성공 콜백 onSocketConnected에서 직접 재조회. hasConnectedOnceRef로 최초 연결의 중복 조회 방지.
  • 점증 백오프: reconnectionDelayMax(30s) 추가. 배너 레이아웃: 절대 위치 → 헤더 아래 레이아웃 흐름 배치.

✅ 테스트 여부 체크(환경 명시) & 스크린샷

  • Android 환경에서 기능이 의도대로 작동함을 확인했습니다. 환경) API 36, Android 14
  • iOS 환경에서 기능이 의도대로 작동함을 확인했습니다. 환경) iOS 18.5

단위 테스트: socket-factory.test.ts(이벤트 배선/위임 7건), socket-reauth.test.ts(순서·중복무시·상한·실패 시 가드해제 8건) 추가. 전체 jest 34/34 통과, npm run pc 통과.
실기기 시나리오 미검증 — 짧은 만료 토큰으로 (1)끊김→재연결 (2)토큰 만료→갱신 (3)갱신 실패→로그인 이동 3가지를 서버 협조 하에 확인 필요.

💬 리뷰 요구사항 (선택)

  • 이 PR은 서버 PR(paxi-popo-nest-api#154, accessTokenExpired 이벤트 emit)과 세트로 머지되어야 실효성이 있습니다. 서버 측 머지 일정과 맞춰주세요.
  • HANDOFF 앱푸시 #5 보안 항목(토큰 query string 제거)은 서버 핸드셰이크가 auth.token만으로 인증을 수용하는지 확인이 필요해 이번 범위에서 제외했습니다.

khkim6040 added 8 commits June 7, 2026 13:22
웹소켓 연결 시점에 액세스 토큰이 만료되면 서버가 accessTokenExpired
이벤트를 내려보낸다(paxi-popo-nest-api #154). 기존에는 이 이벤트의
핸들러가 TODO로 비어 있어, 토큰이 URL 쿼리에 박힌 채 자동 재연결이
만료된 토큰을 그대로 재사용하는 문제가 있었다.

- socket-factory: onAccessTokenExpired 콜백을 추가해 소켓 소유자에게
  재연결을 위임
- NewChatScreen: 이벤트 수신 시 기존 소켓 정리 → refreshAccessToken으로
  토큰 갱신 → 새 토큰으로 소켓 재생성. 중복 진입(isReauthingRef) 및
  갱신 반복 실패(reauthCountRef, 최대 2회) 가드 추가
- 정상 연결되면 갱신 카운터 초기화
NewChatScreen에 인라인돼 있던 토큰 만료 → 갱신 → 소켓 재생성
오케스트레이션을 socket-reauth.ts의 순수 함수로 추출한다. 의존성을
주입받게 해 컴포넌트 렌더 없이 단위 테스트할 수 있도록 한다. 동작은
동일하며, 중복 진입/반복 실패 가드를 ReauthGuard 객체 하나로 통합했다.
- socket-factory: accessTokenExpired 이벤트가 onAccessTokenExpired로
  위임되는지 등 이벤트 배선 검증 (기존 미커버 영역)
- socket-reauth: 정리→갱신→재생성 순서, 중복 호출 무시, maxAttempts
  도달 시 중단, 갱신/재생성 실패 시 가드 해제 검증
reconnectAttempt 변경에 의존하던 useEffect([reconnectAttempt]) 재조회는
'재연결 성공 시 0으로 리셋되는 부수효과'에 기대는 비직관적 구조였다.
재조회를 재연결 성공 콜백(onSocketConnected)에서 명시적으로 호출하도록
옮긴다. 최초 연결은 useFocusEffect 초기 조회와 중복되므로 hasConnectedOnceRef
가드로 건너뛴다. (HANDOFF #4)
"재연결 시도 N회"의 N은 disconnect/error 양쪽에서 증가하는 끊김 누적
수일 뿐 실제 재연결 시도 횟수가 아니었고, 숫자가 커질수록 사용자 불안만
유발했다. reconnectAttempt(number)를 hasDisconnected(boolean)로 바꾸고
안내를 "연결이 끊어졌습니다. 재연결 중…"으로 단순화한다. (HANDOFF #1/#3)
reconnectionDelay 5000 고정으로 장애 시 5초마다 무한 재시도하던 것을,
reconnectionDelayMax(30s)를 추가해 시도마다 지연이 늘어나도록 한다.
서버 부하를 줄이고 일시 장애 회복 여지를 준다. (HANDOFF #5)
position:absolute; top:60 하드코딩은 헤더 높이/노치 환경에서 위치가
어긋날 수 있었다. 헤더 바로 아래 일반 흐름에 배치해 환경에 무관하게
정렬되도록 한다. (HANDOFF #5)
npm run pc(pre-commit) 실행 시 Prettier가 정리한 줄바꿈 반영.
Copilot AI review requested due to automatic review settings June 7, 2026 04:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the resilience and UX of the Paxi chat socket connection in NewChatScreen by adding an explicit “token expired → refresh → recreate socket” flow, simplifying disconnect messaging, and making reconnection recovery (missed-data refetch) more explicit and testable.

Changes:

  • Add socket-reauth orchestration to refresh tokens and recreate the socket when accessTokenExpired is received, with guards against duplicate entry and infinite loops.
  • Update socketFactory to support an onAccessTokenExpired callback and enable exponential reconnection backoff (capped).
  • Refactor NewChatScreen reconnection UX/state and add Jest unit tests for the new socket factory/reauth behaviors.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/utils/socket-reauth.ts New token-refresh + socket recreation orchestrator with guard state and attempt cap.
src/utils/socket-factory.ts Adds accessTokenExpired delegation hook and reconnection backoff cap.
src/utils/__tests__/socket-reauth.test.ts Unit tests covering ordering, dedupe, caps, and failure paths for reauth orchestration.
src/utils/__tests__/socket-factory.test.ts Unit tests validating event wiring and callback delegation in the socket factory.
src/screens/paxi/NewChatScreen.tsx Integrates reauth flow and refactors disconnect UI/state + explicit refetch on reconnect.
src/constants/socket-events.ts Updates documentation for ACCESS_TOKEN_EXPIRED event usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 275 to 279
useCallback(() => {
// 포커스마다 새 소켓을 생성하므로 첫 connect를 최초 연결로 취급한다.
hasConnectedOnceRef.current = false;
getRoomInfo();
getMyInfo();
Comment on lines +51 to +54
if (guard.reauthCount >= maxAttempts) {
console.error('토큰 갱신 반복 실패 — 재연결 중단');
deps.releaseSocket();
return;
khkim6040 added 2 commits June 7, 2026 13:52
이전 세션의 socketConnected/hasDisconnected가 남아, 화면 재진입 시
실제로 끊긴 적 없어도 끊김 배너가 뜨거나 입력창이 잘못 활성화되는
문제를 해소한다. 포커스 진입 시 초기 connect 전까지 두 상태를 false로
초기화한다.
reconnectWithFreshToken이 maxAttempts 상한으로 조기 반환할 때
releaseSocket()만 호출했다. releaseSocket은 리스너 제거 후 disconnect하므로
disconnect 이벤트가 발생하지 않아 UI가 연결됨으로 남을 수 있다.
상한 도달 경로에서도 setSocketConnected(false)를 호출하도록 하고
해당 동작을 검증하는 단위 테스트를 추가한다.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment on lines +184 to +190
const onAccessTokenExpired = () =>
reconnectWithFreshToken(reauthGuardRef.current, {
releaseSocket: releaseCurrentSocket,
refreshAccessToken,
recreateSocket: initSocket,
setSocketConnected,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants