**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é)
1319 lines
43 KiB
TypeScript
Executable File
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;
|
|
}
|
|
}
|