// CSS is loaded via HTML link tag import Database from './services/database.service'; import { secureLogger } from './services/secure-logger'; import Services from './services/service'; import TokenService from './services/token'; import { cleanSubscriptions } from './utils/subscription.utils'; import { LoginComponent } from './pages/home/home-component'; 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'; import { checkPBKDF2Key } from './utils/prerequisites.utils'; const routes: { [key: string]: string } = { home: '/src/pages/home/home.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', 'security-setup': '/src/pages/security-setup/security-setup.html', 'wallet-setup': '/src/pages/wallet-setup/wallet-setup.html', 'birthday-setup': '/src/pages/birthday-setup/birthday-setup.html', 'block-sync': '/src/pages/block-sync/block-sync.html', pairing: '/src/pages/pairing/pairing.html', }; export let currentRoute = ''; /** * Vérifie l'état du storage et détermine quelle page afficher selon l'état actuel * Version légère qui n'initialise pas WebAssembly pour la première visite * Logique de progression : * - Si pairing → account * - Si date anniversaire → pairing * - Si wallet → birthday-setup * - Si pbkdf2 → wallet-setup * - Sinon → security-setup */ export async function checkStorageStateAndNavigate(): Promise { secureLogger.debug('🔍 Checking storage state to determine next step...', { component: 'Router', }); try { // Utiliser DeviceReaderService pour éviter d'initialiser WebAssembly const { DeviceReaderService } = await import('./services/device-reader.service'); const deviceReader = DeviceReaderService.getInstance(); // Vérifier si une clé PBKDF2 existe (vérification la plus légère) const pbkdf2KeyResult = await checkPBKDF2Key(); if (!pbkdf2KeyResult) { // Aucune clé PBKDF2 trouvée, commencer par la configuration de sécurité secureLogger.info('🔐 No PBKDF2 key found, navigating to security-setup', { component: 'Router', }); await navigate('security-setup'); return; } // Vérifier si le wallet existe (même avec birthday = 0) const device = await deviceReader.getDeviceFromDatabase(); if (!device?.sp_wallet) { secureLogger.info('💰 Wallet does not exist, navigating to wallet-setup', { component: 'Router', }); await navigate('wallet-setup'); return; } // Vérifier si la date anniversaire est configurée (wallet avec birthday > 0) if (device.sp_wallet.birthday && device.sp_wallet.birthday > 0) { secureLogger.info('🎂 Birthday is configured, navigating to pairing', { component: 'Router', }); // Rediriger vers la page de pairing standalone await navigate('pairing'); return; } // Wallet existe mais birthday pas configuré secureLogger.info('💰 Wallet exists but birthday not set, navigating to birthday-setup', { component: 'Router', }); await navigate('birthday-setup'); return; } catch (error) { secureLogger.error('❌ Error checking storage state:', error, { component: 'Router' }); // En cas d'erreur, commencer par la configuration de sécurité secureLogger.error('🔐 Error occurred, defaulting to security-setup', { component: 'Router' }); await navigate('security-setup'); } } export async function navigate(path: string) { secureLogger.info('🧭 Navigate called with path:', { component: 'Router', data: path }); cleanSubscriptions(); cleanPage(); path = path.replace(/^\//, ''); if (path.includes('/')) { const parsedPath = path.split('/')[0]; if (!routes[parsedPath]) { path = 'home'; } } secureLogger.info('🧭 Final path after processing:', { component: 'Router', data: path }); await handleLocation(path); secureLogger.info('🧭 handleLocation completed for path:', { component: 'Router', data: path }); } async function handleLocation(path: string) { secureLogger.info('📍 handleLocation called with path:', { component: 'Router', data: path }); const parsedPath = path.split('/'); if (path.includes('/')) { path = parsedPath[0]; } currentRoute = path; const routeHtml = routes[path] || routes['home']; secureLogger.info('📍 Current route set to:', { component: 'Router', data: currentRoute }); secureLogger.info('📍 Route HTML:', { component: 'Router', data: routeHtml }); // Pour les pages de setup, rediriger directement vers la page HTML if ( path === 'security-setup' || path === 'wallet-setup' || path === 'birthday-setup' || path === 'block-sync' || path === 'pairing' ) { secureLogger.info('📍 Processing setup route:', { component: 'Router', data: path }); window.location.href = routeHtml; return; } const content = document.getElementById('containerId'); secureLogger.info('📍 Container element found:', { component: 'Router', data: !!content }); if (content) { if (path === 'home') { secureLogger.info('🏠 Processing home route...', { component: 'Router' }); // Use LoginComponent // const loginComponent = LoginComponent; // Used as type, not variable const container = document.querySelector('#containerId'); secureLogger.info('🏠 Container for home:', { component: 'Router', data: !!container }); const accountComponent = document.createElement('login-4nk-component'); accountComponent.setAttribute( 'style', 'width: 100vw; height: 100vh; position: relative; grid-row: 2;' ); if (container) { container.appendChild(accountComponent); secureLogger.info('🏠 Component appended to container', { component: 'Router' }); } // Initialize the home page after component is added to DOM secureLogger.info('🏠 Initializing home page...', { component: 'Router' }); try { const { initHomePage } = await import('./pages/home/home'); await initHomePage(); secureLogger.info('✅ Home page initialized successfully', { component: 'Router' }); } catch (error) { secureLogger.error('❌ Failed to initialize home page:', error, { component: 'Router' }); } } else { secureLogger.info('📍 Processing other route:', { component: 'Router', data: path }); const html = await fetch(routeHtml).then(data => data.text()); content.innerHTML = html; } await new Promise(requestAnimationFrame); // Essential functions are now handled directly in the application // const modalService = await ModalService.getInstance() // modalService.injectValidationModal() switch (path) { case 'process': // Process functionality removed - redirect to account secureLogger.warn('Process functionality has been removed, redirecting to account', { component: 'Router', }); await navigate('account'); break; case 'process-element': // Process element functionality removed secureLogger.warn('Process element functionality has been removed', { component: 'Router', }); break; case 'account': // Account page now uses device-management component directly break; /*case 'chat': const { ChatComponent } = await import('./pages/chat/chat-component'); const chatContainer = document.querySelector('.group-list'); if (chatContainer) { if (!customElements.get('chat-component')) { customElements.define('chat-component', ChatComponent); } const chatComponent = document.createElement('chat-component'); chatContainer.appendChild(chatComponent); } break;*/ case 'signature': // Signature functionality removed secureLogger.warn('Signature functionality has been removed', { component: 'Router' }); break; } } } window.onpopstate = async () => { const services = await Services.getInstance(); if (!services.isPaired()) { handleLocation('home'); } else { handleLocation('process'); } }; export async function init(): Promise { try { secureLogger.info('🚀 Starting application initialization...', { component: 'Router' }); // Initialiser uniquement la base de données (sans WebAssembly) secureLogger.info('🔧 Initializing database...', { component: 'Router' }); const db = await Database.getInstance(); // Register service worker secureLogger.info('📱 Registering service worker...', { component: 'Router' }); await db.registerServiceWorker('/src/service-workers/database.worker.js'); // Vérifier l'état du storage et naviguer vers la page appropriée // Cette fonction est maintenant légère et ne nécessite pas WebAssembly secureLogger.debug('🔍 Checking storage state and navigating to appropriate page...', { component: 'Router', }); await checkStorageStateAndNavigate(); secureLogger.info('✅ Application initialization completed successfully', { component: 'Router', }); } catch (error) { secureLogger.error('❌ Application initialization failed:', error, { component: 'Router' }); // Handle WebAssembly memory errors specifically if (error instanceof RangeError && error.message.includes('WebAssembly.instantiate')) { secureLogger.error('🚨 WebAssembly memory error detected', { component: 'Router' }); secureLogger.info('💡 Try refreshing the page or closing other tabs to free memory', { component: 'Router', }); // Show user-friendly error message alert('⚠️ Insufficient memory for WebAssembly. Please refresh the page or close other tabs.'); } secureLogger.info('🔄 Falling back to security-setup...', { component: 'Router' }); await navigate('security-setup'); } } export async function registerAllListeners() { const services = await Services.getInstance(); const tokenService = await TokenService.getInstance(); const errorResponse = (errorMsg: string, origin: string, messageId?: string) => { window.parent.postMessage( { type: MessageType.ERROR, error: errorMsg, messageId, }, origin ); }; // const successResponse = (data: any, origin: string, messageId?: string) => { // // Not used anymore, response sent directly in handlers // }; // --- Handler functions --- const handleRequestLink = async (event: MessageEvent) => { if (event.data.type !== MessageType.REQUEST_LINK) { return; } const modalService = await ModalService.getInstance(); const result = await modalService.showConfirmationModal( { title: 'Confirmation de liaison', content: ` `, confirmText: 'Ajouter un service', cancelText: 'Annuler', }, true ); if (!result) { const errorMsg = 'Failed to pair device: User refused to link'; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } try { const tokens = await tokenService.generateSessionToken(event.origin); const acceptedMsg = { type: MessageType.LINK_ACCEPTED, accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, messageId: event.data.messageId, }; window.parent.postMessage(acceptedMsg, event.origin); } catch (error) { const errorMsg = `Failed to generate tokens: ${error}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleCreatePairing = async (event: MessageEvent) => { if (event.data.type !== MessageType.CREATE_PAIRING) { return; } secureLogger.info('📨 [Router] Received CREATE_PAIRING request', { component: 'Router' }); if (services.isPaired()) { const errorMsg = '⚠️ Device already paired — ignoring CREATE_PAIRING request'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { const { accessToken } = event.data; secureLogger.debug('🔐 Checking access token validity...', { component: 'Router' }); const validToken = accessToken && (await tokenService.validateToken(accessToken, event.origin)); if (!validToken) { throw new Error('❌ Invalid or expired session token'); } secureLogger.info('✅ Token validated successfully', { component: 'Router' }); secureLogger.info('🚀 Starting pairing process', { component: 'Router' }); const myAddress = await services.getDeviceAddress(); secureLogger.info('📍 Device address:', { component: 'Router', data: myAddress }); secureLogger.info('🧱 Creating pairing process...', { component: 'Router' }); const createPairingProcessReturn = await services.createPairingProcess('', [myAddress]); secureLogger.info('🧾 Pairing process created:', { component: 'Router', data: createPairingProcessReturn, }); const pairingId = createPairingProcessReturn.updated_process?.process_id; const stateId = createPairingProcessReturn.updated_process?.current_process?.states[0] ?.state_id as string; secureLogger.info('🔗 Pairing ID:', { component: 'Router', data: pairingId }); secureLogger.info('🧩 State ID:', { component: 'Router', data: stateId }); secureLogger.info('🔒 Registering device as paired...', { component: 'Router' }); services.pairDevice(pairingId, [myAddress]); secureLogger.info('🧠 Handling API return for createPairingProcess...', { component: 'Router', }); await services.handleApiReturn(createPairingProcessReturn); secureLogger.debug('🔍 DEBUG: About to create PRD update...', { component: 'Router' }); secureLogger.info('🧰 Creating PRD update...', { component: 'Router' }); const createPrdUpdateReturn = await services.createPrdUpdate(pairingId, stateId); secureLogger.info('🧾 PRD update result:', { component: 'Router', data: createPrdUpdateReturn, }); await services.handleApiReturn(createPrdUpdateReturn); secureLogger.debug('✅ DEBUG: PRD update completed successfully!', { component: 'Router' }); secureLogger.debug('🔍 DEBUG: About to approve change...', { component: 'Router' }); secureLogger.info('✅ Approving change...', { component: 'Router' }); const approveChangeReturn = await services.approveChange(pairingId, stateId); secureLogger.info('📜 Approve change result:', { component: 'Router', data: approveChangeReturn, }); await services.handleApiReturn(approveChangeReturn); secureLogger.debug('✅ DEBUG: approveChange completed successfully!', { component: 'Router', }); secureLogger.debug( '🔍 DEBUG: approveChange completed, about to call waitForPairingCommitment...', { component: 'Router' } ); secureLogger.info('⏳ Waiting for pairing process to be committed...', { component: 'Router', }); await services.waitForPairingCommitment(pairingId); secureLogger.debug('✅ DEBUG: waitForPairingCommitment completed successfully!', { component: 'Router', }); secureLogger.debug('🔍 DEBUG: About to call confirmPairing...', { component: 'Router' }); secureLogger.info('🔁 Confirming pairing...', { component: 'Router' }); await services.confirmPairing(pairingId); secureLogger.debug('✅ DEBUG: confirmPairing completed successfully!', { component: 'Router', }); secureLogger.info('🎉 Pairing successfully completed!', { component: 'Router' }); // ✅ Send success response to frontend const successMsg = { type: MessageType.PAIRING_CREATED, pairingId, messageId: event.data.messageId, }; secureLogger.info('📤 Sending PAIRING_CREATED message to UI:', { component: 'Router', data: successMsg, }); window.parent.postMessage(successMsg, event.origin); } catch (e) { const errorMsg = `❌ Failed to create pairing process: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleGetMyProcesses = async (event: MessageEvent) => { if (event.data.type !== MessageType.GET_MY_PROCESSES) { return; } if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { 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 ); } catch (e) { const errorMsg = `Failed to get processes: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleGetProcesses = async (event: MessageEvent) => { if (event.data.type !== MessageType.GET_PROCESSES) { return; } const tokenService = await TokenService.getInstance(); if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { const { accessToken } = event.data; // Validate the session token 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 ); } catch (e) { const errorMsg = `Failed to get processes: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; /// We got a state for some process and return as many clear attributes as we can const handleDecryptState = async (event: MessageEvent) => { if (event.data.type !== MessageType.RETRIEVE_DATA) { return; } const tokenService = await TokenService.getInstance(); if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { const { processId, stateId, accessToken } = event.data; if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { throw new Error('Invalid or expired session token'); } // Retrieve the state for the process const process = await services.getProcess(processId); if (!process) { throw new Error("Can't find process"); } const state = services.getStateFromId(process, stateId); await services.checkConnections(process, stateId); const res: Record = {}; if (state) { // Decrypt all the data we have the key for for (const attribute of Object.keys(state.pcd_commitment)) { if (attribute === 'roles' || state.public_data[attribute]) { continue; } const decryptedAttribute = await services.decryptAttribute(processId, state, attribute); if (decryptedAttribute) { res[attribute] = decryptedAttribute; } } } else { throw new Error('Unknown state for process', processId); } window.parent.postMessage( { type: MessageType.DATA_RETRIEVED, data: res, messageId: event.data.messageId, }, event.origin ); } catch (e) { const errorMsg = `Failed to retrieve data: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleValidateToken = async (event: MessageEvent) => { if (event.data.type !== MessageType.VALIDATE_TOKEN) { return; } const accessToken = event.data.accessToken; const refreshToken = event.data.refreshToken; if (!accessToken || !refreshToken) { errorResponse( 'Failed to validate token: missing access, refresh token or both', event.origin, event.data.messageId ); } const isValid = await tokenService.validateToken(accessToken, event.origin); window.parent.postMessage( { type: MessageType.VALIDATE_TOKEN, accessToken: accessToken, refreshToken: refreshToken, isValid: isValid, messageId: event.data.messageId, }, event.origin ); }; const handleRenewToken = async (event: MessageEvent) => { if (event.data.type !== MessageType.RENEW_TOKEN) { return; } try { 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'); } window.parent.postMessage( { type: MessageType.RENEW_TOKEN, accessToken: newAccessToken, refreshToken: refreshToken, messageId: event.data.messageId, }, event.origin ); } catch (error) { const errorMsg = `Failed to renew token: ${error}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleGetPairingId = async (event: MessageEvent) => { if (event.data.type !== MessageType.GET_PAIRING_ID) { return; } if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { 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 ); } catch (e) { const errorMsg = `Failed to get pairing id: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleCreateProcess = async (event: MessageEvent) => { if (event.data.type !== MessageType.CREATE_PROCESS) { return; } if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { const { processData, privateFields, roles, accessToken } = event.data; if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { throw new Error('Invalid or expired session token'); } 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; // const stateId = process.states[0].state_id; // Not used await services.handleApiReturn(createProcessReturn); const res = { processId, process, processData, }; window.parent.postMessage( { type: MessageType.PROCESS_CREATED, processCreated: res, messageId: event.data.messageId, }, event.origin ); } catch (e) { const errorMsg = `Failed to create process: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleNotifyUpdate = async (event: MessageEvent) => { if (event.data.type !== MessageType.NOTIFY_UPDATE) { return; } if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { 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 ); } catch (e) { const errorMsg = `Failed to notify update for process: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleValidateState = async (event: MessageEvent) => { if (event.data.type !== MessageType.VALIDATE_STATE) { return; } if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { 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 ); } catch (e) { const errorMsg = `Failed to validate process: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleUpdateProcess = async (event: MessageEvent) => { if (event.data.type !== MessageType.UPDATE_PROCESS) { return; } if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed } try { // privateFields is only used if newData contains new fields // roles can be empty meaning that roles from the last commited state are kept const { processId, newData, privateFields, roles, accessToken } = event.data; if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { throw new Error('Invalid or expired session token'); } // Check if the new data is already in the process or if it's a new field const process = await services.getProcess(processId); if (!process) { throw new Error('Process not found'); } let lastState = services.getLastCommitedState(process); if (!lastState) { 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) { // Try to send it again anyway const res = await services.createPrdUpdate(processId, firstState.state_id); await services.handleApiReturn(res); } } // Wait a couple seconds await new Promise(resolve => setTimeout(resolve, 2000)); 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"); } // Shouldn't happen const privateData: Record = {}; const publicData: Record = {}; for (const field of Object.keys(newData)) { // Public data are carried along each new state // So the first thing we can do is check if the new data is public data if (lastState.public_data[field]) { // Add it to public data publicData[field] = newData[field]; continue; } // If it's not a public data, it may be either a private data update, or a new field (public of private) // Caller gave us a list of new private fields, if we see it here this is a new private field if (privateFields.includes(field)) { // Add it to private data privateData[field] = newData[field]; continue; } // Now it can be an update of private data or a new public data // We check that the field exists in previous states private data for (let i = lastStateIndex; i >= 0; i--) { const state = process.states[i]; if (state.pcd_commitment[field]) { // We don't even check if it's a public field, we would have seen it in the last state privateData[field] = newData[field]; break; } else { // This attribute was not modified in that state, we go back to the previous state continue; } } if (privateData[field]) { continue; } // We've get back all the way to the first state without seeing it, it's a new public field publicData[field] = newData[field]; } // We'll let the wasm check if roles are consistent 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 ); } catch (e) { const errorMsg = `Failed to update process: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleDecodePublicData = async (event: MessageEvent) => { if (event.data.type !== MessageType.DECODE_PUBLIC_DATA) { return; } if (!services.isPaired()) { const errorMsg = 'Device not paired'; secureLogger.warn(errorMsg, { component: 'Router' }); // Error handling - no response needed return; } try { 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 ); } catch (e) { const errorMsg = `Failed to decode data: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleHashValue = async (event: MessageEvent) => { if (event.data.type !== MessageType.HASH_VALUE) { return; } secureLogger.info('handleHashValue', { component: 'Router', data: event.data }); try { 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 ); } catch (e) { const errorMsg = `Failed to hash value: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleGetMerkleProof = async (event: MessageEvent) => { if (event.data.type !== MessageType.GET_MERKLE_PROOF) { return; } try { 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 ); } catch (e) { const errorMsg = `Failed to get merkle proof: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; const handleValidateMerkleProof = async (event: MessageEvent) => { if (event.data.type !== MessageType.VALIDATE_MERKLE_PROOF) { return; } try { const { accessToken, merkleProof, documentHash } = event.data; if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) { throw new Error('Invalid or expired session token'); } // Try to parse the proof // We will validate it's a MerkleProofResult in the wasm let parsedMerkleProof: MerkleProofResult; try { parsedMerkleProof = JSON.parse(merkleProof); } catch { 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 ); } catch (e) { const errorMsg = `Failed to get merkle proof: ${e}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } }; 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.CREATE_CONVERSATION: secureLogger.warn('CREATE_CONVERSATION functionality has been removed', { component: 'Router', }); 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; case MessageType.PAIRING_4WORDS_CREATE: await handlePairing4WordsCreate(event); break; case MessageType.PAIRING_4WORDS_JOIN: await handlePairing4WordsJoin(event); break; case 'LISTENING': // Parent is listening for messages - no action needed secureLogger.info('👂 Parent is listening for messages', { component: 'Router' }); break; case 'IFRAME_READY': // Iframe is ready - no action needed secureLogger.info('🔗 Iframe is ready', { component: 'Router' }); break; default: secureLogger.warn('Unhandled message type: ${event.data.type}', { component: 'Router' }); } } catch (error) { const errorMsg = `Error handling message: ${error}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } } window.parent.postMessage( { type: MessageType.LISTENING, }, '*' ); } // 4 Words Pairing Handlers async function handlePairing4WordsCreate(_event: MessageEvent) { try { secureLogger.info('🔐 Handling 4 words pairing create request', { component: 'Router' }); const service = await Services.getInstance(); // Use service variable secureLogger.info('Service instance:', { component: 'Router', data: service }); const iframePairingService = await import('./services/iframe-pairing.service'); const IframePairingService = iframePairingService.default; const pairingService = IframePairingService.getInstance(); await pairingService.createPairing(); // Pairing creation initiated - no response needed } catch (error) { const errorMsg = `Error creating 4 words pairing: ${error}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } } async function handlePairing4WordsJoin(event: MessageEvent) { try { secureLogger.info('🔗 Handling 4 words pairing join request', { component: 'Router' }); const { words } = event.data; if (!words) { throw new Error('Words are required for joining pairing'); } const iframePairingService = await import('./services/iframe-pairing.service'); const IframePairingService = iframePairingService.default; const pairingService = IframePairingService.getInstance(); await pairingService.joinPairing(words); // Pairing join initiated - no response needed } catch (error) { const errorMsg = `Error joining 4 words pairing: ${error}`; secureLogger.error(errorMsg, { component: 'Router' }); // Error handling - no response needed } } async function cleanPage() { const container = document.querySelector('#containerId'); if (container) { container.innerHTML = ''; } } // Essential functions are now handled directly in the application // No need to import from header component since it was removed (window as any).navigate = navigate; // Global function to delete account (window as any).deleteAccount = async () => { if ( confirm( '⚠️ Êtes-vous sûr de vouloir supprimer complètement votre compte ?\n\nCette action est IRRÉVERSIBLE et supprimera :\n• Tous vos processus\n• Toutes vos données\n• Votre wallet\n• Votre historique\n\nTapez "SUPPRIMER" pour confirmer.' ) ) { const confirmation = prompt('Tapez "SUPPRIMER" pour confirmer la suppression :'); if (confirmation === 'SUPPRIMER') { try { const services = await Services.getInstance(); await services.deleteAccount(); // Show success message alert( "✅ Compte supprimé avec succès !\n\nLa page va se recharger pour redémarrer l'application." ); // Reload the page to restart the application window.location.reload(); } catch (error) { secureLogger.error('❌ Erreur lors de la suppression du compte:', error, { component: 'Router', }); alert('❌ Erreur lors de la suppression du compte. Veuillez réessayer.'); } } else { alert('❌ Suppression annulée. Le texte de confirmation ne correspond pas.'); } } }; document.addEventListener('navigate', (e: Event) => { const event = e as CustomEvent<{ page: string; processId?: string }>; if (event.detail.page === '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 || ''); } } }); /** * ÉTAPE 2: Gestion de la sécurité (clés de sécurité) * Cette étape doit être la première et rien d'autre ne doit s'exécuter en parallèle */ // eslint-disable-next-line @typescript-eslint/no-unused-vars async function handleSecurityKeyManagement(): Promise { secureLogger.info('🔐 Starting security key management...', { component: 'Router' }); try { // Vérifier d'abord si un mode de sécurité est configuré const { SecurityModeService } = await import('./services/security-mode.service'); const securityModeService = SecurityModeService.getInstance(); const currentMode = await securityModeService.getCurrentMode(); if (!currentMode) { secureLogger.info('🔐 No security mode configured, redirecting to security setup...', { component: 'Router', }); window.location.href = '/src/pages/security-setup/security-setup.html'; return false; } secureLogger.info('🔐 Security mode configured:', { component: 'Router', data: currentMode }); // Vérifier si des credentials existent const { SecureCredentialsService } = await import('./services/secure-credentials.service'); const secureCredentialsService = SecureCredentialsService.getInstance(); const hasCredentials = await secureCredentialsService.hasCredentials(); if (!hasCredentials) { secureLogger.info('🔐 No security credentials found, redirecting to wallet setup...', { component: 'Router', }); window.location.href = '/wallet-setup.html'; return false; } else { secureLogger.info('🔐 Security credentials found, verifying access...', { component: 'Router', }); // Vérifier l'accès aux credentials const credentials = await secureCredentialsService.retrieveCredentials(''); if (!credentials) { secureLogger.error('❌ Failed to access security credentials', { component: 'Router' }); window.location.href = '/wallet-setup.html'; return false; } secureLogger.info('✅ Security credentials verified', { component: 'Router' }); return true; } } catch (error) { secureLogger.error('❌ Security key management failed:', error, { component: 'Router' }); secureLogger.info('🔐 Redirecting to security setup...', { component: 'Router' }); window.location.href = '/security-setup.html'; return false; } } /** * ÉTAPE 5: Handshake */ // eslint-disable-next-line @typescript-eslint/no-unused-vars async function performHandshake(_services: any): Promise { secureLogger.info('🤝 Performing handshake...', { component: 'Router' }); try { // Le handshake est déjà fait lors de la connexion aux relais // Cette fonction peut être étendue pour des handshakes supplémentaires secureLogger.info('✅ Handshake completed', { component: 'Router' }); } catch (error) { secureLogger.error('❌ Handshake failed:', error, { component: 'Router' }); throw error; } } /** * ÉTAPE 6: Pairing */ // eslint-disable-next-line @typescript-eslint/no-unused-vars async function handlePairing(services: any): Promise { secureLogger.info('🔗 Handling device pairing...', { component: 'Router' }); try { // Vérifier le statut de pairing const isPaired = services.isPaired(); secureLogger.debug('🔍 Device pairing status:', { component: 'Router', data: isPaired ? 'Paired' : 'Not paired', }); if (!isPaired) { secureLogger.warn('⚠️ Device not paired, user must complete pairing...', { component: 'Router', }); // Le pairing sera géré par la page home return; } else { secureLogger.info('✅ Device is already paired', { component: 'Router' }); } } catch (error) { secureLogger.error('❌ Pairing handling failed:', error, { component: 'Router' }); throw error; } } /** * ÉTAPE 7: Écoute des processus */ // eslint-disable-next-line @typescript-eslint/no-unused-vars async function startProcessListening(services: any): Promise { secureLogger.info('👂 Starting process listening...', { component: 'Router' }); try { // Restore data from database (these operations can fail, so we handle them separately) try { secureLogger.info('📊 Restoring processes from database...', { component: 'Router' }); await services.restoreProcessesFromDB(); } catch (error) { secureLogger.warn('⚠️ Failed to restore processes from database:', { component: 'Router', data: error, }); } try { secureLogger.info('🔐 Restoring secrets from database...', { component: 'Router' }); await services.restoreSecretsFromDB(); } catch (error) { secureLogger.warn('⚠️ Failed to restore secrets from database:', { component: 'Router', data: error, }); } secureLogger.info('✅ Process listening started', { component: 'Router' }); } catch (error) { secureLogger.error('❌ Process listening failed:', error, { component: 'Router' }); throw error; } }