fix(service): Remplacer le polling des clés par un listener pour éviter une race condition

This commit is contained in:
NicolasCantu 2025-11-15 12:08:32 +01:00
parent f610a1bfa6
commit 95e7044e0a

View File

@ -36,6 +36,7 @@ export default class Services {
private relayReadyResolver: (() => void) | null = null; private relayReadyResolver: (() => void) | null = null;
private relayReadyPromise: Promise<void> | null = null; private relayReadyPromise: Promise<void> | null = null;
private secretsAreCompromised: boolean = false; private secretsAreCompromised: boolean = false;
private pendingKeyRequests: Map<string, (key: string) => void> = new Map();
// Private constructor to prevent direct instantiation from outside // Private constructor to prevent direct instantiation from outside
private constructor() {} private constructor() {}
@ -1138,10 +1139,44 @@ export default class Services {
} }
} }
this._resolvePendingKeyRequests(updatedProcess.current_process);
// Vérifier la logique métier spécifique au pairing // Vérifier la logique métier spécifique au pairing
await this.checkAndConfirmPairing(processId, updatedProcess); await this.checkAndConfirmPairing(processId, updatedProcess);
} }
private _resolvePendingKeyRequests(process: Process) {
if (this.pendingKeyRequests.size === 0) {
return; // Optimisation : ne rien faire si personne n'attend
}
console.log(`[Services:KeyResolver] 🔍 Vérification de ${this.pendingKeyRequests.size} requête(s) de clé en attente...`);
for (const state of process.states) {
if (!state.keys || Object.keys(state.keys).length === 0) {
continue; // Pas de clés dans cet état
}
for (const [attributeName, key] of Object.entries(state.keys)) {
const requestId = `${process.process_id}_${state.state_id}_${attributeName}`;
// Avons-nous une requête en attente pour CETTE clé ?
if (this.pendingKeyRequests.has(requestId)) {
console.log(`[Services:KeyResolver] ✅ Résolution de la requête pour ${requestId}`);
// Récupérer la fonction "resolve" et l'appeler avec la clé
const resolveCallback = this.pendingKeyRequests.get(requestId);
if (resolveCallback) {
resolveCallback(key as string);
}
// Nettoyer la requête
this.pendingKeyRequests.delete(requestId);
}
}
}
}
private async saveEncryptedData(encryptedData: Record<string, string>) { private async saveEncryptedData(encryptedData: Record<string, string>) {
console.log(`[Services:saveEncryptedData] 💾 Sauvegarde de ${Object.keys(encryptedData).length} blob(s) chiffré(s)...`); console.log(`[Services:saveEncryptedData] 💾 Sauvegarde de ${Object.keys(encryptedData).length} blob(s) chiffré(s)...`);
for (const [hash, cipher] of Object.entries(encryptedData)) { for (const [hash, cipher] of Object.entries(encryptedData)) {
@ -1906,11 +1941,11 @@ export default class Services {
} }
/** /**
* NOUVELLE MÉTHODE PRIVÉE * NOUVELLE MÉTHODE PRIVÉE (MODIFIÉE)
* Gère la logique complexe de demande aux pairs et la boucle de retentative. * Gère la logique de demande aux pairs en utilisant un système de Promise au lieu de polling.
*/ */
private async _fetchMissingKey(processId: string, state: ProcessState, attribute: string): Promise<{ hash: string | null; key: string | null }> { private async _fetchMissingKey(processId: string, state: ProcessState, attribute: string): Promise<{ hash: string | null; key: string | null }> {
console.group(`🔐 Gestion de la clé manquante pour '${attribute}'`); console.group(`🔐 Gestion de la clé manquante pour '${attribute}' (via Promise)`);
try { try {
const process = await this.getProcess(processId); const process = await this.getProcess(processId);
@ -1919,53 +1954,42 @@ export default class Services {
return { hash: null, key: null }; return { hash: null, key: null };
} }
// 1. Demander les connexions et envoyer la requête (comme avant)
await this.ensureConnections(process); await this.ensureConnections(process);
console.log(`🗣️ Demande de données aux pairs...`); console.log(`🗣️ Demande de données aux pairs...`);
await this.requestDataFromPeers(processId, [state.state_id], [state.roles]); await this.requestDataFromPeers(processId, [state.state_id], [state.roles]);
// Boucle de retentative // 2. CRÉER LA PROMISE D'ATTENTE
const maxRetries = 5; const requestId = `${processId}_${state.state_id}_${attribute}`;
const retryDelay = 500; const keyRequestPromise = new Promise<string>((resolve, reject) => {
let retries = 0; // Sécurité : Définir un timeout (ex: 15 secondes)
let hash: string | null = null; const timeout = setTimeout(() => {
let key: string | null = null; console.warn(`⌛ Timeout: La clé pour '${attribute}' n'est jamais arrivée.`);
this.pendingKeyRequests.delete(requestId); // Nettoyer
reject(new Error(`Timeout waiting for key: ${attribute}`));
}, 15000); // 15 secondes
console.groupCollapsed(`⏳ Boucle d'attente de la clé (max ${maxRetries} tentatives)`); // Enregistrer le "resolve" pour qu'il soit appelé de l'extérieur
try { this.pendingKeyRequests.set(requestId, (key: string) => {
while (retries < maxRetries) { clearTimeout(timeout); // Annuler le timeout
console.log(`(Tentative ${retries + 1}/${maxRetries})...`); console.log(`✅ Clé reçue via listener pour '${attribute}'!`);
await new Promise((resolve) => setTimeout(resolve, retryDelay)); resolve(key);
});
});
// On rafraîchit les données depuis la source // 3. Attendre la clé
const updatedProcess = await this.getProcess(processId); const receivedKey = await keyRequestPromise;
const updatedState = this.getStateFromId(updatedProcess, state.state_id);
if (updatedState) { // 4. Re-vérifier l'état (le hash a pu changer)
hash = updatedState.pcd_commitment[attribute]; const updatedProcess = await this.getProcess(processId);
key = updatedState.keys[attribute]; const updatedState = this.getStateFromId(updatedProcess, state.state_id);
const updatedHash = updatedState ? updatedState.pcd_commitment[attribute] : state.pcd_commitment[attribute];
if (hash && key) { return { hash: updatedHash, key: receivedKey };
console.log('✅ Clé et hash reçus !');
break; // Sortir de la boucle while
}
}
retries++;
}
} finally {
// Garantit la fermeture du groupe de la boucle
console.groupEnd();
}
if (!hash || !key) {
console.warn(`⌛ Clé ou hash toujours manquant après ${maxRetries} tentatives.`);
}
return { hash, key };
} catch (e) { } catch (e) {
console.error(`💥 Erreur durant _fetchMissingKey: ${e}`); console.error(`💥 Erreur durant _fetchMissingKey: ${e}`);
return { hash: null, key: null }; return { hash: null, key: null };
} finally { } finally {
// Garantit la fermeture du groupe de gestion de clé
console.groupEnd(); console.groupEnd();
} }
} }