import { MessageType } from "../types/index"; import Services from "./service"; import TokenService from "./token.service"; import { cleanSubscriptions } from "../utils/subscription.utils"; import { splitPrivateData, isValid32ByteHex } from "../utils/service.utils"; import { MerkleProofResult } from "../../pkg/sdk_client"; export class IframeController { private static isInitialized = false; static async init() { if (this.isInitialized) return; if (window.self !== window.top) { console.log( "[IframeController] 📡 Mode Iframe détecté. Démarrage des listeners API..." ); await IframeController.registerAllListeners(); } else { console.log( "[IframeController] ℹ️ Mode Standalone (pas d'iframe). Listeners API inactifs." ); } } private static async registerAllListeners() { console.log( "[Router:API] 🎧 Enregistrement des gestionnaires de messages (postMessage)..." ); const services = await Services.getInstance(); const tokenService = await TokenService.getInstance(); const errorResponse = ( errorMsg: string, origin: string, messageId?: string ) => { console.error( `[Router:API] 📤 Envoi Erreur: ${errorMsg} (Origine: ${origin}, MsgID: ${messageId})` ); window.parent.postMessage( { type: MessageType.ERROR, error: errorMsg, messageId, }, origin ); }; const withToken = async ( event: MessageEvent, action: () => Promise ) => { const { accessToken } = event.data; if ( !accessToken || !(await tokenService.validateToken(accessToken, event.origin)) ) { throw new Error("Invalid or expired session token"); } await action(); }; // --- HANDLERS --- const handleRequestLink = async (event: MessageEvent) => { console.log( `[Router:API] 📨 Message ${MessageType.REQUEST_LINK} reçu de ${event.origin}` ); const device = await services.getDeviceFromDatabase(); if (device && device.pairing_process_commitment) { console.log( "[Router:API] Appareil déjà appairé. Pas besoin d'attendre home.ts." ); } else { await new Promise((resolve, reject) => { const cleanup = () => { document.removeEventListener( "app:pairing-ready", handler as EventListener ); clearTimeout(timeoutId); }; const handler = (e: CustomEvent) => { cleanup(); if (e.detail && e.detail.success) { resolve(); } else { reject(new Error(e.detail?.error || "Auto-pairing failed")); } }; const timeoutId = setTimeout(() => { cleanup(); reject(new Error("Auto-pairing timed out (Event not received)")); }, 5000); document.addEventListener( "app:pairing-ready", handler as EventListener ); }); console.log(`[Router:API] Feu vert de home.ts reçu !`); } console.log(`[Router:API] Traitement de la liaison...`); const tokens = await tokenService.generateSessionToken(event.origin); window.parent.postMessage( { type: MessageType.LINK_ACCEPTED, accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, messageId: event.data.messageId, }, event.origin ); console.log( `[Router:API] ✅ ${MessageType.REQUEST_LINK} accepté et jetons envoyés.` ); }; const handleCreatePairing = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PAIRING} reçu`); // 🔥 CORRECTION TS2801 : Ajout de await if (await services.isPaired()) { throw new Error( "Device already paired — ignoring CREATE_PAIRING request" ); } await withToken(event, async () => { console.log("[Router:API] 🚀 Démarrage du processus d'appairage..."); // 🔥 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..."); const createPairingProcessReturn = await services.createPairingProcess( "", [myAddress] ); const pairingId = createPairingProcessReturn.updated_process?.process_id; const stateId = createPairingProcessReturn.updated_process ?.current_process?.states[0]?.state_id as string; if (!pairingId || !stateId) { throw new Error( "Pairing process creation failed to return valid IDs" ); } console.log(`[Router:API] 2/7: Processus ${pairingId} créé.`); console.log("[Router:API] 3/7: Enregistrement local de l'appareil..."); // 🔥 CORRECTION TS2322 : myAddress est maintenant une string, plus une Promise await services.pairDevice(pairingId, [myAddress]); console.log( "[Router:API] 4/7: Traitement du retour (handleApiReturn)..." ); await services.handleApiReturn(createPairingProcessReturn); console.log("[Router:API] 5/7: Création de la mise à jour PRD..."); const createPrdUpdateReturn = await services.createPrdUpdate( pairingId, stateId ); await services.handleApiReturn(createPrdUpdateReturn); console.log("[Router:API] 6/7: Approbation du changement..."); const approveChangeReturn = await services.approveChange( pairingId, stateId ); await services.handleApiReturn(approveChangeReturn); console.log("[Router:API] 7/7: Confirmation finale du pairing..."); console.log("[Router:API] 🎉 Appairage terminé avec succès !"); const successMsg = { type: MessageType.PAIRING_CREATED, pairingId, messageId: event.data.messageId, }; window.parent.postMessage(successMsg, event.origin); }); }; const handleGetMyProcesses = async (event: MessageEvent) => { console.log( `[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu` ); if (!(await services.isPaired())) throw new Error("Device not paired"); await withToken(event, async () => { const myProcesses = await services.getMyProcesses(); window.parent.postMessage( { type: MessageType.GET_MY_PROCESSES, myProcesses, messageId: event.data.messageId, }, event.origin ); }); }; const handleGetProcesses = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`); if (!(await services.isPaired())) throw new Error("Device not paired"); await withToken(event, async () => { const processes = await services.getProcesses(); window.parent.postMessage( { type: MessageType.PROCESSES_RETRIEVED, processes, messageId: event.data.messageId, }, event.origin ); }); }; const handleDecryptState = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`); if (!(await services.isPaired())) throw new Error("Device not paired"); const { processId, stateId } = event.data; await withToken(event, async () => { const process = await services.getProcess(processId); if (!process) throw new Error("Can't find process"); // 🔥 CORRECTION TS2339 & TS2345 : Ajout de await car getStateFromId est async const state = await services.getStateFromId(process, stateId); if (!state) throw new Error(`Unknown state ${stateId} for process ${processId}`); console.log( `[Router:API] 🔐 Démarrage du déchiffrement pour ${processId}` ); await services.ensureConnections(process, stateId); const res: Record = {}; for (const attribute of Object.keys(state.pcd_commitment)) { if ( attribute === "roles" || (state.public_data && state.public_data[attribute]) ) { continue; } const decryptedAttribute = await services.decryptAttribute( processId, state, attribute ); if (decryptedAttribute) { res[attribute] = decryptedAttribute; } } console.log( `[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length } attribut(s) déchiffré(s).` ); window.parent.postMessage( { type: MessageType.DATA_RETRIEVED, data: res, messageId: event.data.messageId, }, event.origin ); }); }; const handleValidateToken = async (event: MessageEvent) => { // ... (Code identique, pas d'erreurs ici normalement) console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_TOKEN} reçu`); const accessToken = event.data.accessToken; const refreshToken = event.data.refreshToken; if (!accessToken || !refreshToken) { throw new Error("Missing access, refresh token or both"); } const isValid = await tokenService.validateToken( accessToken, event.origin ); console.log(`[Router:API] 🔑 Validation Jeton: ${isValid}`); window.parent.postMessage( { type: MessageType.VALIDATE_TOKEN, accessToken: accessToken, refreshToken: refreshToken, isValid: isValid, messageId: event.data.messageId, }, event.origin ); }; const handleRenewToken = async (event: MessageEvent) => { // ... (Code identique) console.log(`[Router:API] 📨 Message ${MessageType.RENEW_TOKEN} reçu`); const refreshToken = event.data.refreshToken; if (!refreshToken) throw new Error("No refresh token provided"); const newAccessToken = await tokenService.refreshAccessToken( refreshToken, event.origin ); if (!newAccessToken) throw new Error("Failed to refresh token (invalid refresh token)"); console.log(`[Router:API] 🔑 Jeton d'accès renouvelé.`); window.parent.postMessage( { type: MessageType.RENEW_TOKEN, accessToken: newAccessToken, refreshToken: refreshToken, messageId: event.data.messageId, }, event.origin ); }; const handleGetPairingId = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.GET_PAIRING_ID} reçu`); const maxRetries = 10; const retryDelay = 300; let pairingId: string | null = null; for (let i = 0; i < maxRetries; i++) { const device = await services.getDeviceFromDatabase(); if (device && device.pairing_process_commitment) { pairingId = device.pairing_process_commitment; console.log( `[Router:API] GET_PAIRING_ID: ID trouvé en BDD (tentative ${i + 1 }/${maxRetries})` ); break; } await new Promise((resolve) => setTimeout(resolve, retryDelay)); } if (!pairingId) { throw new Error("Device not paired"); } await withToken(event, async () => { window.parent.postMessage( { type: MessageType.GET_PAIRING_ID, userPairingId: pairingId, messageId: event.data.messageId, }, event.origin ); }); }; const handleCreateProcess = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`); if (!(await services.isPaired())) throw new Error("Device not paired"); const { processData, privateFields, roles } = event.data; await withToken(event, async () => { const { privateData, publicData } = splitPrivateData( processData, privateFields ); const createProcessReturn = await services.createProcess( privateData, publicData, roles ); if (!createProcessReturn.updated_process) { throw new Error("Empty updated_process in createProcessReturn"); } const processId = createProcessReturn.updated_process.process_id; const process = createProcessReturn.updated_process.current_process; await services.handleApiReturn(createProcessReturn); console.log(`[Router:API] 🎉 Processus ${processId} créé.`); const res = { processId, process, processData, }; window.parent.postMessage( { type: MessageType.PROCESS_CREATED, processCreated: res, messageId: event.data.messageId, }, event.origin ); }); }; const handleNotifyUpdate = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`); if (!(await services.isPaired())) throw new Error("Device not paired"); const { processId, stateId } = event.data; await withToken(event, async () => { if (!isValid32ByteHex(stateId)) throw new Error("Invalid state id"); const res = await services.createPrdUpdate(processId, stateId); await services.handleApiReturn(res); window.parent.postMessage( { type: MessageType.UPDATE_NOTIFIED, messageId: event.data.messageId, }, event.origin ); }); }; const handleValidateState = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`); if (!(await services.isPaired())) throw new Error("Device not paired"); const { processId, stateId } = event.data; await withToken(event, async () => { const res = await services.approveChange(processId, stateId); await services.handleApiReturn(res); window.parent.postMessage( { type: MessageType.STATE_VALIDATED, validatedProcess: res.updated_process, messageId: event.data.messageId, }, event.origin ); }); }; const handleUpdateProcess = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`); if (!(await services.isPaired())) throw new Error("Device not paired"); const { processId, newData, privateFields, roles } = event.data; await withToken(event, async () => { const res = await services.updateProcess( processId, newData, privateFields, roles ); await services.handleApiReturn(res); window.parent.postMessage( { type: MessageType.PROCESS_UPDATED, updatedProcess: res.updated_process, messageId: event.data.messageId, }, event.origin ); }); }; const handleDecodePublicData = async (event: MessageEvent) => { console.log( `[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu` ); if (!(await services.isPaired())) throw new Error("Device not paired"); const { encodedData } = event.data; await withToken(event, async () => { const decodedData = await services.decodeValue(encodedData); window.parent.postMessage( { type: MessageType.PUBLIC_DATA_DECODED, decodedData, messageId: event.data.messageId, }, event.origin ); }); }; const handleHashValue = async (event: MessageEvent) => { console.log(`[Router:API] 📨 Message ${MessageType.HASH_VALUE} reçu`); const { commitedIn, label, fileBlob } = event.data; await withToken(event, async () => { const hash = await services.getHashForFile(commitedIn, label, fileBlob); window.parent.postMessage( { type: MessageType.VALUE_HASHED, hash, messageId: event.data.messageId, }, event.origin ); }); }; const handleGetMerkleProof = async (event: MessageEvent) => { console.log( `[Router:API] 📨 Message ${MessageType.GET_MERKLE_PROOF} reçu` ); const { processState, attributeName } = event.data; await withToken(event, async () => { const proof = await services.getMerkleProofForFile( processState, attributeName ); window.parent.postMessage( { type: MessageType.MERKLE_PROOF_RETRIEVED, proof, messageId: event.data.messageId, }, event.origin ); }); }; const handleValidateMerkleProof = async (event: MessageEvent) => { console.log( `[Router:API] 📨 Message ${MessageType.VALIDATE_MERKLE_PROOF} reçu` ); const { merkleProof, documentHash } = event.data; await withToken(event, async () => { let parsedMerkleProof: MerkleProofResult; try { parsedMerkleProof = JSON.parse(merkleProof); } catch (e) { throw new Error("Provided merkleProof is not a valid json object"); } const res = await services.validateMerkleProof( parsedMerkleProof, documentHash ); window.parent.postMessage( { type: MessageType.MERKLE_PROOF_VALIDATED, isValid: res, messageId: event.data.messageId, }, event.origin ); }); }; window.removeEventListener("message", handleMessage); window.addEventListener("message", handleMessage); async function handleMessage(event: MessageEvent) { try { // Switch/case inchangé ... switch (event.data.type) { case MessageType.REQUEST_LINK: await handleRequestLink(event); break; case MessageType.CREATE_PAIRING: await handleCreatePairing(event); break; case MessageType.GET_MY_PROCESSES: await handleGetMyProcesses(event); break; case MessageType.GET_PROCESSES: await handleGetProcesses(event); break; case MessageType.RETRIEVE_DATA: await handleDecryptState(event); break; case MessageType.VALIDATE_TOKEN: await handleValidateToken(event); break; case MessageType.RENEW_TOKEN: await handleRenewToken(event); break; case MessageType.GET_PAIRING_ID: await handleGetPairingId(event); break; case MessageType.CREATE_PROCESS: await handleCreateProcess(event); break; case MessageType.NOTIFY_UPDATE: await handleNotifyUpdate(event); break; case MessageType.VALIDATE_STATE: await handleValidateState(event); break; case MessageType.UPDATE_PROCESS: await handleUpdateProcess(event); break; case MessageType.DECODE_PUBLIC_DATA: await handleDecodePublicData(event); break; case MessageType.HASH_VALUE: await handleHashValue(event); break; case MessageType.GET_MERKLE_PROOF: await handleGetMerkleProof(event); break; case MessageType.VALIDATE_MERKLE_PROOF: await handleValidateMerkleProof(event); break; default: // console.warn("[Router:API] ⚠️ Message non géré reçu:", event.data); } } catch (error: any) { const errorMsg = `[Router:API] 💥 Erreur de haut niveau: ${error}`; errorResponse(errorMsg, event.origin, event.data.messageId); } } window.parent.postMessage( { type: MessageType.LISTENING, }, "*" ); console.log( "[Router:API] ✅ Tous les listeners sont actifs. Envoi du message LISTENING au parent." ); } }