ncantu 9291eab9d5 UserWallet: runCollectLoop options object, root null check, pairing/collect/bip32
**Motivations:**
- Passer runCollectLoop en objet d’options pour lisibilité et évolution
- Éviter non-null assertion sur root dans main.tsx
- Ajustements pairing, collect signatures, bip32

**Root causes:**
- N/A (évolutions + correctifs ciblés)

**Correctifs:**
- main.tsx: vérification root !== null avant createRoot, throw explicite si absent

**Evolutions:**
- LoginScreen: runCollectLoop({ relayEndpoints, hash, ourSigs, path, pairToMembers, pubkeyToPair, opts })
- pairingConfirm.ts, PairingDisplayScreen, PairingSetupBlock: modifications
- collectSignatures.ts, bip32.ts: ajustements
- SyncScreen: modifications mineures

**Pages affectées:**
- userwallet: LoginScreen, main.tsx, PairingDisplayScreen, PairingSetupBlock, SyncScreen, pairingConfirm.ts, bip32.ts, collectSignatures.ts
- data: sync-utxos.log
2026-01-28 09:43:55 +01:00

137 lines
4.5 KiB
TypeScript

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useIdentity } from '../hooks/useIdentity';
import { useErrorHandler } from '../hooks/useErrorHandler';
import { ErrorDisplay } from './ErrorDisplay';
import { getStoredRelays } from '../utils/relay';
import { getStoredPairs } from '../utils/pairing';
import { SyncService } from '../services/syncService';
import { GraphResolver } from '../services/graphResolver';
import { checkPairingConfirmationFromSync } from '../services/pairingConfirm';
export function SyncScreen(): JSX.Element {
const navigate = useNavigate();
const { identity } = useIdentity();
const { error, handleError, clearError } = useErrorHandler();
const [isSyncing, setIsSyncing] = useState(false);
const [stats, setStats] = useState<{
messages: number;
newMessages: number;
decrypted: number;
validated: number;
indechiffrable: number;
nonValide: number;
relayStatus: Array<{ endpoint: string; ok: boolean }>;
} | null>(null);
const handleSync = async (): Promise<void> => {
if (identity === null) {
handleError('Identité requise pour synchroniser', 'MISSING_IDENTITY');
return;
}
setIsSyncing(true);
clearError();
try {
const relays = getStoredRelays().filter((r) => r.enabled);
if (relays.length === 0) {
handleError('Aucun relais activé', 'NO_ENABLED_RELAYS');
return;
}
const graphResolver = new GraphResolver();
const syncService = new SyncService(relays, graphResolver, identity);
await syncService.init();
const start = identity.t0_anniversaire;
const end = Date.now();
const result = await syncService.sync(start, end);
setStats(result);
const pairs = getStoredPairs();
const local = pairs.find((p) => p.is_local);
const remote = pairs.find((p) => !p.is_local);
if (
local !== undefined &&
remote !== undefined &&
identity.privateKey !== undefined
) {
void checkPairingConfirmationFromSync({
relays,
pairLocal: local.uuid,
pairRemote: remote.uuid,
identity,
start,
end,
remotePublicKey: remote.publicKey,
}).catch((err: unknown) => {
console.error('Pairing confirmation check during sync:', err);
});
}
if (result.messages === 0) {
handleError('Aucun message récupéré. Vérifiez la connectivité des relais.', 'NO_MESSAGES');
}
} catch (err) {
handleError(err, 'Erreur lors de la synchronisation');
} finally {
setIsSyncing(false);
}
};
return (
<main>
<h1>Synchronisation</h1>
{error !== null && <ErrorDisplay error={error} onDismiss={clearError} />}
<section aria-labelledby="sync-info">
<h2 id="sync-info">Information</h2>
{identity !== null && (
<p>
Fenêtre de scan: depuis {new Date(identity.t0_anniversaire).toLocaleString()}{' '}
jusqu'à maintenant
</p>
)}
<button onClick={handleSync} disabled={isSyncing || identity === null}>
{isSyncing ? 'Synchronisation...' : 'Synchroniser maintenant'}
</button>
</section>
{stats !== null && (
<section aria-labelledby="sync-results">
<h2 id="sync-results">Résultats</h2>
<ul>
<li>Messages récupérés: {stats.messages}</li>
<li>Nouveaux messages: {stats.newMessages}</li>
<li>Messages déchiffrés: {stats.decrypted}</li>
<li>Messages validés: {stats.validated}</li>
<li>
Indéchiffrables (clé manquante): {stats.indechiffrable}
</li>
<li>
Non validés (ex. signature manquante): {stats.nonValide}
</li>
</ul>
{stats.relayStatus.length > 0 && (
<>
<h3 id="relay-status">Statut relais</h3>
<ul aria-labelledby="relay-status">
{stats.relayStatus.map((r) => (
<li key={r.endpoint}>
{r.endpoint}: {r.ok ? 'OK' : 'indisponible'}
</li>
))}
</ul>
</>
)}
</section>
)}
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem' }}>
<button onClick={() => navigate('/service-sync')}>
Sync par service
</button>
<button onClick={() => navigate('/')}>Retour</button>
</div>
</main>
);
}