react-native-security-checker
Version:
A comprehensive React Native security checker that detects jailbreak, root, emulators, hooks, tampering, and other security threats
234 lines (206 loc) • 6.98 kB
text/typescript
import { useEffect, useRef, useCallback, useState } from 'react';
import { AppState, type AppStateStatus } from 'react-native';
import {
detectEnvironment,
type SecurityCheckConfig,
type SecurityCheckResult,
} from './index';
export interface UseSecurityCheckerOptions {
/** Configuration for security checks */
config?: SecurityCheckConfig;
/** Interval in milliseconds for periodic checks (default: 5000) */
interval?: number;
/** Whether to check when app goes to background (default: true) */
checkOnBackground?: boolean;
/** Whether to check when app comes to foreground (default: true) */
checkOnForeground?: boolean;
/** Whether to start monitoring immediately (default: true) */
startImmediately?: boolean;
/** Whether to enable continuous monitoring (default: true) */
enableContinuousMonitoring?: boolean;
}
export interface SecurityCheckerState {
/** Current security check result */
result: SecurityCheckResult | null;
/** Whether a security check is currently running */
isChecking: boolean;
/** Last error that occurred during checking */
lastError: Error | null;
/** Whether monitoring is currently active */
isMonitoring: boolean;
/** Number of checks performed */
checkCount: number;
/** Timestamp of last check */
lastCheckTime: number | null;
}
export interface UseSecurityCheckerReturn {
/** Current state of the security checker */
state: SecurityCheckerState;
/** Manually trigger a security check */
checkSecurity: () => Promise<void>;
/** Start monitoring */
startMonitoring: () => void;
/** Stop monitoring */
stopMonitoring: () => void;
/** Reset the checker state */
reset: () => void;
}
/**
* Custom hook for continuous security monitoring
*
* @param onSecurityIssue - Callback function called when security issues are detected
* @param interval - Interval in milliseconds for periodic checks (default: 5000)
* @param options - Additional configuration options
* @returns Security checker state and control functions
*/
export function useSecurityChecker(
onSecurityIssue: (result: SecurityCheckResult) => void,
interval: number = 5000,
options: UseSecurityCheckerOptions = {}
): UseSecurityCheckerReturn {
const {
config,
checkOnBackground = true,
checkOnForeground = true,
startImmediately = true,
enableContinuousMonitoring = true,
} = options;
// State
const [state, setState] = useState<SecurityCheckerState>({
result: null,
isChecking: false,
lastError: null,
isMonitoring: false,
checkCount: 0,
lastCheckTime: null,
});
// Refs for cleanup
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const appStateRef = useRef<AppStateStatus | null>(
AppState.currentState as AppStateStatus | null
);
const isCheckingRef = useRef<boolean>(false);
const isMonitoringRef = useRef<boolean>(false);
const startMonitoringRef = useRef<() => void>(() => {});
const stopMonitoringRef = useRef<() => void>(() => {});
const handleAppStateChangeRef = useRef<
(nextAppState: AppStateStatus) => void
>(() => {});
// Security check function
const checkSecurity = useCallback(async () => {
if (isCheckingRef.current) return; // Prevent multiple simultaneous checks
isCheckingRef.current = true;
setState((prev) => ({ ...prev, isChecking: true, lastError: null }));
try {
const result = await detectEnvironment(config);
setState((prev) => ({
...prev,
result,
isChecking: false,
checkCount: prev.checkCount + 1,
lastCheckTime: Date.now(),
}));
// Call callback if security issues are detected
if (result.isInsecureEnvironment) {
onSecurityIssue(result);
}
} catch (error) {
const errorObj =
error instanceof Error ? error : new Error(String(error));
setState((prev) => ({
...prev,
isChecking: false,
lastError: errorObj,
}));
} finally {
isCheckingRef.current = false;
}
}, [config, onSecurityIssue]);
// Start monitoring
const startMonitoring = useCallback(() => {
if (isMonitoringRef.current) return; // Prevent multiple starts
isMonitoringRef.current = true;
setState((prev) => ({ ...prev, isMonitoring: true }));
// Start interval if continuous monitoring is enabled
if (enableContinuousMonitoring && interval > 0) {
intervalRef.current = setInterval(checkSecurity, interval);
}
}, [enableContinuousMonitoring, interval, checkSecurity]);
// Store the latest function reference
startMonitoringRef.current = startMonitoring;
// Stop monitoring
const stopMonitoring = useCallback(() => {
isMonitoringRef.current = false;
setState((prev) => ({ ...prev, isMonitoring: false }));
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, []);
// Store the latest function reference
stopMonitoringRef.current = stopMonitoring;
// Reset state
const reset = useCallback(() => {
stopMonitoring();
isCheckingRef.current = false;
setState({
result: null,
isChecking: false,
lastError: null,
isMonitoring: false,
checkCount: 0,
lastCheckTime: null,
});
}, [stopMonitoring]);
// App state change handler
const handleAppStateChange = useCallback(
(nextAppState: AppStateStatus) => {
const currentState = appStateRef.current;
appStateRef.current = nextAppState;
// Check on background
if (
checkOnBackground &&
currentState === 'active' &&
nextAppState === 'background'
) {
checkSecurity();
}
// Check on foreground
if (
checkOnForeground &&
currentState === 'background' &&
nextAppState === 'active'
) {
checkSecurity();
}
},
[checkOnBackground, checkOnForeground, checkSecurity]
);
// Store the latest function reference
handleAppStateChangeRef.current = handleAppStateChange;
// Setup effects - single useEffect with no dependencies to prevent loops
useEffect(() => {
// Start monitoring if enabled
if (startImmediately) {
startMonitoringRef.current?.();
}
// Setup app state listener
const appStateSubscription = AppState.addEventListener(
'change',
(nextAppState: AppStateStatus) => {
handleAppStateChangeRef.current?.(nextAppState);
}
);
return () => {
appStateSubscription?.remove();
stopMonitoringRef.current?.();
};
}, []); // Empty dependency array - only run once
return {
state,
checkSecurity,
startMonitoring,
stopMonitoring,
reset,
};
}