배경
입시 강의를 운영하는 파트너 교육 포털이 강의실로 MeetMate를 쓰기로 했습니다. 웹에는 포털 전용 기능(라이브 시험, 모의고사, 성적표, 1:1 라이브 대기열)이 이미 있었지만, 모바일 앱에는 하나도 없었습니다. 학생이 포털에서 강의를 누르면 앱에서는 로그인 화면부터 다시 시작해야 했습니다.
요구는 두 가지가 더 있었습니다. 강의 자료가 유출되지 않도록 휴대폰 녹화를 막아야 했고, 그러면서도 강사가 앱에서 화면을 공유하는 기능은 남겨야 했습니다. 문제는 기기 녹화와 앱 화면 공유가 OS에서는 같은 캡처 경로를 쓴다는 점이었습니다.
접근
먼저 진입 경로를 만들었습니다. 포털 딥링크는 엄격하게 검증해서, 우리 링크가 아니면 무시하고 내용이 어긋나면 실패 화면을 보여 줍니다. 외부 세션은 영속 저장소가 아니라 메모리에만 주입했습니다. 기존 일반 로그인은 그대로 두고, 세션이 끝나면 스냅샷으로 복원합니다. 외부 세션에서 401이 나도 일반 로그인을 지우지 않습니다. 콜드 스타트에서는 초기 URL을 앱 준비 조건에 포함했고, 이미 회의 중일 때 들어온 링크는 무시합니다.
시험·모의고사는 웹 계약을 그대로 옮기되, 규칙은 순수 함수로 분리했습니다. 시트 병합, 택1 과목, 제출 페이로드, 정오표 같은 규칙은 UI 없이 테스트할 수 있게 만들었습니다. 60초 임시 저장, 문항이 보일 때 시작해 백그라운드에서 멈추고 돌아오면 이어지는 개별 타이머, 만료 시 자동 제출은 서비스 계층에 두었습니다. 처음에는 종료 감시를 시트 컴포넌트에 넣었는데, 시트를 접은 채로 시간이 끝나자 타이머가 00:00:00에서 멈췄습니다. 그래서 감시를 서비스 수명으로 옮겼습니다.
성적표는 처음에 네이티브 표로 그렸습니다. 하지만 “웹과 같은 파일이어야 한다”는 결정에 따라 웹의 jsPDF 레이아웃을 손대지 않고 옮겼습니다. Hermes에 없는 atob/btoa를 폴리필로 채우고, 폰트는 한 번 내려받아 캐시합니다. 같은 실데이터로 바이트 동등성을 검증한 뒤 네이티브 표는 지웠습니다.
화면 녹화를 막는 기능과 화면 공유 기능은 OS 입장에서는 같은 행위입니다. 그래서 둘을 따로 설계하지 않고, 차단 여부를 한 곳에서 결정하도록 했습니다.
녹화 차단은 플랫폼마다 제공하는 수단이 달랐습니다. Android는 창에 FLAG_SECURE를 거는데, 플래그가 창 단위라서 회전처럼 액티비티가 다시 만들어지면 다시 걸어야 합니다. 이걸 빠뜨리면 화면을 한 번 돌리는 것만으로 구멍이 생깁니다. iOS에는 녹화를 막는 API가 없어서, 캡처를 감지하면 앱 전체를 가림막으로 덮습니다. 최종 차단 여부는 “정책상 차단이고 내가 공유 중이 아닐 때” 한 곳에서 계산하고, 공유를 시작하기 직전에 풀었다가 공유가 끝나거나 실패하면 다시 겁니다. 에뮬레이터 screencap으로 검은 픽셀 비율을 단계별로 측정했습니다. 일반 0%, 차단 99.7%, 내 공유 중 40.5%, 공유 종료 99.7%, 세션 종료 0%였습니다.
나머지 차이도 정리했습니다. 저장만 되고 실제 송출은 5fps에 고정돼 있던 화면 공유 화질 설정은 실제 송출 상한으로 연결했습니다. 강사·조교의 셀프뷰는 칠판 글씨가 뒤집히지 않도록 미러링에서 뺐고, 학생 화면의 강사 타일에는 워터마크를 넣었습니다. 포털 분기는 컴포넌트마다 흩어 두지 않고 정책 필드와 순수 함수 하나에 모았습니다. 1주 뒤 일반 회의에도 녹화 차단이 필요해졌을 때 정책 값을 on/off에서 범위(off, 회의 중, 항상)로 바꾸는 것만으로 확장할 수 있었습니다. 일반 회의는 로비 0%, 장치 확인 100%, 회의실 99.7%, 퇴장 후 로비 0%로 측정됐습니다.
결과
- 포털 딥링크로 로그인·로비 없이 장치 확인 화면에 바로 진입. 외부 세션과 일반 로그인을 분리(테스트 66건).
- 라이브 시험·설문·파일 첨부·모의고사 전용 화면·1:1 라이브 대기열을 네이티브로 이식하고 학생·강사 전 흐름을 실측.
- 성적표 PDF가 웹 생성기와 바이트 동등.
- 기기 녹화 차단 0% → 99.7% 검은 픽셀, 앱 화면 공유는 유지. 일반 회의로 확장.
- 화면 공유 화질 5/15/30fps 실제 반영, 셀프뷰 미러링 예외, 강사 타일 유출 추적 워터마크.
- 테스트 1,883 → 2,030건, tsc 신규 오류 0.
배운 점
같은 결과를 내야 할 때는 다시 만들지 말고 그대로 옮겨라. 성적표를 네이티브 표로 다시 그리는 동안 웹과 다른 부분은 계속 생겼고, 웹 생성기를 그대로 옮기고 나서야 “웹과 같다”를 바이트 비교 테스트로 증명할 수 있게 됐습니다.
또, 파트너 전용 요구를 컴포넌트 분기로 흩어 두지 않고 정책 필드 하나로 모아 둔 것이 일주일 뒤에 효과를 냈습니다. 일반 회의에도 녹화 차단이 필요해졌을 때, 기능을 새로 만들지 않고 정책 값의 범위를 넓히는 것만으로 끝났습니다.