// Cross-device recovery uses authenticated server state, not the browser's recent-token list. import { useEffect, useRef, useState } from '../../vendor/hooks.module.js'; import { html, Button, Modal } from '../ui/components.js'; import { useLang } from '../ui/lang.js'; import { t } from '../../../shared/i18n.js'; import { accountRecovery } from './gate.js'; function recoveryError(error) { if (error.code === 'resume_expired') return t('此对局已结束或过期,请刷新后重试。'); if (error.code === 'session') return t('游戏会话已失效,请关闭后重试。'); return t('连接失败,请重试。'); } export function AccountResumeMatchButton({ bound, onResume }) { useLang(); const [open, setOpen] = useState(false); const [item, setItem] = useState(null); const [loading, setLoading] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [revision, setRevision] = useState(0); const lock = useRef(false); const alive = useRef(true); useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []); useEffect(() => { if (!bound) { setOpen(false); setItem(null); return undefined; } if (!open) return undefined; const abort = new AbortController(); setLoading(true); setItem(null); setError(''); accountRecovery({ signal: abort.signal }).then((value) => { if (!abort.signal.aborted) setItem(value); }).catch((failure) => { if (!abort.signal.aborted) setError(recoveryError(failure)); }).finally(() => { if (!abort.signal.aborted) setLoading(false); }); return () => abort.abort(); }, [open, bound, revision]); if (!bound) return null; const resume = async () => { if (!item || lock.current) return; lock.current = true; setBusy(true); setError(''); try { await onResume(item.id); if (alive.current) setOpen(false); } catch (failure) { if (alive.current) { setError(recoveryError(failure)); if (failure.code === 'resume_expired') setItem(null); } } finally { lock.current = false; if (alive.current) setBusy(false); } }; return html`<${Button} variant="secondary" size="sm" data-testid="resume-account-match" onClick=${() => setOpen(true)}>${t('恢复账号对局')} <${Modal} open=${open} onClose=${() => !busy && setOpen(false)} title=${t('恢复账号对局')} class="sp-account-resume" actions=${html`<${Button} variant="secondary" disabled=${busy || loading} onClick=${() => setRevision((value) => value + 1)}>${t('刷新')} <${Button} variant="ghost" disabled=${busy} onClick=${() => setOpen(false)}>${t('关闭')}`}>

${t('登录同一账号并选择同一区服角色,可在其他设备继续尚未过期的对局。服务器重启后无法恢复。')}

${loading ? html`

${t('正在查询账号对局…')}

` : null} ${error ? html`

${error}

` : null} ${!loading && !item && !error ? html`

${t('当前角色没有可恢复的对局。')}

` : null} ${item ? html`

${item.inMatch ? t('对局进行中') : t('同盟等候中')}${item.inMatch && item.round > 0 ? ' · ' + t('第 {r} 回合', { r: item.round }) : ''}

${t('如果此角色仍在其他设备在线,恢复后将断开旧连接。')}

<${Button} variant="primary" block=${true} disabled=${busy || loading} data-testid="confirm-account-resume" onClick=${resume}> ${t('{name} · 同盟 {code}', { name: item.name || t('博士'), code: item.code })}` : null} `; }