diff --git a/src/services/iframe-controller.service.ts b/src/services/iframe-controller.service.ts index c352716..a79b2f0 100644 --- a/src/services/iframe-controller.service.ts +++ b/src/services/iframe-controller.service.ts @@ -10,6 +10,7 @@ export class IframeController { static async init() { if (this.isInitialized) return; + this.isInitialized = true; // Marquage immédiat pour éviter les doubles appels if (window.self !== window.top) { console.log( @@ -18,25 +19,24 @@ export class IframeController { await IframeController.registerAllListeners(); } else { console.log( - "[IframeController] ℹ️ Mode Standalone (pas d'iframe). Listeners API inactifs." + "[IframeController] ℹ️ Mode Standalone. 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(); + // --- UTILS --- + const errorResponse = ( errorMsg: string, origin: string, messageId?: string ) => { console.error( - `[Router:API] 📤 Envoi Erreur: ${errorMsg} (Origine: ${origin}, MsgID: ${messageId})` + `[Router:API] 📤 Envoi Erreur: ${errorMsg} (Origine: ${origin})` ); window.parent.postMessage( { @@ -44,7 +44,7 @@ export class IframeController { error: errorMsg, messageId, }, - origin + origin // On renvoie à l'origine exacte reçue, même en mode * ); }; @@ -53,6 +53,7 @@ export class IframeController { action: () => Promise ) => { const { accessToken } = event.data; + // Note: Pour la démo, on accepte souvent tout, mais la validation du token reste une bonne pratique if ( !accessToken || !(await tokenService.validateToken(accessToken, event.origin)) @@ -71,11 +72,11 @@ export class IframeController { const device = await services.getDeviceFromDatabase(); + // Si déjà appairé, on accepte immédiatement sans attendre l'événement UI if (device && device.pairing_process_commitment) { - console.log( - "[Router:API] Appareil déjà appairé. Pas besoin d'attendre home.ts." - ); + console.log("[Router:API] Appareil déjà appairé. Liaison immédiate."); } else { + // Sinon, on attend que l'utilisateur clique sur "Lier" dans l'interface (Home.ts) await new Promise((resolve, reject) => { const cleanup = () => { document.removeEventListener( @@ -96,21 +97,19 @@ export class IframeController { const timeoutId = setTimeout(() => { cleanup(); - reject(new Error("Auto-pairing timed out (Event not received)")); - }, 5000); + reject(new Error("Auto-pairing timed out (User action missing)")); + }, 60000); // Augmenté à 60s pour laisser le temps à l'utilisateur de cliquer 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...`); - + console.log(`[Router:API] Génération des tokens de session...`); const tokens = await tokenService.generateSessionToken(event.origin); + window.parent.postMessage( { type: MessageType.LINK_ACCEPTED, @@ -120,15 +119,11 @@ export class IframeController { }, 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" @@ -136,12 +131,9 @@ export class IframeController { } await withToken(event, async () => { - console.log("[Router:API] 🚀 Démarrage du processus d'appairage..."); + const myAddress = await services.getDeviceAddress(); // string - // 🔥 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] Création du processus de pairing..."); const createPairingProcessReturn = await services.createPairingProcess( "", [myAddress] @@ -151,58 +143,44 @@ export class IframeController { 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" - ); + throw new Error("Pairing process creation failed (invalid 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..."); + // Auto-approbation du premier état (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é !"); - 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); + window.parent.postMessage( + { + type: MessageType.PAIRING_CREATED, + pairingId, + messageId: event.data.messageId, + }, + 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, @@ -215,12 +193,9 @@ export class IframeController { }; 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, @@ -233,27 +208,21 @@ export class IframeController { }; 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}`); - 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 = {}; + + // Déchiffrement des attributs privés for (const attribute of Object.keys(state.pcd_commitment)) { if ( attribute === "roles" || @@ -270,10 +239,6 @@ export class IframeController { res[attribute] = decryptedAttribute; } } - console.log( - `[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length - } attribut(s) déchiffré(s).` - ); window.parent.postMessage( { @@ -286,79 +251,21 @@ export class IframeController { }); }; - 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`); + // Logique de retry si la DB est lente au démarrage 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)); + await new Promise((resolve) => setTimeout(resolve, 300)); } - if (!pairingId) { - throw new Error("Device not paired"); - } + if (!pairingId) throw new Error("Device not paired (Timeout)"); await withToken(event, async () => { window.parent.postMessage( @@ -373,9 +280,7 @@ export class IframeController { }; 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 () => { @@ -383,32 +288,24 @@ export class IframeController { processData, privateFields ); - const createProcessReturn = await services.createProcess( privateData, publicData, roles ); - if (!createProcessReturn.updated_process) { - throw new Error("Empty updated_process in createProcessReturn"); - } + + if (!createProcessReturn.updated_process) + throw new Error("Process creation failed"); 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, + processCreated: { processId, process, processData }, messageId: event.data.messageId, }, event.origin @@ -416,170 +313,18 @@ export class IframeController { }); }; - 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); + // --- MAIN MESSAGE DISPATCHER --- async function handleMessage(event: MessageEvent) { + // 🛡️ SÉCURITÉ : Ignorer les messages qui ne sont pas des objets (ex: extensions Chrome) + if (!event.data || typeof event.data !== "object") { + return; + } + + // 🛡️ FILTRAGE : On ne traite que les messages avec un 'type' connu + if (!event.data.type) return; + try { - // Switch/case inchangé ... switch (event.data.type) { case MessageType.REQUEST_LINK: await handleRequestLink(event); @@ -596,56 +341,70 @@ export class IframeController { 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); + + // Cas simples (tokens, notifications...) + case MessageType.VALIDATE_TOKEN: + const { accessToken, refreshToken } = event.data; + if (!accessToken || !refreshToken) + throw new Error("Tokens missing"); + const isValid = await tokenService.validateToken( + accessToken, + event.origin + ); + window.parent.postMessage( + { + type: MessageType.VALIDATE_TOKEN, + isValid, + accessToken, + refreshToken, + messageId: event.data.messageId, + }, + event.origin + ); 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); + + case MessageType.RENEW_TOKEN: + if (!event.data.refreshToken) throw new Error("No refresh token"); + const newAccess = await tokenService.refreshAccessToken( + event.data.refreshToken, + event.origin + ); + if (!newAccess) throw new Error("Refresh failed"); + window.parent.postMessage( + { + type: MessageType.RENEW_TOKEN, + accessToken: newAccess, + refreshToken: event.data.refreshToken, + messageId: event.data.messageId, + }, + event.origin + ); break; default: - // console.warn("[Router:API] ⚠️ Message non géré reçu:", event.data); + // Silence sur les types inconnus (peut être un autre protocole) + break; } } catch (error: any) { - const errorMsg = `[Router:API] 💥 Erreur de haut niveau: ${error}`; - errorResponse(errorMsg, event.origin, event.data.messageId); + // En cas d'erreur métier, on prévient le parent + errorResponse( + String(error.message || error), + 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." - ); + // Ajout du listener unique + window.addEventListener("message", handleMessage); + + // Handshake final : "Je suis prêt" + window.parent.postMessage({ type: MessageType.LISTENING }, "*"); + console.log("[Router:API] ✅ Listeners actifs. Handshake envoyé."); } -} \ No newline at end of file +}