guardia-messenger/hooks/useScreenshotBlock.ts

74 lines
2.5 KiB
TypeScript

/**
* #35 민감 화면 스크린샷 차단 훅
*
* 완전 차단(Android FLAG_SECURE / iOS)은 네이티브 모듈(expo-screen-capture)이 필요하다.
* 해당 모듈이 설치되어 있으면 동적으로 활성화하고, 없으면(EAS 안전 모드)
* 오버레이 가림(SecureOverlay)으로 대체할 수 있도록 상태를 반환한다.
*
* 추가 네이티브 의존성 없이 동작 — package.json/app.json 변경 불필요.
*/
import { useEffect, useRef, useState } from 'react'
import { AppState, AppStateStatus } from 'react-native'
interface ScreenshotBlockResult {
/** 앱이 비활성(백그라운드/전환 중) 상태일 때 true → 민감 내용을 오버레이로 가린다 */
shouldObscure: boolean
/** 네이티브 FLAG_SECURE가 실제 적용되었는지 */
nativeSecured: boolean
}
let cachedModule: any | undefined // undefined: 미탐지, null: 없음, obj: 모듈
function loadScreenCaptureModule(): any | null {
if (cachedModule !== undefined) return cachedModule
try {
// 설치되어 있을 때만 로드 (미설치 시 require가 throw → catch)
// eslint-disable-next-line @typescript-eslint/no-var-requires
cachedModule = require('expo-screen-capture')
} catch {
cachedModule = null
}
return cachedModule
}
export function useScreenshotBlock(enabled: boolean): ScreenshotBlockResult {
const [shouldObscure, setShouldObscure] = useState(false)
const [nativeSecured, setNativeSecured] = useState(false)
const tagRef = useRef<string>(`secure-${Math.random().toString(36).slice(2)}`)
// 네이티브 FLAG_SECURE 시도
useEffect(() => {
if (!enabled) return
const mod = loadScreenCaptureModule()
let active = true
if (mod?.preventScreenCaptureAsync) {
mod
.preventScreenCaptureAsync(tagRef.current)
.then(() => active && setNativeSecured(true))
.catch(() => active && setNativeSecured(false))
}
return () => {
active = false
if (mod?.allowScreenCaptureAsync) {
mod.allowScreenCaptureAsync(tagRef.current).catch(() => {})
}
setNativeSecured(false)
}
}, [enabled])
// 오버레이 가림: 앱이 inactive/background 일 때 (앱 스위처 미리보기 차단)
useEffect(() => {
if (!enabled) {
setShouldObscure(false)
return
}
const onChange = (state: AppStateStatus) => {
setShouldObscure(state !== 'active')
}
const sub = AppState.addEventListener('change', onChange)
return () => sub.remove()
}, [enabled])
return { shouldObscure, nativeSecured }
}