import React, { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react'; import { FormTiming, MouseMovement, validateAntibotData, checkRateLimit, checkRateLimitStatus, clearRateLimit, getFingerprintHash, type AntibotValidationResult, } from '../../../shared/utils/antibot'; interface AntibotContextType { timing: FormTiming; fingerprintHash: string; startFormTracking: () => void; stopFormTracking: () => void; validateForm: ( honeypotValue: string, recaptchaToken: string | null, options?: { minTimeOnPage?: number; minTimeToFill?: number; requireRecaptcha?: boolean; checkMouseMovements?: boolean; } ) => AntibotValidationResult; checkActionRateLimit: (action: string, maxAttempts?: number) => { allowed: boolean; remainingAttempts: number; resetTime: number; }; checkActionRateLimitStatus: (action: string, maxAttempts?: number) => { allowed: boolean; remainingAttempts: number; resetTime: number; }; clearActionRateLimit: (action: string) => void; recordMouseMovement: (x: number, y: number) => void; recordClick: () => void; recordKeyPress: () => void; reset: () => void; } const AntibotContext = createContext(undefined); export const useAntibot = () => { const context = useContext(AntibotContext); if (!context) { throw new Error('useAntibot must be used within an AntibotProvider'); } return context; }; interface AntibotProviderProps { children: React.ReactNode; } export const AntibotProvider: React.FC = ({ children }) => { const [timing, setTiming] = useState({ pageLoadTime: Date.now(), formStartTime: null, formSubmitTime: null, timeOnPage: 0, timeToFill: null, mouseMovements: [], clickCount: 0, keyPressCount: 0, }); const [fingerprintHash] = useState(() => getFingerprintHash()); const pageLoadTimeRef = useRef(Date.now()); const formStartTimeRef = useRef(null); const mouseMovementsRef = useRef([]); const clickCountRef = useRef(0); const keyPressCountRef = useRef(0); const updateIntervalRef = useRef(null); // Update time on page periodically useEffect(() => { updateIntervalRef.current = window.setInterval(() => { setTiming((prev) => ({ ...prev, timeOnPage: Date.now() - pageLoadTimeRef.current, })); }, 1000); return () => { if (updateIntervalRef.current !== null) { clearInterval(updateIntervalRef.current); } }; }, []); // Track mouse movements useEffect(() => { const handleMouseMove = (e: MouseEvent) => { recordMouseMovement(e.clientX, e.clientY); }; window.addEventListener('mousemove', handleMouseMove); return () => window.removeEventListener('mousemove', handleMouseMove); }, []); // Track clicks useEffect(() => { const handleClick = () => { recordClick(); }; window.addEventListener('click', handleClick); return () => window.removeEventListener('click', handleClick); }, []); // Track key presses useEffect(() => { const handleKeyPress = () => { recordKeyPress(); }; window.addEventListener('keypress', handleKeyPress); return () => window.removeEventListener('keypress', handleKeyPress); }, []); const recordMouseMovement = useCallback((x: number, y: number) => { const movement: MouseMovement = { x, y, timestamp: Date.now(), }; mouseMovementsRef.current.push(movement); // Keep only last 50 movements to avoid memory issues if (mouseMovementsRef.current.length > 50) { mouseMovementsRef.current = mouseMovementsRef.current.slice(-50); } setTiming((prev) => ({ ...prev, mouseMovements: [...mouseMovementsRef.current], })); }, []); const recordClick = useCallback(() => { clickCountRef.current += 1; setTiming((prev) => ({ ...prev, clickCount: clickCountRef.current, })); }, []); const recordKeyPress = useCallback(() => { keyPressCountRef.current += 1; setTiming((prev) => ({ ...prev, keyPressCount: keyPressCountRef.current, })); }, []); const startFormTracking = useCallback(() => { formStartTimeRef.current = Date.now(); setTiming((prev) => ({ ...prev, formStartTime: formStartTimeRef.current, })); }, []); const stopFormTracking = useCallback(() => { if (formStartTimeRef.current !== null) { const formSubmitTime = Date.now(); const timeToFill = formSubmitTime - formStartTimeRef.current; setTiming((prev) => ({ ...prev, formSubmitTime, timeToFill, })); } }, []); const validateForm = useCallback(( honeypotValue: string, recaptchaToken: string | null, options?: { minTimeOnPage?: number; minTimeToFill?: number; requireRecaptcha?: boolean; checkMouseMovements?: boolean; } ): AntibotValidationResult => { return validateAntibotData(timing, honeypotValue, recaptchaToken, options); }, [timing]); const checkActionRateLimit = useCallback((action: string, maxAttempts: number = 5) => { return checkRateLimit(action, maxAttempts); }, []); const checkActionRateLimitStatus = useCallback((action: string, maxAttempts: number = 5) => { return checkRateLimitStatus(action, maxAttempts); }, []); const clearActionRateLimit = useCallback((action: string) => { clearRateLimit(action); }, []); const reset = useCallback(() => { pageLoadTimeRef.current = Date.now(); formStartTimeRef.current = null; mouseMovementsRef.current = []; clickCountRef.current = 0; keyPressCountRef.current = 0; setTiming({ pageLoadTime: pageLoadTimeRef.current, formStartTime: null, formSubmitTime: null, timeOnPage: 0, timeToFill: null, mouseMovements: [], clickCount: 0, keyPressCount: 0, }); }, []); const value: AntibotContextType = { timing, fingerprintHash, startFormTracking, stopFormTracking, validateForm, checkActionRateLimit, checkActionRateLimitStatus, clearActionRateLimit, recordMouseMovement, recordClick, recordKeyPress, reset, }; return ( {children} ); };