885 lines
34 KiB
TypeScript
Executable File
885 lines
34 KiB
TypeScript
Executable File
// @ts-nocheck
|
||
|
||
import './4nk.css';
|
||
import { initHeader } from '../src/components/header/header';
|
||
/*import { initChat } from '../src/pages/chat/chat';*/
|
||
import Database from './services/database.service';
|
||
import Services from './services/service';
|
||
import TokenService from './services/token';
|
||
import { cleanSubscriptions } from './utils/subscription.utils';
|
||
import { prepareAndSendPairingTx } from './utils/sp-address.utils';
|
||
import ModalService from './services/modal.service';
|
||
import { MessageType } from './models/process.model';
|
||
import { splitPrivateData, isValid32ByteHex } from './utils/service.utils';
|
||
import { MerkleProofResult } from 'pkg/sdk_client';
|
||
|
||
// ===================================================================================
|
||
// ## 🧭 1. Routage de Page (Navigation)
|
||
// ===================================================================================
|
||
|
||
const routes: { [key: string]: string } = {
|
||
home: '/src/pages/home/home.html',
|
||
process: '/src/pages/process/process.html',
|
||
'process-element': '/src/pages/process-element/process-element.html',
|
||
account: '/src/pages/account/account.html',
|
||
chat: '/src/pages/chat/chat.html',
|
||
signature: '/src/pages/signature/signature.html',
|
||
};
|
||
|
||
export let currentRoute = '';
|
||
|
||
export async function navigate(path: string) {
|
||
console.log(`[Router] 🧭 Navigation vers: ${path}`);
|
||
cleanSubscriptions();
|
||
cleanPage();
|
||
path = path.replace(/^\//, ''); // Retire le slash de début
|
||
|
||
// Gère les chemins simples ou avec paramètres (ex: 'process-element/123_456')
|
||
if (path.includes('/')) {
|
||
const parsedPath = path.split('/')[0];
|
||
if (!routes[parsedPath]) {
|
||
console.warn(`[Router] ⚠️ Route inconnue "${parsedPath}", redirection vers 'home'.`);
|
||
path = 'home';
|
||
}
|
||
} else if (!routes[path]) {
|
||
console.warn(`[Router] ⚠️ Route inconnue "${path}", redirection vers 'home'.`);
|
||
path = 'home';
|
||
}
|
||
|
||
await handleLocation(path);
|
||
}
|
||
|
||
async function handleLocation(path: string) {
|
||
// 1. Log de démarrage
|
||
console.log(`[Router:handleLocation] 🧭 Gestion de la nouvelle route: ${path}`);
|
||
|
||
const parsedPath = path.split('/');
|
||
const baseRoute = parsedPath[0];
|
||
currentRoute = baseRoute;
|
||
|
||
const routeHtml = routes[baseRoute] || routes['home'];
|
||
const content = document.getElementById('containerId');
|
||
if (!content) {
|
||
console.error('[Router] 💥 Erreur critique: div #containerId non trouvée !');
|
||
return;
|
||
} else {
|
||
// console.debug('[Router:handleLocation] ✅ conteneur #containerId trouvé.');
|
||
}
|
||
|
||
// --- Injection de Contenu ---
|
||
if (baseRoute === 'home') {
|
||
console.log('[Router:handleLocation] 🏠 Route "home" détectée. Importation dynamique de <login-4nk-component>...');
|
||
|
||
const { LoginComponent } = await import('./pages/home/home-component');
|
||
|
||
if (!customElements.get('login-4nk-component')) {
|
||
customElements.define('login-4nk-component', LoginComponent);
|
||
console.log('[Router:handleLocation] ℹ️ <login-4nk-component> défini.');
|
||
}
|
||
|
||
const container = document.querySelector('#containerId');
|
||
const accountComponent = document.createElement('login-4nk-component');
|
||
accountComponent.setAttribute('style', 'width: 100vw; height: 100vh; position: relative; grid-row: 2;');
|
||
if (container) {
|
||
container.appendChild(accountComponent);
|
||
// 4. Log de succès (le plus important pour ton bug)
|
||
console.log('[Router:handleLocation] ✅ <login-4nk-component> ajouté au DOM.');
|
||
}
|
||
} else if (baseRoute !== 'process') {
|
||
// 2. Log pour les autres pages
|
||
console.log(`[Router:handleLocation] 📄 Fetching et injection de HTML pour: ${routeHtml}`);
|
||
const html = await fetch(routeHtml).then((data) => data.text());
|
||
content.innerHTML = html;
|
||
console.log(`[Router:handleLocation] ✅ HTML pour "${baseRoute}" injecté.`);
|
||
} // (Le cas 'process' est géré plus bas dans le switch)
|
||
await new Promise(requestAnimationFrame);
|
||
console.log('[Router:handleLocation] 💉 Injection du header...');
|
||
injectHeader(); // --- Logique Spécifique à la Page (Lazy Loading) ---
|
||
|
||
console.log(`[Router] Initialisation de la logique pour la route: ${baseRoute}`);
|
||
switch (baseRoute) {
|
||
case 'process':
|
||
console.log('[Router:switch] 📦 Chargement de ProcessListComponent...');
|
||
const { ProcessListComponent } = await import('./pages/process/process-list-component');
|
||
const container2 = document.querySelector('#containerId');
|
||
const processListComponent = document.createElement('process-list-4nk-component');
|
||
|
||
if (!customElements.get('process-list-4nk-component')) {
|
||
console.log('[Router:switch] ℹ️ Définition de <process-list-4nk-component>...');
|
||
customElements.define('process-list-4nk-component', ProcessListComponent);
|
||
}
|
||
processListComponent.setAttribute('style', 'height: 100vh; position: relative; grid-row: 2; grid-column: 4;');
|
||
if (container2) {
|
||
container2.appendChild(processListComponent);
|
||
console.log('[Router:switch] ✅ <process-list-4nk-component> ajouté au DOM.');
|
||
}
|
||
break;
|
||
|
||
case 'process-element':
|
||
console.log(`[Router:switch] 📦 Chargement de <process-4nk-component>...`);
|
||
if (parsedPath[1]) {
|
||
// 1. Importe le composant (juste pour être sûr qu'il est défini)
|
||
const { ProcessElementComponent } = await import('./pages/process-element/process-component');
|
||
if (!customElements.get('process-4nk-component')) {
|
||
customElements.define('process-4nk-component', ProcessElementComponent);
|
||
}
|
||
|
||
// 2. Sépare les IDs
|
||
const [processId, stateId] = parsedPath[1].split('_');
|
||
|
||
// 3. Crée l'élément et passe les attributs
|
||
const container = document.querySelector('#containerId');
|
||
const processElement = document.createElement('process-4nk-component');
|
||
processElement.setAttribute('process-id', processId);
|
||
processElement.setAttribute('state-id', stateId);
|
||
|
||
if (container) {
|
||
container.appendChild(processElement);
|
||
console.log(`[Router:switch] ✅ <process-4nk-component> ajouté au DOM pour ${processId}_${stateId}`);
|
||
}
|
||
} else {
|
||
console.error('[Router] 💥 Route process-element appelée sans ID (ex: process-element/processId_stateId)');
|
||
navigate('process');
|
||
}
|
||
break;
|
||
|
||
case 'account':
|
||
console.log('[Router:switch] 📦 Chargement de AccountComponent...');
|
||
const { AccountComponent } = await import('./pages/account/account-component');
|
||
const accountContainer = document.querySelector('.parameter-list');
|
||
if (accountContainer) {
|
||
if (!customElements.get('account-component')) {
|
||
console.log('[Router:switch] ℹ️ Définition de <account-component>...');
|
||
customElements.define('account-component', AccountComponent);
|
||
}
|
||
const accountComponent = document.createElement('account-component');
|
||
accountContainer.appendChild(accountComponent);
|
||
console.log('[Router:switch] ✅ <account-component> ajouté au DOM.');
|
||
} else {
|
||
console.warn('[Router:switch] ⚠️ Impossible de trouver ".parameter-list" pour injecter AccountComponent.');
|
||
}
|
||
break;
|
||
|
||
case 'signature':
|
||
console.log('[Router:switch] 📦 Chargement de SignatureComponent...');
|
||
const { SignatureComponent } = await import('./pages/signature/signature-component');
|
||
const container = document.querySelector('.group-list');
|
||
if (container) {
|
||
if (!customElements.get('signature-component')) {
|
||
console.log('[Router:switch] ℹ️ Définition de <signature-component>...');
|
||
customElements.define('signature-component', SignatureComponent);
|
||
}
|
||
const signatureComponent = document.createElement('signature-component');
|
||
container.appendChild(signatureComponent);
|
||
console.log('[Router:switch] ✅ <signature-component> ajouté au DOM.');
|
||
} else {
|
||
console.warn('[Router:switch] ⚠️ Impossible de trouver ".group-list" pour injecter SignatureComponent.');
|
||
}
|
||
break;
|
||
|
||
// Log pour les cas non gérés
|
||
case 'home':
|
||
console.log('[Router:switch] ℹ️ Route "home". La logique a déjà été gérée avant le switch.');
|
||
break;
|
||
|
||
default:
|
||
console.log(`[Router:switch] ℹ️ Route "${baseRoute}" n'a pas de logique d'initialisation spécifique dans le switch.`);
|
||
}
|
||
}
|
||
|
||
window.onpopstate = async () => {
|
||
console.log('[Router] 🔄 Changement d\'état "popstate" (bouton retour/avant)');
|
||
const services = await Services.getInstance();
|
||
if (!services.isPaired()) {
|
||
handleLocation('home');
|
||
} else {
|
||
handleLocation('process');
|
||
}
|
||
};
|
||
|
||
// --- Fin de la section Routage de Page ---
|
||
// ===================================================================================
|
||
|
||
// ===================================================================================
|
||
// ## 🚀 2. Initialisation de l'Application
|
||
// ===================================================================================
|
||
|
||
export async function init(): Promise<void> {
|
||
console.log("[Router:Init] 🚀 Démarrage de l'application...");
|
||
try {
|
||
const services = await Services.getInstance();
|
||
(window as any).myService = services; // Pour débogage manuel
|
||
|
||
console.log('[Router:Init] 📦 Initialisation de la base de données (IndexedDB)...');
|
||
const db = await Database.getInstance();
|
||
db.registerServiceWorker('/src/service-workers/database.worker.js');
|
||
|
||
console.log("[Router:Init] 📱 Vérification de l'appareil (device)...");
|
||
const device = await services.getDeviceFromDatabase();
|
||
console.log('🚀 ~ setTimeout ~ device:', device); // Log original gardé
|
||
|
||
if (!device) {
|
||
console.log("[Router:Init] ✨ Aucun appareil trouvé. Création d'un nouvel appareil...");
|
||
await services.createNewDevice();
|
||
} else {
|
||
console.log("[Router:Init] 🔄 Restauration de l'appareil depuis la BDD...");
|
||
services.restoreDevice(device);
|
||
}
|
||
|
||
console.log("[Router:Init] 💾 Restauration de l'état (processus et secrets) depuis la BDD...");
|
||
await services.restoreProcessesFromDB();
|
||
await services.restoreSecretsFromDB();
|
||
|
||
console.log('[Router:Init] 🔌 Connexion à tous les relais...');
|
||
await services.connectAllRelays();
|
||
|
||
// S'enregistre comme "serveur" API si nous sommes dans une iframe
|
||
if (window.self !== window.top) {
|
||
console.log('[Router:Init] 📡 Nous sommes dans une iframe. Enregistrement des listeners API...');
|
||
await registerAllListeners();
|
||
} else {
|
||
console.log('[Router:Init] ℹ️ Exécution en mode standalone (pas dans une iframe).');
|
||
}
|
||
|
||
console.log("[Router:Init] 🧭 Vérification du statut d'appairage pour la navigation...");
|
||
if (services.isPaired()) {
|
||
console.log('[Router:Init] ✅ Appairé. Navigation vers "process".');
|
||
await navigate('process');
|
||
} else {
|
||
console.log('[Router:Init] ❌ Non appairé. Navigation vers "home".');
|
||
await navigate('home');
|
||
}
|
||
} catch (error) {
|
||
console.error("[Router:Init] 💥 ERREUR CRITIQUE PENDANT L'INITIALISATION:", error);
|
||
await navigate('home');
|
||
}
|
||
}
|
||
|
||
// --- Fin de la section Initialisation ---
|
||
// ===================================================================================
|
||
|
||
// ===================================================================================
|
||
// ## 📡 3. API (Message Listeners pour Iframe)
|
||
// ===================================================================================
|
||
|
||
export async function registerAllListeners() {
|
||
console.log('[Router:API] 🎧 Enregistrement des gestionnaires de messages (postMessage)...');
|
||
const services = await Services.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 = (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,
|
||
);
|
||
};
|
||
|
||
// --- Définitions des gestionnaires (Handlers) ---
|
||
|
||
const handleRequestLink = async (event: MessageEvent) => {
|
||
console.log(`[Router:API] 📨 Message ${MessageType.REQUEST_LINK} reçu de ${event.origin}`);
|
||
const modalService = await ModalService.getInstance();
|
||
const result = await modalService.showConfirmationModal(
|
||
{
|
||
title: 'Confirmation de liaison',
|
||
content: `
|
||
<div class="modal-confirmation">
|
||
<h3>Liaison avec ${event.origin}</h3>
|
||
<p>Vous êtes sur le point de lier l'identité numérique de la clé securisée propre à votre appareil avec ${event.origin}.</p>
|
||
<p>Cette action permettra à ${event.origin} d'intéragir avec votre appareil.</p>
|
||
<p>Voulez-vous continuer ?</p>
|
||
</div>
|
||
`,
|
||
confirmText: 'Ajouter un service',
|
||
cancelText: 'Annuler',
|
||
},
|
||
true,
|
||
);
|
||
|
||
if (!result) {
|
||
throw new Error('Failed to pair device: User refused to link');
|
||
}
|
||
|
||
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`);
|
||
|
||
if (services.isPaired()) {
|
||
throw new Error('Device already paired — ignoring CREATE_PAIRING request');
|
||
}
|
||
|
||
const { accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
console.log("[Router:API] 🚀 Démarrage du processus d'appairage...");
|
||
|
||
const myAddress = 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...");
|
||
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...');
|
||
await services.confirmPairing();
|
||
|
||
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 (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
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 (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
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 (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { processId, stateId, accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
const process = await services.getProcess(processId);
|
||
if (!process) throw new Error("Can't find process");
|
||
|
||
const state = 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}:${stateId}`);
|
||
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}:${stateId}. ${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) => {
|
||
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) => {
|
||
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`);
|
||
if (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
const userPairingId = services.getPairingProcessId();
|
||
window.parent.postMessage(
|
||
{
|
||
type: MessageType.GET_PAIRING_ID,
|
||
userPairingId,
|
||
messageId: event.data.messageId,
|
||
},
|
||
event.origin,
|
||
);
|
||
};
|
||
|
||
const handleCreateProcess = async (event: MessageEvent) => {
|
||
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`);
|
||
if (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { processData, privateFields, roles, accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
console.log('[Router:API] 🚀 Démarrage de la création de processus standard...');
|
||
const { privateData, publicData } = splitPrivateData(processData, privateFields);
|
||
|
||
console.log('[Router:API] 1/2: Création du processus...');
|
||
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;
|
||
const stateId = process.states[0].state_id;
|
||
console.log(`[Router:API] 2/2: Processus ${processId} créé. Traitement...`);
|
||
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 (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { processId, stateId, accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
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 (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { processId, stateId, accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
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 (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { processId, newData, privateFields, roles, accessToken } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
const process = await services.getProcess(processId);
|
||
if (!process) throw new Error('Process not found');
|
||
|
||
// --- Logique complexe de gestion d'état ---
|
||
// (Cette logique est très dense et pourrait être déplacée dans le service)
|
||
console.log(`[Router:API] 🔄 Calcul de la diff pour la mise à jour de ${processId}...`);
|
||
let lastState = services.getLastCommitedState(process);
|
||
if (!lastState) {
|
||
console.warn(`[Router:API] ⚠️ Processus ${processId} n'a pas d'état "commited". Tentative d'auto-approbation du 1er état...`);
|
||
const firstState = process.states[0];
|
||
const roles = firstState.roles;
|
||
if (services.rolesContainsUs(roles)) {
|
||
const approveChangeRes = await services.approveChange(processId, firstState.state_id);
|
||
await services.handleApiReturn(approveChangeRes);
|
||
const prdUpdateRes = await services.createPrdUpdate(processId, firstState.state_id);
|
||
await services.handleApiReturn(prdUpdateRes);
|
||
} else {
|
||
if (firstState.validation_tokens.length > 0) {
|
||
const res = await services.createPrdUpdate(processId, firstState.state_id);
|
||
await services.handleApiReturn(res);
|
||
}
|
||
}
|
||
await new Promise((resolve) => setTimeout(resolve, 2000)); // Attente arbitraire
|
||
lastState = services.getLastCommitedState(process);
|
||
if (!lastState) {
|
||
throw new Error("Process doesn't have a commited state yet");
|
||
}
|
||
}
|
||
const lastStateIndex = services.getLastCommitedStateIndex(process);
|
||
if (lastStateIndex === null) throw new Error("Process doesn't have a commited state yet");
|
||
|
||
const privateData: Record<string, any> = {};
|
||
const publicData: Record<string, any> = {};
|
||
|
||
for (const field of Object.keys(newData)) {
|
||
if (lastState.public_data[field]) {
|
||
publicData[field] = newData[field];
|
||
continue;
|
||
}
|
||
if (privateFields.includes(field)) {
|
||
privateData[field] = newData[field];
|
||
continue;
|
||
}
|
||
for (let i = lastStateIndex; i >= 0; i--) {
|
||
const state = process.states[i];
|
||
if (state.pcd_commitment[field]) {
|
||
privateData[field] = newData[field];
|
||
break;
|
||
} else {
|
||
continue;
|
||
}
|
||
}
|
||
if (privateData[field]) continue;
|
||
publicData[field] = newData[field];
|
||
}
|
||
console.log(`[Router:API] 🔄 Envoi de la mise à jour à services.updateProcess...`);
|
||
const res = await services.updateProcess(process, privateData, publicData, 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 (!services.isPaired()) throw new Error('Device not paired');
|
||
|
||
const { accessToken, encodedData } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
const decodedData = 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 { accessToken, commitedIn, label, fileBlob } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
const hash = 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 { accessToken, processState, attributeName } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
const proof = 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 { accessToken, merkleProof, documentHash } = event.data;
|
||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||
throw new Error('Invalid or expired session token');
|
||
}
|
||
|
||
let parsedMerkleProof: MerkleProofResult;
|
||
try {
|
||
parsedMerkleProof = JSON.parse(merkleProof);
|
||
} catch (e) {
|
||
throw new Error('Provided merkleProof is not a valid json object');
|
||
}
|
||
|
||
const res = services.validateMerkleProof(parsedMerkleProof, documentHash);
|
||
window.parent.postMessage(
|
||
{
|
||
type: MessageType.MERKLE_PROOF_VALIDATED,
|
||
isValid: res,
|
||
messageId: event.data.messageId,
|
||
},
|
||
event.origin,
|
||
);
|
||
};
|
||
|
||
// --- Le "Switchyard" : il reçoit tous les messages et les dispatche ---
|
||
|
||
window.removeEventListener('message', handleMessage);
|
||
window.addEventListener('message', handleMessage);
|
||
|
||
async function handleMessage(event: MessageEvent) {
|
||
try {
|
||
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) {
|
||
const errorMsg = `[Router:API] 💥 Erreur de haut niveau: ${error.message || 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.');
|
||
}
|
||
|
||
// --- Fonctions utilitaires de la page ---
|
||
|
||
async function cleanPage() {
|
||
const container = document.querySelector('#containerId');
|
||
if (container) container.innerHTML = '';
|
||
}
|
||
|
||
async function injectHeader() {
|
||
const headerContainer = document.getElementById('header-container');
|
||
if (headerContainer) {
|
||
const headerHtml = await fetch('/src/components/header/header.html').then((res) => res.text());
|
||
headerContainer.innerHTML = headerHtml;
|
||
|
||
const script = document.createElement('script');
|
||
script.src = '/src/components/header/header.ts';
|
||
script.type = 'module';
|
||
document.head.appendChild(script);
|
||
initHeader();
|
||
}
|
||
}
|
||
|
||
(window as any).navigate = navigate;
|
||
|
||
// Gère les événements de navigation personnalisés (ex: depuis le header)
|
||
document.addEventListener('navigate', (e: Event) => {
|
||
const event = e as CustomEvent<{ page: string; processId?: string }>;
|
||
console.log(`[Router] 🧭 Événement de navigation personnalisé reçu: ${event.detail.page}`);
|
||
if (event.detail.page === 'chat') {
|
||
// Logique spécifique pour 'chat'
|
||
const container = document.querySelector('.container');
|
||
if (container) container.innerHTML = '';
|
||
|
||
//initChat();
|
||
|
||
const chatElement = document.querySelector('chat-element');
|
||
if (chatElement) {
|
||
chatElement.setAttribute('process-id', event.detail.processId || '');
|
||
}
|
||
} else {
|
||
// Gère les autres navigations personnalisées
|
||
navigate(event.detail.page);
|
||
}
|
||
});
|
||
|
||
// --- Fin de la section API ---
|
||
// ===================================================================================
|