같은 진행자 계정의 다중 탭에서 Waiting for guest 고착 수정 완료 PPI-1311
마지막 업데이트 2026-09-18
결론
같은 진행자 계정으로 대시보드 탭과 룸 모니터 탭을 동시에 열면, 계정 ID 하나를 키로 사용하던 서버의 모니터 presence가 두 번째 소켓으로 덮어써졌다. 룸 모니터는 다음 45초 갱신에서 현재 소켓으로 인정되지 않아 권한과 룸 claim을 잃었고, 게스트 입장 뒤 상태 요청은 unauthorized가 됐다. 웹 화면은 이 응답을 대기 상태와 구분하지 않아 Waiting for guest가 계속 표시됐다.
수정 후: 외부
peerId는 계정 ID를 유지하면서 내부 Map·Redis 저장 키를 monitor-presence:{accountId}:{socketId}로 분리한다. 두 탭의 presence가 독립적으로 갱신되고, 권한 상실 시 클라이언트에는 별도 이벤트와 연결 끊김 안내가 전달된다.증상과 실행 경로
- 룸 모니터가
MONITOR_CONNECTED로 presence를 등록한다. - 같은 계정의 대시보드가 등록되며 기존
peers[principal.sub].socketId가 대시보드 소켓으로 바뀐다. - 룸 claim의 갱신 tick(기본 45초)이
getTrustedMonitorPresence검사를 실패시킨다. revokeLocalMonitoringAuthority가 claim을 제거하지만, 수정 전에는 클라이언트 통보가 없었다.- 게스트 입장 후
host-request-room-state가 unauthorized로 거절되고 화면은 기존 대기 문구에 남는다.
룸 모니터 socket A 등록 → peers[accountId] = socket A 대시보드 socket B 등록 → peers[accountId] = socket B // 수정 전 덮어쓰기 45초 갱신 → socket A !== socket B → 룸 claim 회수 게스트 입장 → 상태 요청 unauthorized → Waiting for guest 고착
코드 근거와 변경
| 영역 | 구현 경로 | 변경 |
|---|---|---|
| Presence 저장 | apps/socket/src/sfu-socket/handlers/connection-handlers.tsMONITOR_CONNECTED, MONITOR_DISCONNECTED | storageKey를 계정·소켓 단위로 생성하고 현재 소켓의 presence만 등록·삭제 |
| 피어 저장소 | apps/socket/src/sfu-socket/peer-ops.tsapps/socket/src/redis/peer-store.ts | 외부 peerId와 내부 Map·Redis 키를 분리해 저장·삭제 경로를 일치시킴 |
| 권한 검사 | apps/socket/src/sfu-socket/handlers/monitoring-handlers.tsgetTrustedMonitorPresence | 계정 단일 조회 대신 계정·현재 socketId·presence 종류를 함께 확인 |
| 권한 상실 통보 | MONITOR_AUTHORITY_REVOKED | 갱신 실패 사유와 영향 룸을 서버 로그·소켓 이벤트로 전달 |
| 화면 상태 | apps/web/entities/host-socket/model/use-host-socket.tsapps/web/shared/ui/monitor-media-display.tsx | isMonitorAuthorityLost를 통해 대기 화면과 연결 상실 화면을 분리 |
Before / After
Before
같은 계정의 두 번째 소켓이 첫 번째 presence의
같은 계정의 두 번째 소켓이 첫 번째 presence의
socketId를 덮어쓴다. 룸 모니터는 실제로 연결돼 있어도 서버 권한을 잃고, 클라이언트는 unauthorized를 대기 상태로 표시한다.After
각 소켓이 독립 presence를 갖는다. 룸 claim의 소유권은 기존처럼 룸 단위로 유지되며, 권한 갱신 실패가 발생하면 연결 상실 상태를 사용자에게 보여준다.
각 소켓이 독립 presence를 갖는다. 룸 claim의 소유권은 기존처럼 룸 단위로 유지되며, 권한 갱신 실패가 발생하면 연결 상실 상태를 사용자에게 보여준다.
중요한 결정: 외부 메시지의 계정 기반
peerId 계약은 유지하고, 저장소 식별자만 분리했다. 이로써 호스트 목록과 기존 모니터 claim 식별자는 불필요하게 변경하지 않았다.검증
pnpm --dir packages/shared build— 성공pnpm --dir apps/socket exec vitest run test/monitor-principal-auth.test.ts test/monitoring-handlers.voice-control-auth.test.ts --reporter=dot— 2개 파일, 60개 테스트 통과pnpm --dir apps/socket exec tsc --noEmit --pretty false— 성공pnpm --dir apps/web build-storybook— 성공- 회귀 테스트: 동일 계정의 룸 모니터·대시보드 소켓 등록 후 두 presence와 두
socketId가 모두 유지됨
운영 메모와 잔여 위험
- 실제 브라우저에서 두 탭을 동시에 열고 45초 이상 유지하는 운영 재현은 별도 확인이 필요하다.
- 소켓 단절, 인증 만료, Redis 장애는 이번 수정의 대상이 아니며 별도 실패 경로다.
- 배포 중 기존 Redis에 남은 구형 계정 키 presence는 TTL·reaper 정리 상태를 확인해야 한다.