/ apps / web / src / hooks / useAltcha.ts
useAltcha.ts
  1  import { useState, useEffect, useCallback } from 'react';
  2  import { altchaService, AltchaVerification } from '../services/altcha/AltchaService';
  3  
  4  export interface UseAltchaOptions {
  5    autoVerify?: boolean;
  6    maxnumber?: number;
  7    onVerified?: (verification: AltchaVerification) => void;
  8    onError?: (error: Error) => void;
  9  }
 10  
 11  export interface UseAltchaReturn {
 12    isVerified: boolean;
 13    isLoading: boolean;
 14    stage: 'idle' | 'generating' | 'solving' | 'verifying' | 'error';
 15    progress: number;
 16    hashRate: number;
 17    error: Error | null;
 18    verification: AltchaVerification | null;
 19    verify: () => Promise<void>;
 20    reset: () => void;
 21    getRemainingTime: () => number;
 22  }
 23  
 24  /**
 25   * Hook for managing ALTCHA verification state
 26   */
 27  export function useAltcha(options: UseAltchaOptions = {}): UseAltchaReturn {
 28    const {
 29      autoVerify = false,
 30      maxnumber = 100000,
 31      onVerified,
 32      onError,
 33    } = options;
 34  
 35    const [isVerified, setIsVerified] = useState(false);
 36    const [isLoading, setIsLoading] = useState(false);
 37    const [stage, setStage] = useState<UseAltchaReturn['stage']>('idle');
 38    const [progress, setProgress] = useState(0);
 39    const [hashRate, setHashRate] = useState(0);
 40    const [error, setError] = useState<Error | null>(null);
 41    const [verification, setVerification] = useState<AltchaVerification | null>(null);
 42  
 43    // Check initial verification status
 44    useEffect(() => {
 45      const checkVerification = () => {
 46        const verified = altchaService.isVerified();
 47        setIsVerified(verified);
 48        
 49        if (verified) {
 50          // Get stored verification details
 51          const stored = sessionStorage.getItem('altcha_verification');
 52          if (stored) {
 53            try {
 54              setVerification(JSON.parse(stored));
 55            } catch (e) {
 56              console.error('Failed to parse stored verification:', e);
 57            }
 58          }
 59        }
 60      };
 61  
 62      checkVerification();
 63  
 64      // Check periodically for expiration
 65      const interval = setInterval(checkVerification, 30000);
 66      return () => clearInterval(interval);
 67    }, []);
 68  
 69    // Verify function
 70    const verify = useCallback(async () => {
 71      if (isLoading || isVerified) return;
 72  
 73      setIsLoading(true);
 74      setError(null);
 75      setProgress(0);
 76      setHashRate(0);
 77      setStage('idle');
 78  
 79      try {
 80        const result = await altchaService.completeAltchaFlow({
 81          maxnumber,
 82          onProgress: (stage, progress, hashRate) => {
 83            setStage(stage);
 84            if (progress !== undefined) setProgress(progress);
 85            if (hashRate !== undefined) setHashRate(hashRate);
 86          },
 87        });
 88  
 89        setVerification(result);
 90        setIsVerified(true);
 91        setStage('idle');
 92        onVerified?.(result);
 93      } catch (err) {
 94        const error = err instanceof Error ? err : new Error('Verification failed');
 95        setError(error);
 96        setStage('error');
 97        onError?.(error);
 98      } finally {
 99        setIsLoading(false);
100      }
101    }, [isLoading, isVerified, maxnumber, onVerified, onError]);
102  
103    // Reset function
104    const reset = useCallback(() => {
105      altchaService.clearVerification();
106      setIsVerified(false);
107      setVerification(null);
108      setError(null);
109      setProgress(0);
110      setHashRate(0);
111      setStage('idle');
112    }, []);
113  
114    // Get remaining time
115    const getRemainingTime = useCallback(() => {
116      return altchaService.getRemainingTime();
117    }, []);
118  
119    // Auto-verify if requested
120    useEffect(() => {
121      if (autoVerify && !isVerified && !isLoading && stage === 'idle') {
122        verify();
123      }
124    }, [autoVerify, isVerified, isLoading, stage, verify]);
125  
126    return {
127      isVerified,
128      isLoading,
129      stage,
130      progress,
131      hashRate,
132      error,
133      verification,
134      verify,
135      reset,
136      getRemainingTime,
137    };
138  }
139  
140  /**
141   * Hook to require ALTCHA verification for a component
142   */
143  export function useRequireAltcha(options: UseAltchaOptions = {}) {
144    const altcha = useAltcha({ ...options, autoVerify: true });
145    
146    // Throw in render if not verified (for use with error boundaries)
147    if (!altcha.isVerified && !altcha.isLoading && altcha.stage !== 'idle') {
148      throw new Error('ALTCHA verification required');
149    }
150    
151    return altcha;
152  }
153  
154  /**
155   * Hook to get ALTCHA verification status without triggering verification
156   */
157  export function useAltchaStatus() {
158    const [isVerified, setIsVerified] = useState(altchaService.isVerified());
159    const [remainingTime, setRemainingTime] = useState(altchaService.getRemainingTime());
160  
161    useEffect(() => {
162      const checkStatus = () => {
163        setIsVerified(altchaService.isVerified());
164        setRemainingTime(altchaService.getRemainingTime());
165      };
166  
167      // Check immediately
168      checkStatus();
169  
170      // Check every second for countdown
171      const interval = setInterval(checkStatus, 1000);
172      return () => clearInterval(interval);
173    }, []);
174  
175    return {
176      isVerified,
177      remainingTime,
178      remainingMinutes: Math.floor(remainingTime / 60000),
179      remainingSeconds: Math.floor((remainingTime % 60000) / 1000),
180    };
181  }