미지원 기기/브라우저 접속 제한 안내 (PPI-1085)

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

미지원 기기/브라우저 접속 제한 안내 (PPI-1085): 증상: 이 문서는 이렇게 읽으면 됩니다, 원인: 흐름 ② — 런타임 판정 경로 (입장 클릭 → 매칭 → 모달), 수정·검증: 리뷰 관전 포인트 흐름
동작 흐름 요약
  1. 증상: 이 문서는 이렇게 읽으면 됩니다
  2. 원인: 흐름 ② — 런타임 판정 경로 (입장 클릭 → 매칭 → 모달)
  3. 수정·검증: 리뷰 관전 포인트
문서 읽는 법 · 변경 검토식

이 문서는 이렇게 읽으면 됩니다

미지원 기기/브라우저 접속 제한 안내 (PPI-1085) — 커밋 리뷰 (43623131)의 핵심을 변경 검토식으로 먼저 안내합니다. 기술적 결론과 원문 근거는 아래 본문에 보존되어 있습니다.

핵심 흐름 펼쳐 보기
  1. 변경 목적과 주요 흐름을 먼저 파악합니다.
  2. 핵심 diff와 영향 범위를 따라갑니다.
  3. 관전 포인트와 테스트로 위험을 점검합니다.
  • 무엇이, 왜 바뀌었나
  • 흐름 ① — 설정 저장 경로 (관리자 UI → PUT → DynamoDB → GET → 훅)
  • 흐름 ② — 런타임 판정 경로 (입장 클릭 → 매칭 → 모달)
43623131 모카 · 2026-06-29 Feature 9 files +584 −11

미지원 기기/브라우저로 수업에 입장하려는 아동에게 이동 전 확인 모달을 띄워, 보호자가 STT·오디오 불안정 가능성을 미리 인지하게 하는 기능. 기존 language-guard-settings 서브피처들과 완전히 동일한 draft/save 패턴을 재사용했고 enabled: false 기본값이라 즉시 영향은 없다. 부수적으로 parseBrowser의 죽은 코드(Samsung/Opera가 항상 Chrome으로 잡히던 순서 버그)를 고쳤는데, 이 함수는 진행자 모니터도 공유해서 써서 리뷰 시 가장 먼저 볼 지점은 이 유틸 수정이 이번 기능과 무관하게 기존 모니터 표시에도 영향을 준다는 것.

무엇이, 왜 바뀌었나

치료사가 매 수업마다 구두로 "이 기기/브라우저는 지원 안 됩니다"를 안내해야 했던 부담을 줄이기 위해, 게스트가 입장 버튼을 누르는 순간 서버에 저장된 규칙과 매칭되면 세션 이동 전에 확인 모달을 띄운다.

흐름 ① — 설정 저장 경로 (관리자 UI → PUT → DynamoDB → GET → 훅)

1

메시지 탭 — ON/OFF · 규칙 · 모델 키워드 편집

components/sections/access-restriction-section.tsx

2

draft 버퍼만 변경 (저장 전 미반영)

setDraftAccessRestrictionEnabled/Rules/DeviceModels

3

PUT — 방어적 파싱 후 머지 저장

PUT /api/language-guard-settings

4

DynamoDB — 로드 시 DEFAULTS 머지

lib/db-queries.ts · getLanguageGuardSettings()

5

게스트 최초 로드 시 자동 반영

hydrateFromServer() (기존 module-level 훅 재사용)

흐름 ② — 런타임 판정 경로 (입장 클릭 → 매칭 → 모달)

1

게스트 기기정보 계산

app/client-guest/page.tsx · parseUserAgent + getDeviceModel()

2

매칭 판정

accessMatchKinddeviceModels 부분일치 / rules OS·browser 일치

3

입장 클릭 시 네비게이션 가로채기

navigateToSession · isAccessRestricted && !compatConfirmedRef.current

4

확인 모달 — 문구 3분기(device/browser/both)

components/ui/access-restriction-confirm-modal.tsx

5

"확인하고 입장" → 재호출(이번엔 통과)

compatConfirmedRef.current = true

코드로 보는 핵심 지점

parseBrowser 순서 교정 — Samsung/Opera가 죽은 코드였다

lib/user-agent-utils.ts const samsungMatch = userAgent.match(/SamsungBrowser\/([\d.]+)/); if (samsungMatch) return `Samsung Internet ${samsungMatch[1]}`; const operaMatch = userAgent.match(/OPR\/([\d.]+)/); if (operaMatch) return `Opera ${operaMatch[1]}`; // ↑ 이 두 체크가 기존엔 Chrome 판별 뒤에 있어서 도달 불가능했음 - // (기존 위치: 아래 Chrome 블록 이후) const chromeMatch = userAgent.match(/Chrome\/([\d.]+)/); if (chromeMatch && !userAgent.includes("Edg")) return `Chrome ${chromeMatch[1]}`;

Chrome 정규식은 !userAgent.includes("Edg")만 걸러내고 SamsungBrowser/OPR 토큰은 걸러내지 않아, 이전 순서에서는 삼성 인터넷·오페라가 항상 "Chrome"으로 오인식됐다. 이 함수는 진행자 모니터의 기기 표시도 공유해서 쓴다.

세션 재확인 안 함 — 사실은 "컴포넌트 생존 기간" 단위

app/client-guest/page.tsx const compatConfirmedRef = useRef(false); // ... if (isAccessRestricted && !compatConfirmedRef.current) { setPendingCompatNav({ roomId, lessonIndex, activityIndex, stepIndex }); setShowCompatModal(true); return; }

sessionStorage가 아니라 컴포넌트 내부 useRef라, 새로고침이나 재마운트가 일어나면 다시 모달이 뜬다. PR 설명의 "같은 세션에서 재확인 안 함"과 정확히는 다르지만 안전한 방향의 차이라 실질적 문제는 아니다.

레이어별 변경 요약

레이어파일핵심 변경
타입types/db/language-guard-settings.types.tsAccessRestrictionRule/Settings, ACCESS_RESTRICTION_ANY_OS="*", DEFAULTS(enabled:false)
유틸lib/user-agent-utils.tsparseBrowser 순서 교정, OS/BROWSER 옵션 상수, getBrowserName()
스토어stores/use-language-guard-settings-store.tsaccessRestriction 적용값 + draft 3종 액션, hydrate/payloadEquals 반영
APIapp/api/language-guard-settings/route.tsPUT에 accessRestriction 방어적 파싱 추가
DBlib/db-queries.tsgetLanguageGuardSettings() 로드 시 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+브라우저 rulesparseOperatingSystem이 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 인앱 브라우저 대응 — 이번 기능이 대응하려는 "미지원 브라우저 환경" 문제의 선행 사례.