ihm_client/src/services/iframe-controller.service.ts

651 lines
20 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<void>
) => {
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<void>((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<string, any> = {};
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."
);
}
}