ihm_client/src/router.ts
NicolasCantu 90bb585251 refactor: remove duplicated code and dead code
**Motivations :**
- Éliminer la duplication de code pour la vérification PBKDF2 key
- Éliminer la duplication de code pour la vérification du wallet avec retries
- Supprimer les imports commentés (code mort)
- Centraliser la logique de vérification des prérequis dans un utilitaire

**Modifications :**
- Créer src/utils/prerequisites.utils.ts avec checkPBKDF2Key() et checkWalletWithRetries()
- Remplacer toutes les occurrences dupliquées dans router.ts, home.ts, birthday-setup.ts, block-sync.ts, wallet-setup.ts
- Supprimer les imports commentés dans device-management.ts, pairing.service.ts, modal.service.ts
- Utiliser pbkdf2KeyResult.key au lieu de récupérer la clé plusieurs fois dans wallet-setup.ts

**Pages affectées :**
- src/utils/prerequisites.utils.ts (nouveau fichier utilitaire)
- src/router.ts (utilise checkPBKDF2Key)
- src/pages/home/home.ts (utilise checkPBKDF2Key, checkWalletWithRetries)
- src/pages/birthday-setup/birthday-setup.ts (utilise checkPBKDF2Key, checkWalletWithRetries)
- src/pages/block-sync/block-sync.ts (utilise checkPBKDF2Key, checkWalletWithRetries)
- src/pages/wallet-setup/wallet-setup.ts (utilise checkPBKDF2Key, pbkdf2KeyResult.key)
- src/services/service.ts (supprime imports commentés)
- src/components/device-management/device-management.ts (supprime import commenté)
- src/services/pairing.service.ts (supprime imports commentés)
- src/services/modal.service.ts (supprime import commenté)
2025-10-29 16:47:12 +01:00

1319 lines
43 KiB
TypeScript
Executable File

