102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
/*
|
|
* 민감화면 보호 컨텍스트 — B4(화면캡처 차단) + B7(백그라운드 스냅샷 마스킹) 통합.
|
|
* 근거: docs/security/mobile-security-audit.md 백로그 B4·B7, checklist DP-4·DP-5.
|
|
*
|
|
* 동작:
|
|
* · 민감화면이 focus되면 useSecureScreen 훅이 등록(activeCount++) + preventCapture.
|
|
* · 앱이 background/inactive(태스크 스위처·홈 전환)로 가고 activeCount>0이면
|
|
* 루트에 브랜드 오버레이를 덮어 스냅샷에 민감 정보가 남지 않게 마스킹.
|
|
* · 이탈/포그라운드 복귀 시 자동 해제.
|
|
* 방어적: 캡처 모듈 부재여도 오버레이(B7)는 순수 RN(AppState)로 동작.
|
|
*/
|
|
import { useFocusEffect } from 'expo-router';
|
|
import React, {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from 'react';
|
|
import { AppState, type AppStateStatus, StyleSheet, Text, View } from 'react-native';
|
|
import { allowCapture, preventCapture } from '../lib/screenCapture';
|
|
import { colors, spacing, type } from '../theme';
|
|
|
|
interface SecureScreenContextValue {
|
|
register: () => void;
|
|
unregister: () => void;
|
|
}
|
|
|
|
const SecureScreenContext = createContext<SecureScreenContextValue | undefined>(undefined);
|
|
|
|
export function SecureScreenProvider({ children }: { children: React.ReactNode }) {
|
|
const [activeCount, setActiveCount] = useState(0);
|
|
const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState);
|
|
|
|
const register = useCallback(() => setActiveCount((n) => n + 1), []);
|
|
const unregister = useCallback(() => setActiveCount((n) => Math.max(0, n - 1)), []);
|
|
|
|
useEffect(() => {
|
|
const sub = AppState.addEventListener('change', (next) => setAppState(next));
|
|
return () => sub.remove();
|
|
}, []);
|
|
|
|
const value = useMemo<SecureScreenContextValue>(() => ({ register, unregister }), [register, unregister]);
|
|
|
|
// 민감화면 활성 + 비활성 상태(태스크 스위처/백그라운드) → 마스킹.
|
|
const masked = activeCount > 0 && appState !== 'active';
|
|
|
|
return (
|
|
<SecureScreenContext.Provider value={value}>
|
|
{children}
|
|
{masked ? (
|
|
<View style={styles.mask} pointerEvents="none" accessibilityElementsHidden>
|
|
<Text style={styles.markTop}>KINTEX</Text>
|
|
<Text style={styles.markSub}>AI 전시·행사시스템</Text>
|
|
</View>
|
|
) : null}
|
|
</SecureScreenContext.Provider>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 민감화면 보호 훅 — 화면 컴포넌트 본문에서 1회 호출.
|
|
* @param key 캡처 스택 구분용 고유 키(화면별 상수).
|
|
* focus 시 캡처 차단 + 백그라운드 마스킹 등록, blur 시 해제.
|
|
*/
|
|
export function useSecureScreen(key: string): void {
|
|
const ctx = useContext(SecureScreenContext);
|
|
const registered = useRef(false);
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
preventCapture(key);
|
|
if (ctx && !registered.current) {
|
|
ctx.register();
|
|
registered.current = true;
|
|
}
|
|
return () => {
|
|
allowCapture(key);
|
|
if (ctx && registered.current) {
|
|
ctx.unregister();
|
|
registered.current = false;
|
|
}
|
|
};
|
|
// ctx는 프로바이더 수명 내 안정적 — key 변경 시에만 재바인딩
|
|
}, [ctx, key]),
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
mask: {
|
|
...StyleSheet.absoluteFillObject,
|
|
backgroundColor: colors.primary700,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: spacing.xs,
|
|
},
|
|
markTop: { color: colors.white, fontSize: 30, fontWeight: '800', letterSpacing: 2 },
|
|
markSub: { color: 'rgba(255,255,255,0.8)', fontSize: type.body.fontSize, fontWeight: '600' },
|
|
});
|