마지막 업데이트 2026-07-22
미지원 기기/브라우저로 수업에 입장하려는 아동에게 이동 전 확인 모달을 띄워, 보호자가 STT·오디오 불안정
가능성을 미리 인지하게 하는 기능. 기존 language-guard-settings 서브피처들과 완전히 동일한
draft/save 패턴을 재사용했고 enabled: false 기본값이라 즉시 영향은 없다. 부수적으로
parseBrowser의 죽은 코드(Samsung/Opera가 항상 Chrome으로 잡히던 순서 버그)를 고쳤는데, 이
함수는 진행자 모니터도 공유해서 써서 리뷰 시 가장 먼저 볼 지점은 이 유틸 수정이 이번 기능과
무관하게 기존 모니터 표시에도 영향을 준다는 것.
치료사가 매 수업마다 구두로 "이 기기/브라우저는 지원 안 됩니다"를 안내해야 했던 부담을 줄이기 위해, 게스트가 입장 버튼을 누르는 순간 서버에 저장된 규칙과 매칭되면 세션 이동 전에 확인 모달을 띄운다.
ACCESS_RESTRICTION_DEFAULTS = {enabled: false, rules: [], deviceModels: []} —
관리자가 규칙을 켜기 전까지 기존 세션에 영향 없음language-guard-settings 서브피처와 동일하게
draft → PUT → DynamoDB → hydrateFromServer 패턴 재사용, 새 구독 메커니즘을
따로 만들지 않음parseBrowser(lib/user-agent-utils.ts) 순서 교정은 진행자
모니터의 getDeviceDisplayString 등이 그대로 호출해서 쓰므로 자동으로 함께 반영됨메시지 탭 — ON/OFF · 규칙 · 모델 키워드 편집
components/sections/access-restriction-section.tsx
draft 버퍼만 변경 (저장 전 미반영)
setDraftAccessRestrictionEnabled/Rules/DeviceModels
PUT — 방어적 파싱 후 머지 저장
PUT /api/language-guard-settings
DynamoDB — 로드 시 DEFAULTS 머지
lib/db-queries.ts · getLanguageGuardSettings()
게스트 최초 로드 시 자동 반영
hydrateFromServer() (기존 module-level 훅 재사용)
게스트 기기정보 계산
app/client-guest/page.tsx · parseUserAgent + getDeviceModel()
매칭 판정
accessMatchKind — deviceModels 부분일치 / rules OS·browser 일치
입장 클릭 시 네비게이션 가로채기
navigateToSession · isAccessRestricted && !compatConfirmedRef.current
확인 모달 — 문구 3분기(device/browser/both)
components/ui/access-restriction-confirm-modal.tsx
"확인하고 입장" → 재호출(이번엔 통과)
compatConfirmedRef.current = true
Chrome 정규식은 !userAgent.includes("Edg")만 걸러내고 SamsungBrowser/OPR
토큰은 걸러내지 않아, 이전 순서에서는 삼성 인터넷·오페라가 항상 "Chrome"으로 오인식됐다. 이 함수는
진행자 모니터의 기기 표시도 공유해서 쓴다.
sessionStorage가 아니라 컴포넌트 내부 useRef라, 새로고침이나 재마운트가
일어나면 다시 모달이 뜬다. PR 설명의 "같은 세션에서 재확인 안 함"과 정확히는 다르지만 안전한 방향의
차이라 실질적 문제는 아니다.
| 레이어 | 파일 | 핵심 변경 |
|---|---|---|
| 타입 | types/db/language-guard-settings.types.ts | AccessRestrictionRule/Settings, ACCESS_RESTRICTION_ANY_OS="*", DEFAULTS(enabled:false) |
| 유틸 | lib/user-agent-utils.ts | parseBrowser 순서 교정, OS/BROWSER 옵션 상수, getBrowserName() |
| 스토어 | stores/use-language-guard-settings-store.ts | accessRestriction 적용값 + draft 3종 액션, hydrate/payloadEquals 반영 |
| API | app/api/language-guard-settings/route.ts | PUT에 accessRestriction 방어적 파싱 추가 |
| DB | lib/db-queries.ts | getLanguageGuardSettings() 로드 시 DEFAULTS 머지 |
| UI(관리) | components/sections/access-restriction-section.tsx (신규) | ON/OFF, OS+브라우저 규칙, 기기 모델 키워드 편집 |
| UI(게스트) | components/ui/access-restriction-confirm-modal.tsx (신규) | device/browser/both 3분기 문구 + 카카오톡 안내 |
| 라우팅 | app/client-guest/page.tsx | 매칭 판정 accessMatchKind, 네비게이션 가로채기, compatConfirmedRef |
| 등록 | components/pages/chat-preset.tsx | 메시지 탭에 섹션 배치(5줄) |
parseBrowser 재정렬은 session-header.tsx·
widgets/monitor/ui/system-status-panel.tsx·host.tsx가
공유 호출하는 getDeviceDisplayString을 거쳐 자동 반영된다. PR 설명의 "모니터 표시도 같이
교정됨"은 코드상 사실 — 별도 배선 없이 부수적으로 기존 화면이 바뀐다는 뜻이니 배포 후 모니터 표시가
달라진 걸 보고 놀라지 않도록 공유해둘 것.
compatConfirmedRef는 인메모리 useRef라 새로고침/재마운트 시 초기화된다.
"같은 세션 재확인 안 함"이라는 문서상 표현과 달리 실제로는 "컴포넌트 인스턴스 생존 동안만" — 안전한
방향의 차이지만 의도와 구현이 정확히 일치하진 않는다.
작성자가 우려한 "iOS 모델 식별 불가"는 deviceModels 룰만 무력화되고, OS+브라우저
rules는 parseOperatingSystem이 iOS/iPadOS를 정상 판별하므로 영향받지 않는다.
실제 갭이 아니라 이미 설계로 커버된 케이스.
browser 규칙엔 와일드카드가 없다(ACCESS_RESTRICTION_ANY_OS는 os 필드
전용). "iOS는 브라우저 무관 전부 제한" 같은 규칙을 만들려면 브라우저 옵션을 하나씩 다 추가해야 해서
관리 UX상 번거로울 수 있음.
API PUT 레벨에서 rules/deviceModels 중복 제거를 하지 않는다(대소문자만
다른 키워드, 동일 os+browser 조합 등). 클라이언트 UI는 대소문자 무시 중복 체크를 하지만 API를 직접
호출하면 우회 가능 — 매칭 로직 자체(.some(...))는 중복이 있어도 정확성엔 영향 없어 실질
리스크는 낮음.
관련 문서: 언어 가드 설정 스토어(draft/save) — 코드레벨 동작 흐름
— 이 PR의 설정 저장 경로(흐름①)가 그대로 재사용한 draft/save/hydrateFromServer 패턴의 원본 문서.
호스트 모니터링 claim / 그룹 모니터링 — 코드레벨 동작 흐름
— parseBrowser 교정이 부수적으로 반영되는 진행자 모니터 기기표시 로직 문서.
영상 종료 이벤트 미발생 분석 & NAVER 인앱 브라우저 대응
— 이번 기능이 대응하려는 "미지원 브라우저 환경" 문제의 선행 사례.