refactor(iframe-controller): enhance async handling, improve error checks, and remove unnecessary comments for better code clarity

This commit is contained in:
Sadrinho27 2025-12-02 00:25:41 +01:00
parent 23fe47f69f
commit da76c1ac3e

View File

@ -6,12 +6,11 @@ import { splitPrivateData, isValid32ByteHex } from "../utils/service.utils";
import { MerkleProofResult } from "../../pkg/sdk_client"; import { MerkleProofResult } from "../../pkg/sdk_client";
export class IframeController { export class IframeController {
private static isInitialized = false; // <--- VERROU private static isInitialized = false;
static async init() { static async init() {
if (this.isInitialized) return; // On sort si déjà lancé if (this.isInitialized) return;
// On ne lance l'écoute que si on est dans une iframe
if (window.self !== window.top) { if (window.self !== window.top) {
console.log( console.log(
"[IframeController] 📡 Mode Iframe détecté. Démarrage des listeners API..." "[IframeController] 📡 Mode Iframe détecté. Démarrage des listeners API..."
@ -31,9 +30,6 @@ export class IframeController {
const services = await Services.getInstance(); const services = await Services.getInstance();
const tokenService = await TokenService.getInstance(); const tokenService = await TokenService.getInstance();
/**
* Fonction centralisée pour envoyer des réponses d'erreur à la fenêtre parente (l'application A).
*/
const errorResponse = ( const errorResponse = (
errorMsg: string, errorMsg: string,
origin: string, origin: string,
@ -52,43 +48,35 @@ export class IframeController {
); );
}; };
// Helper pour vérifier le token avant chaque action sensible
const withToken = async ( const withToken = async (
event: MessageEvent, event: MessageEvent,
action: () => Promise<void> action: () => Promise<void>
) => { ) => {
const { accessToken } = event.data; const { accessToken } = event.data;
// On vérifie si le token est présent ET valide pour l'origine de l'iframe
if ( if (
!accessToken || !accessToken ||
!(await tokenService.validateToken(accessToken, event.origin)) !(await tokenService.validateToken(accessToken, event.origin))
) { ) {
throw new Error("Invalid or expired session token"); throw new Error("Invalid or expired session token");
} }
// Si tout est bon, on exécute l'action
await action(); await action();
}; };
// --- Définitions des gestionnaires (Handlers) --- // --- HANDLERS ---
const handleRequestLink = async (event: MessageEvent) => { const handleRequestLink = async (event: MessageEvent) => {
console.log( console.log(
`[Router:API] 📨 Message ${MessageType.REQUEST_LINK} reçu de ${event.origin}` `[Router:API] 📨 Message ${MessageType.REQUEST_LINK} reçu de ${event.origin}`
); );
// 1. Vérifier si l'appareil est DÉJÀ appairé (cas de la 2ème connexion)
const device = await services.getDeviceFromDatabase(); const device = await services.getDeviceFromDatabase();
if (device && device.pairing_process_commitment) { if (device && device.pairing_process_commitment) {
console.log( console.log(
"[Router:API] Appareil déjà appairé. Pas besoin d'attendre home.ts." "[Router:API] Appareil déjà appairé. Pas besoin d'attendre home.ts."
); );
// On saute l'attente et on passe directement à la suite.
} else { } else {
// 2. Cas de la 1ère connexion (appareil non appairé)
// On doit attendre que home.ts (auto-pairing) ait fini son travail.
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
// Fonction de nettoyage pour éviter les fuites de mémoire
const cleanup = () => { const cleanup = () => {
document.removeEventListener( document.removeEventListener(
"app:pairing-ready", "app:pairing-ready",
@ -97,7 +85,6 @@ export class IframeController {
clearTimeout(timeoutId); clearTimeout(timeoutId);
}; };
// Le gestionnaire de l'événement
const handler = (e: CustomEvent) => { const handler = (e: CustomEvent) => {
cleanup(); cleanup();
if (e.detail && e.detail.success) { if (e.detail && e.detail.success) {
@ -107,13 +94,11 @@ export class IframeController {
} }
}; };
// Timeout de sécurité (5 secondes)
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
cleanup(); cleanup();
reject(new Error("Auto-pairing timed out (Event not received)")); reject(new Error("Auto-pairing timed out (Event not received)"));
}, 5000); }, 5000);
// On écoute l'événement qu'on a créé dans Home.ts
document.addEventListener( document.addEventListener(
"app:pairing-ready", "app:pairing-ready",
handler as EventListener handler as EventListener
@ -124,7 +109,6 @@ export class IframeController {
} }
console.log(`[Router:API] Traitement de la liaison...`); console.log(`[Router:API] Traitement de la liaison...`);
const result = true; // Auto-confirmation
const tokens = await tokenService.generateSessionToken(event.origin); const tokens = await tokenService.generateSessionToken(event.origin);
window.parent.postMessage( window.parent.postMessage(
@ -144,7 +128,8 @@ export class IframeController {
const handleCreatePairing = async (event: MessageEvent) => { const handleCreatePairing = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PAIRING} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PAIRING} reçu`);
if (services.isPaired()) { // 🔥 CORRECTION TS2801 : Ajout de await
if (await services.isPaired()) {
throw new Error( throw new Error(
"Device already paired — ignoring CREATE_PAIRING request" "Device already paired — ignoring CREATE_PAIRING request"
); );
@ -153,7 +138,9 @@ export class IframeController {
await withToken(event, async () => { await withToken(event, async () => {
console.log("[Router:API] 🚀 Démarrage du processus d'appairage..."); console.log("[Router:API] 🚀 Démarrage du processus d'appairage...");
const myAddress = services.getDeviceAddress(); // 🔥 CORRECTION TS2322 : Ajout de await pour récupérer la string
const myAddress = await services.getDeviceAddress();
console.log("[Router:API] 1/7: Création du processus de pairing..."); console.log("[Router:API] 1/7: Création du processus de pairing...");
const createPairingProcessReturn = await services.createPairingProcess( const createPairingProcessReturn = await services.createPairingProcess(
"", "",
@ -172,7 +159,8 @@ export class IframeController {
console.log(`[Router:API] 2/7: Processus ${pairingId} créé.`); console.log(`[Router:API] 2/7: Processus ${pairingId} créé.`);
console.log("[Router:API] 3/7: Enregistrement local de l'appareil..."); console.log("[Router:API] 3/7: Enregistrement local de l'appareil...");
services.pairDevice(pairingId, [myAddress]); // 🔥 CORRECTION TS2322 : myAddress est maintenant une string, plus une Promise
await services.pairDevice(pairingId, [myAddress]);
console.log( console.log(
"[Router:API] 4/7: Traitement du retour (handleApiReturn)..." "[Router:API] 4/7: Traitement du retour (handleApiReturn)..."
@ -194,7 +182,6 @@ export class IframeController {
await services.handleApiReturn(approveChangeReturn); await services.handleApiReturn(approveChangeReturn);
console.log("[Router:API] 7/7: Confirmation finale du pairing..."); console.log("[Router:API] 7/7: Confirmation finale du pairing...");
// await services.confirmPairing();
console.log("[Router:API] 🎉 Appairage terminé avec succès !"); console.log("[Router:API] 🎉 Appairage terminé avec succès !");
@ -211,7 +198,7 @@ export class IframeController {
console.log( console.log(
`[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu` `[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu`
); );
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
await withToken(event, async () => { await withToken(event, async () => {
const myProcesses = await services.getMyProcesses(); const myProcesses = await services.getMyProcesses();
@ -229,7 +216,7 @@ export class IframeController {
const handleGetProcesses = async (event: MessageEvent) => { const handleGetProcesses = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`);
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
await withToken(event, async () => { await withToken(event, async () => {
const processes = await services.getProcesses(); const processes = await services.getProcesses();
@ -247,7 +234,7 @@ export class IframeController {
const handleDecryptState = async (event: MessageEvent) => { const handleDecryptState = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`);
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
const { processId, stateId } = event.data; const { processId, stateId } = event.data;
@ -255,7 +242,9 @@ export class IframeController {
const process = await services.getProcess(processId); const process = await services.getProcess(processId);
if (!process) throw new Error("Can't find process"); if (!process) throw new Error("Can't find process");
const state = services.getStateFromId(process, stateId); // 🔥 CORRECTION TS2339 & TS2345 : Ajout de await car getStateFromId est async
const state = await services.getStateFromId(process, stateId);
if (!state) if (!state)
throw new Error(`Unknown state ${stateId} for process ${processId}`); throw new Error(`Unknown state ${stateId} for process ${processId}`);
@ -282,8 +271,7 @@ export class IframeController {
} }
} }
console.log( console.log(
`[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${ `[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length
Object.keys(res).length
} attribut(s) déchiffré(s).` } attribut(s) déchiffré(s).`
); );
@ -299,6 +287,7 @@ export class IframeController {
}; };
const handleValidateToken = async (event: MessageEvent) => { const handleValidateToken = async (event: MessageEvent) => {
// ... (Code identique, pas d'erreurs ici normalement)
console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_TOKEN} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_TOKEN} reçu`);
const accessToken = event.data.accessToken; const accessToken = event.data.accessToken;
const refreshToken = event.data.refreshToken; const refreshToken = event.data.refreshToken;
@ -324,6 +313,7 @@ export class IframeController {
}; };
const handleRenewToken = async (event: MessageEvent) => { const handleRenewToken = async (event: MessageEvent) => {
// ... (Code identique)
console.log(`[Router:API] 📨 Message ${MessageType.RENEW_TOKEN} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.RENEW_TOKEN} reçu`);
const refreshToken = event.data.refreshToken; const refreshToken = event.data.refreshToken;
if (!refreshToken) throw new Error("No refresh token provided"); if (!refreshToken) throw new Error("No refresh token provided");
@ -349,42 +339,24 @@ export class IframeController {
const handleGetPairingId = async (event: MessageEvent) => { const handleGetPairingId = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.GET_PAIRING_ID} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.GET_PAIRING_ID} reçu`);
const maxRetries = 10; const maxRetries = 10;
const retryDelay = 300; const retryDelay = 300;
let pairingId: string | null = null; let pairingId: string | null = null;
// Boucle de polling
for (let i = 0; i < maxRetries; i++) { for (let i = 0; i < maxRetries; i++) {
// On lit DIRECTEMENT la BDD (la "source de vérité")
const device = await services.getDeviceFromDatabase(); const device = await services.getDeviceFromDatabase();
// On vérifie si l'ID est maintenant présent dans la BDD
if (device && device.pairing_process_commitment) { if (device && device.pairing_process_commitment) {
// SUCCÈS ! L'ID est dans la BDD
pairingId = device.pairing_process_commitment; pairingId = device.pairing_process_commitment;
console.log( console.log(
`[Router:API] GET_PAIRING_ID: ID trouvé en BDD (tentative ${ `[Router:API] GET_PAIRING_ID: ID trouvé en BDD (tentative ${i + 1
i + 1
}/${maxRetries})` }/${maxRetries})`
); );
break; // On sort de la boucle break;
} }
// Si non trouvé, on patiente
console.warn(
`[Router:API] GET_PAIRING_ID: Non trouvé en BDD, nouvelle tentative... (${
i + 1
}/${maxRetries})`
);
await new Promise((resolve) => setTimeout(resolve, retryDelay)); await new Promise((resolve) => setTimeout(resolve, retryDelay));
} }
// Si la boucle se termine sans succès
if (!pairingId) { if (!pairingId) {
console.error(
`[Router:API] GET_PAIRING_ID: Échec final, non trouvé en BDD après ${maxRetries} tentatives.`
);
throw new Error("Device not paired"); throw new Error("Device not paired");
} }
@ -402,20 +374,16 @@ export class IframeController {
const handleCreateProcess = async (event: MessageEvent) => { const handleCreateProcess = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`);
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
const { processData, privateFields, roles } = event.data; const { processData, privateFields, roles } = event.data;
await withToken(event, async () => { await withToken(event, async () => {
console.log(
"[Router:API] 🚀 Démarrage de la création de processus standard..."
);
const { privateData, publicData } = splitPrivateData( const { privateData, publicData } = splitPrivateData(
processData, processData,
privateFields privateFields
); );
console.log("[Router:API] 1/2: Création du processus...");
const createProcessReturn = await services.createProcess( const createProcessReturn = await services.createProcess(
privateData, privateData,
publicData, publicData,
@ -427,12 +395,8 @@ export class IframeController {
const processId = createProcessReturn.updated_process.process_id; const processId = createProcessReturn.updated_process.process_id;
const process = createProcessReturn.updated_process.current_process; const process = createProcessReturn.updated_process.current_process;
const stateId = process.states[0].state_id;
console.log(
`[Router:API] 2/2: Processus ${processId} créé. Traitement...`
);
await services.handleApiReturn(createProcessReturn);
await services.handleApiReturn(createProcessReturn);
console.log(`[Router:API] 🎉 Processus ${processId} créé.`); console.log(`[Router:API] 🎉 Processus ${processId} créé.`);
const res = { const res = {
@ -454,7 +418,7 @@ export class IframeController {
const handleNotifyUpdate = async (event: MessageEvent) => { const handleNotifyUpdate = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`);
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
const { processId, stateId } = event.data; const { processId, stateId } = event.data;
@ -476,7 +440,7 @@ export class IframeController {
const handleValidateState = async (event: MessageEvent) => { const handleValidateState = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`);
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
const { processId, stateId } = event.data; const { processId, stateId } = event.data;
@ -497,30 +461,23 @@ export class IframeController {
const handleUpdateProcess = async (event: MessageEvent) => { const handleUpdateProcess = async (event: MessageEvent) => {
console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`); console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`);
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
const { processId, newData, privateFields, roles } = event.data; const { processId, newData, privateFields, roles } = event.data;
await withToken(event, async () => { await withToken(event, async () => {
console.log(
`[Router:API] 🔄 Transfert de la mise à jour de ${processId} au service...`
);
// Le service gère maintenant tout : récupération, réparation d'état, et mise à jour.
const res = await services.updateProcess( const res = await services.updateProcess(
processId, processId,
newData, newData,
privateFields, privateFields,
roles roles
); );
// Nous appelons handleApiReturn ici, comme avant.
await services.handleApiReturn(res); await services.handleApiReturn(res);
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.PROCESS_UPDATED, type: MessageType.PROCESS_UPDATED,
updatedProcess: res.updated_process, // res vient directement de l'appel service updatedProcess: res.updated_process,
messageId: event.data.messageId, messageId: event.data.messageId,
}, },
event.origin event.origin
@ -532,12 +489,12 @@ export class IframeController {
console.log( console.log(
`[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu` `[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu`
); );
if (!services.isPaired()) throw new Error("Device not paired"); if (!(await services.isPaired())) throw new Error("Device not paired");
const { encodedData } = event.data; const { encodedData } = event.data;
await withToken(event, async () => { await withToken(event, async () => {
const decodedData = services.decodeValue(encodedData); const decodedData = await services.decodeValue(encodedData);
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.PUBLIC_DATA_DECODED, type: MessageType.PUBLIC_DATA_DECODED,
@ -554,7 +511,7 @@ export class IframeController {
const { commitedIn, label, fileBlob } = event.data; const { commitedIn, label, fileBlob } = event.data;
await withToken(event, async () => { await withToken(event, async () => {
const hash = services.getHashForFile(commitedIn, label, fileBlob); const hash = await services.getHashForFile(commitedIn, label, fileBlob);
window.parent.postMessage( window.parent.postMessage(
{ {
type: MessageType.VALUE_HASHED, type: MessageType.VALUE_HASHED,
@ -573,7 +530,7 @@ export class IframeController {
const { processState, attributeName } = event.data; const { processState, attributeName } = event.data;
await withToken(event, async () => { await withToken(event, async () => {
const proof = services.getMerkleProofForFile( const proof = await services.getMerkleProofForFile(
processState, processState,
attributeName attributeName
); );
@ -602,7 +559,7 @@ export class IframeController {
throw new Error("Provided merkleProof is not a valid json object"); throw new Error("Provided merkleProof is not a valid json object");
} }
const res = services.validateMerkleProof( const res = await services.validateMerkleProof(
parsedMerkleProof, parsedMerkleProof,
documentHash documentHash
); );
@ -617,13 +574,12 @@ export class IframeController {
}); });
}; };
// --- Le "Switchyard" : il reçoit tous les messages et les dispatche ---
window.removeEventListener("message", handleMessage); window.removeEventListener("message", handleMessage);
window.addEventListener("message", handleMessage); window.addEventListener("message", handleMessage);
async function handleMessage(event: MessageEvent) { async function handleMessage(event: MessageEvent) {
try { try {
// Switch/case inchangé ...
switch (event.data.type) { switch (event.data.type) {
case MessageType.REQUEST_LINK: case MessageType.REQUEST_LINK:
await handleRequestLink(event); await handleRequestLink(event);
@ -674,7 +630,7 @@ export class IframeController {
await handleValidateMerkleProof(event); await handleValidateMerkleProof(event);
break; break;
default: default:
console.warn("[Router:API] ⚠️ Message non géré reçu:", event.data); // console.warn("[Router:API] ⚠️ Message non géré reçu:", event.data);
} }
} catch (error: any) { } catch (error: any) {
const errorMsg = `[Router:API] 💥 Erreur de haut niveau: ${error}`; const errorMsg = `[Router:API] 💥 Erreur de haut niveau: ${error}`;