**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
137 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
}
|