Meet/룸 + 호스트 세션 라이프사이클 + 입장 승인 — 코드레벨 동작 흐름 P1코드레벨

마지막 업데이트 2026-07-22

작성일: 2026-06-14 대상: 개발자 — 수업 진입/세션 기록 흐름 파악 핵심 파일: socketSfu.ts, use-host-entry-socket.ts, lib/api/services/lesson-session.service.ts

개요 · 범위

한 수업이 시작되어 아동이 입장하고 종료되기까지의 오케스트레이션. 입장 가능 검증(can-enter) → SFU 소켓 연결(인스턴스 고정) → 입장 승인(리소스 캐싱) → LessonSession 레코드 생성·종료(누적시간 기반). 실제 AI 세션 제어는 세션 릴레이(#6).

roomId = {userId}_{lessonIndex}. LessonSession은 "한 번의 접속"을 나타내는 DynamoDB 레코드로, 재접속마다 새로 생기고 종료 시 duration이 누적된다 → 모니터 누적시간(#13)의 원천 데이터.

전체 흐름

[호스트] /main/meet/[roomId] 진입 → useHostEntrySocket [아동] can-enter 검증(GET /api/lessons/:userId/:index/can-enter) → SFU 소켓 JOIN_ROOM (room pin #5, targetInstance) → emit guest-request-entry [호스트] guest-request-entry 수신 → 리소스 캐싱 → 승인 [아동] 입장 → LessonSession.createSession (enteredAt 기록) → 세션 릴레이(#6)로 핑퐁이 세션 진행 [종료] LessonSession.endSession (exitedAt·duration 누적) disconnect 시 backstop: endPendingSessions

입장 가능 검증 (can-enter) api/lessons/[userId]/[index]/can-enter/route.ts

GET /api/lessons/:userId/:index/can-entercanEnterLesson(userId, lessonIndex). 수업 스케줄/상태로 입장 가능 여부를 게이트한다(시간·중복 등). 게스트 입장 플로우(#25)의 진입 조건.

SFU 소켓 연결 (인스턴스 고정) socketSfu.ts

  • targetInstance: JOIN_ROOM 성공 ack의 instanceIdsetTargetInstance()로 저장 → 자동 재연결 시 auth: cb({ targetInstance })같은 인스턴스에 복귀(room pin #5와 짝). 다른 인스턴스면 서버가 WRONG_INSTANCE redirect.
  • 재연결: reconnection 5회, delay 3s~10s. reconnect 시 앱이 re-join/re-auth 처리.
  • 장기 단절 복구: reconnectionAttempts 소진 후 socket.io가 포기 → navigator online 이벤트로 수동 재연결(단, reconnection(false)로 영구 차단된 소켓은 제외).

입장 승인 use-host-entry-socket.ts

  1. 1아동이 guest-request-entry emit → 호스트 handleRequestpendingGuest 설정(UI에 승인 대기 표시).
  2. 2승인 전 리소스 프리캐싱: 이 수업에 필요한 리소스(resourceFileNames)를 cacheResources로 IndexedDB에 미리 받음(cachingProgress로 진척 표시). 다운로드 실패 시 입장 차단(#22 연계).
  3. 3승인 → 아동 입장. (강제 퇴장 등은 onForceKick)

서버측 승인 이벤트 중계는 SFU room-handlers(#2: GUEST_REQUEST_ENTRY → HOST_APPROVE_ENTRY2 / host-deny-entry).

LessonSession 라이프사이클 lib/api/services/lesson-session.service.ts

데이터 모델 lesson-session.types.ts

LessonSession {
  userId(PK), sessionId(SK = `${lessonIndex}#${enteredAt}`),
  lessonIndex, roomId,
  enteredAt, exitedAt?, duration?,          // 접속 시간
  start/endActivityIndex, start/endStepIndex,  // 진행 위치 스냅샷
  disconnectReason?  // socket-disconnect | auto-closed-on-new-session | test-inject
}
메서드동작
createSession새 접속 레코드 생성. 먼저 미종료(pending) 세션을 endPendingSessions로 정리(중복 라이브 방지) 후 생성
endSessionexitedAt 기록 + duration 계산·totalDuration 누적
endPendingSessions미종료 세션 일괄 종료(backstop). disconnect/재접속 시 호출(#2 connection-handlers)
getSessions/getSession조회
누적시간 정합의 핵심: duration 누적은 endSession이 단독 담당. createSession의 auto-close나 disconnect backstop이 중복 종료하면 이중 가산 위험(→ 누적시간 이중 가산 분석). disconnect 시각 기준으로 clamp.

종료 · disconnect backstop

  • 정상 종료: 수업 종료/퇴장 → endSession.
  • disconnect backstop: SFU 서버(#2)가 게스트 disconnect 시 grace 후 endPendingSessions(disconnectReason="socket-disconnect") 예약. grace 내 재접속이면 취소하되 이전 세션은 disconnect 시각으로 종료(idle gap 누적 방지, PPI-900).
  • 재접속 grace 만료: LESSON_SESSION_EXPIRED → 클라이언트가 새 세션 생성.

함정 · 주의

  • 이중 종료 = 이중 가산: createSession auto-close / disconnect backstop / 정상 endSession이 같은 세션을 중복 종료하면 duration 이중 가산. 종료 경로 추가 시 idempotency·clamp 확인.
  • targetInstance 필수: JOIN_ROOM ack의 instanceId 저장 누락 시 재연결마다 다른 인스턴스로 가 WRONG_INSTANCE redirect 폭주(room pin #5).
  • 리소스 캐싱 게이트: 승인 전 리소스 다운로드 실패 시 입장 차단. 프리캐싱 누락 시 오프라인/지연 상황에서 활동 영상이 안 뜸.
  • sessionId는 enteredAt 포함: {lessonIndex}#{enteredAt}라 같은 수업 재접속마다 새 SK. 조회/집계 시 lessonIndex prefix로 묶어야 함.
  • 장기 단절 복구 분기: reconnection(false)로 영구 차단한 소켓(resourceLoadFailed 등)은 online 이벤트로도 되살리면 안 됨.

파일 · 라인 레퍼런스

파일역할
app/main/meet/[roomId]/page.tsx호스트 수업 진행 페이지
socketSfu.tsSFU 소켓·targetInstance·재연결
hooks/use-host-entry-socket.ts입장 요청 수신·리소스 프리캐싱·승인
lib/api/services/lesson-session.service.tsLessonSession create/end/endPending
types/db/lesson-session.types.tsLessonSession 모델
api/lessons/[userId]/[index]/{can-enter,sessions}/route.ts입장 검증·세션 API