import { useEffect, useRef, useState, useCallback } from 'react'; import { Button } from '@heroui/button'; import { Spinner } from '@heroui/spinner'; import { QRCodeSVG } from 'qrcode.react'; import QQManager from '@/controllers/qq_manager'; interface NewDeviceVerifyProps { /** jumpUrl from loginErrorInfo */ jumpUrl: string; /** QQ uin for OIDB requests */ uin: string; /** Called when QR verification is confirmed, passes str_nt_succ_token */ onVerified: (token: string) => void; /** Called when user cancels */ onCancel?: () => void; } type QRStatus = 'loading' | 'waiting' | 'scanned' | 'confirmed' | 'error'; const NewDeviceVerify: React.FC = ({ jumpUrl, uin, onVerified, onCancel, }) => { const [qrUrl, setQrUrl] = useState(''); const [status, setStatus] = useState('loading'); const [errorMsg, setErrorMsg] = useState(''); const pollTimerRef = useRef | null>(null); const mountedRef = useRef(true); const stopPolling = useCallback(() => { if (pollTimerRef.current) { clearInterval(pollTimerRef.current); pollTimerRef.current = null; } }, []); const startPolling = useCallback((token: string) => { stopPolling(); pollTimerRef.current = setInterval(async () => { if (!mountedRef.current) return; try { const result = await QQManager.pollNewDeviceQR(uin, token); if (!mountedRef.current) return; const s = result?.uint32_guarantee_status; if (s === 3) { setStatus('scanned'); } else if (s === 1) { stopPolling(); setStatus('confirmed'); const ntToken = result?.str_nt_succ_token || ''; onVerified(ntToken); } // s === 0 means still waiting, do nothing } catch { // Ignore poll errors, keep polling } }, 2500); }, [uin, onVerified, stopPolling]); const fetchQRCode = useCallback(async () => { setStatus('loading'); setErrorMsg(''); try { const result = await QQManager.getNewDeviceQRCode(uin, jumpUrl); if (!mountedRef.current) return; if (result?.str_url) { setQrUrl(result.str_url); setStatus('waiting'); // bytes_token 用于轮询,如果 OIDB 未返回则用空字符串 startPolling(result.bytes_token || ''); } else { setStatus('error'); setErrorMsg('获取二维码失败,请重试'); } } catch (e) { if (!mountedRef.current) return; setStatus('error'); setErrorMsg((e as Error).message || '获取二维码失败'); } }, [uin, jumpUrl, startPolling]); useEffect(() => { mountedRef.current = true; fetchQRCode(); return () => { mountedRef.current = false; stopPolling(); }; }, [fetchQRCode, stopPolling]); const statusText: Record = { loading: '正在获取二维码...', waiting: '请使用手机QQ扫描二维码完成验证', scanned: '已扫描,请在手机上确认', confirmed: '验证成功,正在登录...', error: errorMsg || '获取二维码失败', }; const statusColor: Record = { loading: 'text-default-500', waiting: 'text-warning', scanned: 'text-primary', confirmed: 'text-success', error: 'text-danger', }; return (

检测到新设备登录,请使用手机QQ扫描下方二维码完成验证

{status === 'loading' ? (
) : status === 'error' ? (

{errorMsg}

) : (
)}

{statusText[status]}

{status === 'waiting' && ( )}
); }; export default NewDeviceVerify;