import { useState, useEffect, useRef } from 'react'; import { Platform } from 'react-native'; import { WalletType } from 'rnwalletman'; import { startOtpSmsListener } from '../utils/smsRetriever'; export interface OTPBindCallbacks { onRequestOTP: (walletType: WalletType, params: any) => Promise; onVerifyOTP: (walletType: WalletType, params: any) => Promise; onSuccess: (result: any) => void; onError: (error: string) => void; isDebug?: boolean; } export interface OTPBindState { mobile: string; otp: string; password: string; passwordRequired: boolean; formStarted: boolean; otpSent: boolean; step: 'mobile' | 'otp' | 'processing'; loading: boolean; otpData: any; errorMessage: string; resendCountdown: number; } export interface OTPBindActions { setMobile: (mobile: string) => void; setOtp: (otp: string) => void; setPassword: (password: string) => void; requestOTP: () => Promise; verifyOTP: () => Promise; resetToMobile: () => void; clearError: () => void; } export function useOTPBind( walletType: WalletType, callbacks: OTPBindCallbacks, config?: { otpLength?: number; mobileLength?: number; additionalParams?: any; initialMobile?: string; passwordBeforeOtp?: boolean; passwordRequiredMsg?: string; resendCooldown?: number; } ): [OTPBindState, OTPBindActions] { const [mobile, setMobile] = useState(''); const [otp, setOtp] = useState(''); const [password, setPassword] = useState(''); const [passwordRequired, setPasswordRequired] = useState(false); const [formStarted, setFormStarted] = useState(false); const [otpSent, setOtpSent] = useState(false); const [step, setStep] = useState<'mobile' | 'otp' | 'processing'>('mobile'); const [loading, setLoading] = useState(false); const [otpData, setOtpData] = useState(null); const [errorMessage, setErrorMessage] = useState(''); const [resendCountdown, setResendCountdown] = useState(0); const [smsListenKey, setSmsListenKey] = useState(0); const smsStopRef = useRef<(() => void) | null>(null); const { onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug = false } = callbacks; const { otpLength = 6, mobileLength = 10, additionalParams = {}, initialMobile = '', passwordBeforeOtp = false, passwordRequiredMsg = 'Please enter your password', resendCooldown = 20, } = config || {}; useEffect(() => { if (initialMobile) setMobile(initialMobile); }, [initialMobile]); const log = (...args: any[]) => { if (isDebug) console.log(`[${walletType}]`, ...args); }; const error = (...args: any[]) => { if (isDebug) console.error(`[${walletType}]`, ...args); }; useEffect(() => { if (resendCountdown <= 0) return; const timer = setInterval(() => { setResendCountdown(prev => (prev <= 1 ? 0 : prev - 1)); }, 1000); return () => clearInterval(timer); }, [resendCountdown]); const shouldListenSms = Platform.OS === 'android' && (step === 'otp' || (passwordBeforeOtp && otpSent && formStarted)); useEffect(() => { if (!shouldListenSms) { smsStopRef.current?.(); smsStopRef.current = null; return; } let cancelled = false; (async () => { smsStopRef.current?.(); const stop = await startOtpSmsListener(otpLength, (code) => { if (cancelled) return; setOtp(code); log('OTP from SMS Retriever:', code); }); if (cancelled) { stop(); return; } smsStopRef.current = stop; })(); return () => { cancelled = true; smsStopRef.current?.(); smsStopRef.current = null; }; }, [shouldListenSms, smsListenKey, otpLength]); useEffect(() => () => smsStopRef.current?.(), []); const startResendCooldown = () => setResendCountdown(resendCooldown); const clearError = () => setErrorMessage(''); const requestOTP = async () => { const withPassword = passwordBeforeOtp && formStarted && passwordRequired; const isResend = otpSent && formStarted; if (isResend && resendCountdown > 0) { return; } if (withPassword && !password.trim()) { const msg = passwordRequiredMsg; setErrorMessage(msg); onError(msg); return; } if (!mobile || mobile.length !== mobileLength) { const msg = 'Invalid mobile number'; setErrorMessage(msg); onError(msg); return; } setLoading(true); setErrorMessage(''); log(isResend ? 'Resending OTP for:' : withPassword ? 'Requesting OTP with password for:' : 'Requesting OTP for:', mobile); try { const response = await onRequestOTP(walletType, { mobile, ...(withPassword ? { password, sessionId: otpData?.sessionId } : {}), ...(isResend && !withPassword && otpData ? { ...otpData } : {}), ...additionalParams, }); log('OTP response:', response); if (response.success) { setOtpData(response.data); setFormStarted(true); if (passwordBeforeOtp && response.data?.needPassword && !withPassword && !isResend) { setPasswordRequired(true); setOtpSent(false); } else { setPasswordRequired(!!response.data?.needPassword || withPassword); setOtpSent(true); setSmsListenKey((k) => k + 1); startResendCooldown(); if (!passwordBeforeOtp) { setStep('otp'); } } setErrorMessage(''); } else { error('OTP request failed:', response.message); const msg = response.message || 'Failed to request OTP'; setErrorMessage(msg); onError(msg); } } catch (e) { error('Request OTP error:', e); const msg = e instanceof Error ? e.message : 'Failed to request OTP'; setErrorMessage(msg); onError(msg); } finally { setLoading(false); } }; const verifyOTP = async () => { if (!otp || otp.length !== otpLength) { const msg = `Please enter the ${otpLength}-digit verification code`; setErrorMessage(msg); onError(msg); return; } if (passwordRequired && !password.trim()) { const msg = passwordRequiredMsg; setErrorMessage(msg); onError(msg); return; } setLoading(true); setErrorMessage(''); setStep('processing'); log('Verifying OTP:', otp); try { const response = await onVerifyOTP(walletType, { mobile, otp, password: passwordRequired ? password : undefined, ...additionalParams, ...(otpData || {}), }); log('Verify response:', response); if (response.success) { setErrorMessage(''); onSuccess(response.data); } else { log('Verify failed:', response.message); const msg = response.message || 'Failed to verify OTP'; setStep(passwordBeforeOtp ? 'mobile' : 'otp'); setErrorMessage(msg); } } catch (e) { error('Verify OTP error:', e); const msg = e instanceof Error ? e.message : 'Failed to verify OTP'; setStep(passwordBeforeOtp ? 'mobile' : 'otp'); setErrorMessage(msg); } finally { setLoading(false); } }; const resetToMobile = () => { setStep('mobile'); setOtp(''); setPassword(''); setPasswordRequired(false); setFormStarted(false); setOtpSent(false); setOtpData(null); setErrorMessage(''); setResendCountdown(0); setSmsListenKey(0); smsStopRef.current?.(); smsStopRef.current = null; }; return [ { mobile, otp, password, passwordRequired, formStarted, otpSent, step, loading, otpData, errorMessage, resendCountdown, }, { setMobile, setOtp, setPassword, requestOTP, verifyOTP, resetToMobile, clearError }, ]; }