import { getSignatures } from './relay'; import { getStoredPairs } from './pairing'; import { hasEnoughSignatures, collectProgress } from './loginValidation'; import type { LoginPath } from '../types/identity'; import type { MsgSignature } from '../types/message'; export interface ProofSignature { signature: string; cle_publique: string; nonce: string; pair_uuid: string; } /** * Fetch signatures for hash from all relays, aggregate and deduplicate. */ export async function fetchSignaturesForHash( relayEndpoints: string[], hash: string, ): Promise { const seen = new Set(); const out: MsgSignature[] = []; for (const ep of relayEndpoints) { try { const list = await getSignatures(ep, hash); for (const m of list) { const s = m.signature; const key = `${s.cle_publique}\t${s.nonce}\t${s.signature}`; if (seen.has(key)) { continue; } seen.add(key); out.push(m); } } catch { continue; } } return out; } /** * Map pair_uuid -> membres_parents_uuid for pairs in pairsAttendus. */ export function buildPairToMembers( pairsAttendus: string[], ): Map { const pairs = getStoredPairs(); const set = new Set(pairsAttendus); const m = new Map(); for (const p of pairs) { if (!set.has(p.uuid)) { continue; } m.set(p.uuid, p.membres_parents_uuid); } return m; } /** * Map cle_publique -> pair_uuid for local (identity) and remote (pair.publicKey) pairs. */ export function buildPubkeyToPair( identityPublicKey: string, pairsAttendus: string[], ): Map { const pairs = getStoredPairs(); const set = new Set(pairsAttendus); const m = new Map(); for (const p of pairs) { if (!set.has(p.uuid)) { continue; } if (p.is_local) { m.set(identityPublicKey, p.uuid); } else if (p.publicKey !== undefined) { m.set(p.publicKey, p.uuid); } } return m; } /** * Map MsgSignature[] to ProofSignature[] using pubkey->pair. Skip unknown pubkeys. */ export function mapMsgSignaturesToProofFormat( msgs: MsgSignature[], hash: string, pubkeyToPair: Map, ): ProofSignature[] { const out: ProofSignature[] = []; for (const m of msgs) { const s = m.signature; if (s.hash !== hash) { continue; } const pairUuid = pubkeyToPair.get(s.cle_publique); if (pairUuid === undefined) { continue; } out.push({ signature: s.signature, cle_publique: s.cle_publique, nonce: s.nonce, pair_uuid: pairUuid, }); } return out; } export const COLLECT_POLL_MS = 2000; export const COLLECT_TIMEOUT_MS = 300000; export function delay(ms: number): Promise { return new Promise((resolve) => { setTimeout(resolve, ms); }); } /** * Merge our sigs with fetched, dedup by pair_uuid (keep first). */ function mergeDedupByPair( ours: ProofSignature[], fetched: ProofSignature[], ): ProofSignature[] { const byPair = new Map(); for (const s of ours) { byPair.set(s.pair_uuid, s); } for (const s of fetched) { if (!byPair.has(s.pair_uuid)) { byPair.set(s.pair_uuid, s); } } return Array.from(byPair.values()); } export interface CollectLoopOpts { pollMs: number; timeoutMs: number; /** Called each poll with current merged sigs (notifications relais, UI progress). */ onProgress?: (merged: ProofSignature[]) => void; } /** * Collect signatures from relays until we have enough per member, or timeout. * Optional onProgress called each poll for UI (e.g. X/Y signatures). */ export async function runCollectLoop( relayEndpoints: string[], hash: string, ourSigs: ProofSignature[], path: LoginPath, pairToMembers: Map, pubkeyToPair: Map, opts: CollectLoopOpts, ): Promise { const start = Date.now(); let merged = ourSigs; for (;;) { opts.onProgress?.(merged); if (hasEnoughSignatures(path, merged, pairToMembers)) { return merged; } if (Date.now() - start >= opts.timeoutMs) { throw new Error('Collecte distante : timeout (signatures manquantes)'); } const msgs = await fetchSignaturesForHash(relayEndpoints, hash); const fetched = mapMsgSignaturesToProofFormat(msgs, hash, pubkeyToPair); merged = mergeDedupByPair(ourSigs, fetched); await delay(opts.pollMs); } }