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

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

Meet/룸 + 호스트 세션 라이프사이클 + 입장 승인 — 코드레벨 동작 …: 입력: 개요 · 범위, 주요 처리 단계: 전체 흐름, 결과: 입장 가능 검증 (can-enter) api/lessons/[userId]… 흐름
동작 흐름 요약
  1. 입력: 개요 · 범위
  2. 주요 처리 단계: 전체 흐름
  3. 결과: 입장 가능 검증 (can-enter) api/lessons/[userId]…
작성일: 2026-06-14 대상: 개발자 — 수업 진입/세션 기록 흐름 파악 핵심 파일: socketSfu.ts, use-host-entry-socket.ts, lib/api/services/lesson-session.service.ts
💬 대화로 먼저 이해하기 — "학원 등원·출석부 비유" (비개발자·처음 읽는 사람용)
Q아동이 수업 방에 들어가기까지 어떤 과정을 거치나요?
A학원 등원과 비슷해요. 먼저 접수처에서 "지금 이 수업에 들어가도 되나요?"를 확인받고(can-enter), 배정된 분관 건물(SFU 인스턴스)로 가서, 교실 문 앞에서 선생님(호스트)에게 노크해요(guest-request-entry).
Q선생님이 노크를 들으면 바로 문을 열어 주나요?
A아니요, 문을 열기 전에 오늘 수업에 쓸 교재·영상을 교실에 미리 갖다 둬요(리소스 프리캐싱, IndexedDB). 준비가 실패하면 입장 자체를 막아요 — 수업 도중 자료가 안 뜨는 사고를 막는 게이트예요.
Q출석부(LessonSession)에는 뭐가 적히나요?
A접속 한 번 = 출석부 한 줄이에요. 입장 시각(enteredAt)을 적고, 나갈 때 퇴장 시각과 머문 시간(duration)을 적어 누적해요. 재접속하면 새 줄이 생기고, 연결이 끊기면 잠깐 기다렸다가(grace) 대신 마감해 주는 안전장치(backstop)도 있어요.
Q수정할 때 조심할 곳은요?
A같은 줄을 두 번 마감하면 시간이 이중으로 더해져요(누적시간 이중 가산). 그리고 재방문 때 다른 분관으로 가면 되돌려보내지니(WRONG_INSTANCE) targetInstance 저장이 필수예요. 하단 "함정 · 주의"와 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