// CSS is loaded via HTML link tag
import Database from './services/database.service';
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',
};
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<void> {
console.log('🔍 Checking storage state to determine next step...');
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é
console.log('🔐 No PBKDF2 key found, navigating to security-setup');
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) {
console.log('💰 Wallet does not exist, navigating to wallet-setup');
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) {
console.log('🎂 Birthday is configured, checking pairing status...');
// Vérifier l'état du pairing (nécessite Services mais seulement si tout est prêt)
try {
const services = await Services.getInstance();
const isPaired = services.isPaired();
if (isPaired) {
console.log('✅ Device is paired, navigating to account page');
await navigate('account');
return;
}
console.log('🔗 Not paired yet, navigating to pairing');
await navigate('home');
return;
} catch (error) {
console.log('⚠️ Could not check pairing status, navigating to home');
await navigate('home');
return;
}
}
// Wallet existe mais birthday pas configuré
console.log('💰 Wallet exists but birthday not set, navigating to birthday-setup');
await navigate('birthday-setup');
return;
} catch (error) {
console.error('❌ Error checking storage state:', error);
// En cas d'erreur, commencer par la configuration de sécurité
console.log('🔐 Error occurred, defaulting to security-setup');
await navigate('security-setup');
}
}
export async function navigate(path: string) {
console.log('🧭 Navigate called with path:', path);
cleanSubscriptions();
cleanPage();
path = path.replace(/^\//, '');
if (path.includes('/')) {
const parsedPath = path.split('/')[0];
if (!routes[parsedPath]) {
path = 'home';
}
}
console.log('🧭 Final path after processing:', path);
await handleLocation(path);
console.log('🧭 handleLocation completed for path:', path);
}
async function handleLocation(path: string) {
console.log('📍 handleLocation called with path:', path);
const parsedPath = path.split('/');
if (path.includes('/')) {
path = parsedPath[0];
}
currentRoute = path;
const routeHtml = routes[path] || routes['home'];
console.log('📍 Current route set to:', currentRoute);
console.log('📍 Route HTML:', 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') {
console.log('📍 Processing setup route:', path);
window.location.href = routeHtml;
return;
}
const content = document.getElementById('containerId');
console.log('📍 Container element found:', !!content);
if (content) {
if (path === 'home') {
console.log('🏠 Processing home route...');
// Use LoginComponent
const loginComponent = LoginComponent;
const container = document.querySelector('#containerId');
console.log('🏠 Container for home:', !!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);
console.log('🏠 Component appended to container');
}
// Initialize the home page after component is added to DOM
console.log('🏠 Initializing home page...');
try {
const { initHomePage } = await import('./pages/home/home');
await initHomePage();
console.log('✅ Home page initialized successfully');
} catch (error) {
console.error('❌ Failed to initialize home page:', error);
}
} else {
console.log('📍 Processing other route:', 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
console.warn('Process functionality has been removed, redirecting to account');
await navigate('account');
break;
case 'process-element':
// Process element functionality removed
console.warn('Process element functionality has been removed');
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
console.warn('Signature functionality has been removed');
break;
}
}
}
window.onpopstate = async () => {
const services = await Services.getInstance();
if (!services.isPaired()) {
handleLocation('home');
} else {
handleLocation('process');
}
};
export async function init(): Promise<void> {
try {
console.log('🚀 Starting application initialization...');
// Initialiser uniquement la base de données (sans WebAssembly)
console.log('🔧 Initializing database...');
const db = await Database.getInstance();
// Register service worker
console.log('📱 Registering service worker...');
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
console.log('🔍 Checking storage state and navigating to appropriate page...');
await checkStorageStateAndNavigate();
console.log('✅ Application initialization completed successfully');
} catch (error) {
console.error('❌ Application initialization failed:', error);
// Handle WebAssembly memory errors specifically
if (error instanceof RangeError && error.message.includes('WebAssembly.instantiate')) {
console.error('🚨 WebAssembly memory error detected');
console.log('💡 Try refreshing the page or closing other tabs to free memory');
// Show user-friendly error message
alert('⚠️ Insufficient memory for WebAssembly. Please refresh the page or close other tabs.');
}
console.log('🔄 Falling back to security-setup...');
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) => {
// Use successResponse function
console.log('Success response:', data);
window.parent.postMessage(
{
type: MessageType.SUCCESS,
data: data,
messageId,
},
origin
);
};
// --- 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: `
<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) {
const errorMsg = 'Failed to pair device: User refused to link';
console.error(errorMsg);
// 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}`;
console.error(errorMsg);
// Error handling - no response needed
}
};
const handleCreatePairing = async (event: MessageEvent) => {
if (event.data.type !== MessageType.CREATE_PAIRING) {
return;
}
console.log('📨 [Router] Received CREATE_PAIRING request');
if (services.isPaired()) {
const errorMsg = '⚠️ Device already paired — ignoring CREATE_PAIRING request';
console.warn(errorMsg);
// Error handling - no response needed
return;
}
try {
const { accessToken } = event.data;
console.log('🔐 Checking access token validity...');
const validToken =
accessToken && (await tokenService.validateToken(accessToken, event.origin));
if (!validToken) {
throw new Error('❌ Invalid or expired session token');
}
console.log('✅ Token validated successfully');
console.log('🚀 Starting pairing process');
const myAddress = services.getDeviceAddress();
console.log('📍 Device address:', myAddress);
console.log('🧱 Creating pairing process...');
const createPairingProcessReturn = await services.createPairingProcess('', [myAddress]);
console.log('🧾 Pairing process created:', createPairingProcessReturn);
const pairingId = createPairingProcessReturn.updated_process?.process_id;
const stateId = createPairingProcessReturn.updated_process?.current_process?.states[0]
?.state_id as string;
console.log('🔗 Pairing ID:', pairingId);
console.log('🧩 State ID:', stateId);
console.log('🔒 Registering device as paired...');
services.pairDevice(pairingId, [myAddress]);
console.log('🧠 Handling API return for createPairingProcess...');
await services.handleApiReturn(createPairingProcessReturn);
console.log('🔍 DEBUG: About to create PRD update...');
console.log('🧰 Creating PRD update...');
const createPrdUpdateReturn = await services.createPrdUpdate(pairingId, stateId);
console.log('🧾 PRD update result:', createPrdUpdateReturn);
await services.handleApiReturn(createPrdUpdateReturn);
console.log('✅ DEBUG: PRD update completed successfully!');
console.log('🔍 DEBUG: About to approve change...');
console.log('✅ Approving change...');
const approveChangeReturn = await services.approveChange(pairingId, stateId);
console.log('📜 Approve change result:', approveChangeReturn);
await services.handleApiReturn(approveChangeReturn);
console.log('✅ DEBUG: approveChange completed successfully!');
console.log('🔍 DEBUG: approveChange completed, about to call waitForPairingCommitment...');
console.log('⏳ Waiting for pairing process to be committed...');
await services.waitForPairingCommitment(pairingId);
console.log('✅ DEBUG: waitForPairingCommitment completed successfully!');
console.log('🔍 DEBUG: About to call confirmPairing...');
console.log('🔁 Confirming pairing...');
await services.confirmPairing(pairingId);
console.log('✅ DEBUG: confirmPairing completed successfully!');
console.log('🎉 Pairing successfully completed!');
// ✅ Send success response to frontend
const successMsg = {
type: MessageType.PAIRING_CREATED,
pairingId,
messageId: event.data.messageId,
};
console.log('📤 Sending PAIRING_CREATED message to UI:', successMsg);
window.parent.postMessage(successMsg, event.origin);
} catch (e) {
const errorMsg = `❌ Failed to create pairing process: ${e}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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<string, any> = {};
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}`;
console.error(errorMsg);
// 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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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;
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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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<string, any> = {};
const publicData: Record<string, any> = {};
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}`;
console.error(errorMsg);
// 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';
console.warn(errorMsg);
// 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}`;
console.error(errorMsg);
// Error handling - no response needed
}
};
const handleHashValue = async (event: MessageEvent) => {
if (event.data.type !== MessageType.HASH_VALUE) {return;}
console.log('handleHashValue', 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}`;
console.error(errorMsg);
// 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}`;
console.error(errorMsg);
// 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 (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
);
} catch (e) {
const errorMsg = `Failed to get merkle proof: ${e}`;
console.error(errorMsg);
// 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:
console.warn('CREATE_CONVERSATION functionality has been removed');
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
console.log('👂 Parent is listening for messages');
break;
case 'IFRAME_READY':
// Iframe is ready - no action needed
console.log('🔗 Iframe is ready');
break;
default:
console.warn(`Unhandled message type: ${event.data.type}`);
}
} catch (error) {
const errorMsg = `Error handling message: ${error}`;
console.error(errorMsg);
// Error handling - no response needed
}
}
window.parent.postMessage(
{
type: MessageType.LISTENING,
},
'*'
);
}
// 4 Words Pairing Handlers
async function handlePairing4WordsCreate(_event: MessageEvent) {
try {
console.log('🔐 Handling 4 words pairing create request');
const service = await Services.getInstance();
// Use service variable
console.log('Service instance:', 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}`;
console.error(errorMsg);
// Error handling - no response needed
}
}
async function handlePairing4WordsJoin(event: MessageEvent) {
try {
console.log('🔗 Handling 4 words pairing join request');
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}`;
console.error(errorMsg);
// 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) {
console.error('❌ Erreur lors de la suppression du compte:', error);
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
*/
async function handleSecurityKeyManagement(): Promise<boolean> {
console.log('🔐 Starting security key management...');
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) {
console.log('🔐 No security mode configured, redirecting to security setup...');
window.location.href = '/src/pages/security-setup/security-setup.html';
return false;
}
console.log('🔐 Security mode configured:', 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) {
console.log('🔐 No security credentials found, redirecting to wallet setup...');
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
return false;
} else {
console.log('🔐 Security credentials found, verifying access...');
// Vérifier l'accès aux credentials
const credentials = await secureCredentialsService.retrieveCredentials('4nk-secure-password');
if (!credentials) {
console.log('❌ Failed to access security credentials');
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
return false;
}
console.log('✅ Security credentials verified');
return true;
}
} catch (error) {
console.error('❌ Security key management failed:', error);
console.log('🔐 Redirecting to security setup...');
window.location.href = '/src/pages/security-setup/security-setup.html';
return false;
}
}
/**
* ÉTAPE 5: Handshake
*/
async function performHandshake(_services: any): Promise<void> {
console.log('🤝 Performing handshake...');
try {
// Le handshake est déjà fait lors de la connexion aux relais
// Cette fonction peut être étendue pour des handshakes supplémentaires
console.log('✅ Handshake completed');
} catch (error) {
console.error('❌ Handshake failed:', error);
throw error;
}
}
/**
* ÉTAPE 6: Pairing
*/
async function handlePairing(services: any): Promise<void> {
console.log('🔗 Handling device pairing...');
try {
// Vérifier le statut de pairing
const isPaired = services.isPaired();
console.log('🔍 Device pairing status:', isPaired ? 'Paired' : 'Not paired');
if (!isPaired) {
console.log('⚠️ Device not paired, user must complete pairing...');
// Le pairing sera géré par la page home
return;
} else {
console.log('✅ Device is already paired');
}
} catch (error) {
console.error('❌ Pairing handling failed:', error);
throw error;
}
}
/**
* ÉTAPE 7: Écoute des processus
*/
async function startProcessListening(services: any): Promise<void> {
console.log('👂 Starting process listening...');
try {
// Restore data from database (these operations can fail, so we handle them separately)
try {
console.log('📊 Restoring processes from database...');
await services.restoreProcessesFromDB();
} catch (error) {
console.warn('⚠️ Failed to restore processes from database:', error);
}
try {
console.log('🔐 Restoring secrets from database...');
await services.restoreSecretsFromDB();
} catch (error) {
console.warn('⚠️ Failed to restore secrets from database:', error);
}
console.log('✅ Process listening started');
} catch (error) {
console.error('❌ Process listening failed:', error);
throw error;
}
}