import { useEffect, useRef, useState } from 'react'; import { portalRequest, PortalApiError } from './portal-api'; import { createOperationKey } from './operation-key'; import { usePagePreparationGuard } from './page-preparation-guard'; export type SupportPlan = { actorId: number; target: { database: string; port: number }; hash: string; code_hash: string; installed: boolean; consistent: boolean; repairable: boolean; ready: boolean; action: 'none' | 'install' | 'repair_known_base' | 'blocked'; schema: { state: string; ledger: boolean }; dependencies: Record; changes: string[]; backupDate: string | null; rollback: string; commands: { key: string; code_hash: string }[]; mfa_confirmed: boolean; }; type Attempt = { body: { hash: string; reason: string; confirm: true; idempotency_key: string; }; code: string; generation: number; }; const endpoint = '/management/profile-install/technical-support'; const object = (v: unknown): v is Record => !!v && typeof v === 'object' && !Array.isArray(v); const hex = (v: unknown): v is string => typeof v === 'string' && /^[a-f0-9]{64}$/.test(v); const target = (v: unknown): v is SupportPlan['target'] => object(v) && typeof v.database === 'string' && Number.isSafeInteger(v.port) && Number(v.port) > 0; const validPlan = (v: unknown): v is SupportPlan => object(v) && typeof v.actorId === 'number' && Number.isSafeInteger(v.actorId) && v.actorId > 0 && target(v.target) && hex(v.hash) && hex(v.code_hash) && typeof v.installed === 'boolean' && typeof v.consistent === 'boolean' && typeof v.repairable === 'boolean' && typeof v.ready === 'boolean' && ['none', 'install', 'repair_known_base', 'blocked'].includes( String(v.action), ) && object(v.schema) && typeof v.schema.state === 'string' && typeof v.schema.ledger === 'boolean' && object(v.dependencies) && Object.values(v.dependencies).every((x) => typeof x === 'boolean') && Array.isArray(v.changes) && v.changes.every((x) => typeof x === 'string') && (v.backupDate === null || typeof v.backupDate === 'string') && typeof v.rollback === 'string' && typeof v.mfa_confirmed === 'boolean' && Array.isArray(v.commands) && v.commands.every( (x) => object(x) && typeof x.key === 'string' && hex(x.code_hash), ); const sameTarget = (a: SupportPlan['target'], b: SupportPlan['target']) => a.database === b.database && a.port === b.port; export function useProfileSupportInstallation(initial: SupportPlan) { const [plan, setPlan] = useState(initial), [base, setBase] = useState(initial), [reason, setReason] = useState(''), [confirmed, setConfirmed] = useState(false), [busy, setBusy] = useState(false), [refreshing, setRefreshing] = useState(false), [uncertain, setUncertain] = useState(false), [blocked, setBlocked] = useState(false), [needsRefresh, setNeedsRefresh] = useState(false), [message, setMessage] = useState(''), [error, setError] = useState(''); const origin = useRef({ actor: initial.actorId, target: initial.target }), epoch = useRef({ actor: initial.actorId, target: initial.target }), alive = useRef(true), pending = useRef(null), active = useRef(null), reading = useRef(null), generation = useRef(0), text = useRef(reason), committed = useRef(null); if ( epoch.current.actor !== initial.actorId || !sameTarget(epoch.current.target, initial.target) ) epoch.current = { actor: initial.actorId, target: initial.target }; text.current = reason; const valid = validPlan(plan) && plan.actorId === origin.current.actor && sameTarget(plan.target, origin.current.target) && initial.actorId === origin.current.actor && sameTarget(initial.target, origin.current.target); const stale = base.hash !== plan.hash; useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []); function change(value: string) { generation.current++; text.current = value; setReason(value); setConfirmed(false); setError(''); } function acknowledge(attempt: Attempt) { if (pending.current !== attempt) return; pending.current = null; active.current = null; committed.current = attempt; setBusy(false); setUncertain(false); setNeedsRefresh(true); setConfirmed(false); setError(''); setMessage( 'Resultado da actualização confirmado. Confira a prévia actual.', ); if (generation.current === attempt.generation) { text.current = ''; setReason(''); } } function read(value: unknown) { if ( !validPlan(value) || value.actorId !== origin.current.actor || !sameTarget(value.target, origin.current.target) ) return false; setPlan(value); setBlocked(false); setError(''); const attempt = pending.current; if ( attempt && value.commands.some( (c) => c.key === attempt.body.idempotency_key && c.code_hash === attempt.code, ) ) acknowledge(attempt); const known = committed.current; if (known) { if ( !value.commands.some( (c) => c.key === known.body.idempotency_key && c.code_hash === known.code, ) ) { setError( 'Esta consulta ainda não confirma o resultado já recebido. Actualize novamente antes de outra acção.', ); return true; } if ( value.code_hash === known.code && value.installed && value.consistent ) setBase(value); committed.current = null; setNeedsRefresh(false); } else setNeedsRefresh(false); return true; } useEffect(() => { if (validPlan(initial)) read(initial); }, [initial]); async function refresh() { if (reading.current) return; const request = {}, scope = epoch.current; reading.current = request; setRefreshing(true); try { const value = await portalRequest(endpoint); if (alive.current && epoch.current === scope && !read(value)) setError( 'A consulta recebida não corresponde a esta conta e destino. A preparação foi conservada.', ); } catch (e) { if (alive.current && epoch.current === scope) { if ( e instanceof PortalApiError && [401, 403, 419].includes(e.status) ) setBlocked(true); setError( 'Não foi possível actualizar a prévia. O fundamento e a tentativa original foram conservados.', ); } } finally { if ( alive.current && epoch.current === scope && reading.current === request ) { reading.current = null; setRefreshing(false); } } } async function send(retry = false) { if ( !valid || blocked || !plan.mfa_confirmed || active.current || reading.current ) return; if ( !retry && (pending.current || stale || needsRefresh || !confirmed || !plan.ready || plan.installed || !plan.mfa_confirmed || (!plan.consistent && !plan.repairable)) ) return; let attempt = pending.current; if (!attempt) { if (text.current.trim().length < 20 || text.current.length > 1000) { setError('Preencha o fundamento com 20 a 1000 caracteres.'); return; } try { attempt = { body: { hash: base.hash, reason: text.current, confirm: true, idempotency_key: createOperationKey().toLowerCase(), }, code: base.code_hash, generation: generation.current, }; } catch (e) { setError( e instanceof Error ? e.message : 'Não foi possível preparar a operação.', ); return; } pending.current = attempt; } const scope = epoch.current; active.current = attempt; setBusy(true); setUncertain(true); setError(''); try { const result = await portalRequest(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(attempt.body), }); if ( !alive.current || epoch.current !== scope || pending.current !== attempt ) return; if ( !object(result) || result.actor_id !== origin.current.actor || result.command_key !== attempt.body.idempotency_key || result.migration !== '2026_11_03_080000_add_technical_support_profile' || result.code_hash !== attempt.code || result.installed !== true || !target(result.target) || !sameTarget(result.target, origin.current.target) ) throw new Error( 'A confirmação não corresponde à tentativa preparada. Consulte a prévia antes de continuar.', ); acknowledge(attempt); void refresh(); } catch (e) { if ( !alive.current || epoch.current !== scope || pending.current !== attempt ) return; if (e instanceof PortalApiError && [409, 422].includes(e.status)) { pending.current = null; setUncertain(false); setConfirmed(false); setError( e.status === 409 ? 'A tentativa foi recusada porque o plano mudou. Actualize e compare a prévia; o fundamento foi conservado.' : e.errors.reason?.join(' ') || 'A operação foi recusada. Confira as dependências, as provas e o fundamento.', ); if (e.status === 409) { setNeedsRefresh(true); void refresh(); } } else if ( e instanceof PortalApiError && [401, 403, 419].includes(e.status) ) { setBlocked(true); setError( 'Confirme a conta e renove a autenticação MFA antes de repetir. A preparação foi conservada.', ); } else setError( e instanceof Error ? e.message : 'Resultado sem confirmação. Conserve a tentativa original.', ); } finally { if ( alive.current && epoch.current === scope && active.current === attempt ) { active.current = null; setBusy(false); } } } function adopt() { if (!valid || busy || refreshing || pending.current || blocked) return; setBase(plan); setConfirmed(false); setNeedsRefresh(false); setMessage( 'Prévia actual confirmada. Reveja o fundamento e confirme esta operação.', ); setError(''); } usePagePreparationGuard( !!reason || uncertain, 'Existe um fundamento preparado ou uma instalação sem confirmação. Sair desta página?', ); return { plan, base, reason, change, confirmed, setConfirmed, busy, refreshing, uncertain, message, error, stale, refresh, send, adopt, canAdopt: valid && !busy && !refreshing && !pending.current && !blocked && stale, canRetry: valid && plan.mfa_confirmed && !busy && !refreshing && !blocked && !!pending.current, canSend: valid && !blocked && !busy && !refreshing && !needsRefresh && !stale && !uncertain && confirmed && plan.ready && !plan.installed && plan.mfa_confirmed && (plan.consistent || plan.repairable), }; }