diff --git a/src/index.ts b/src/index.ts index 6b67852..30120a1 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,4 @@ export { default as Services } from './services/service'; export { default as Database } from './services/database.service'; +export { MessageType } from './models/process.model'; +export type { ProfileData, ProfileMessage } from './models/process.model'; diff --git a/src/models/process.model.ts b/src/models/process.model.ts index b236f53..4a35289 100755 --- a/src/models/process.model.ts +++ b/src/models/process.model.ts @@ -21,3 +21,77 @@ export interface INotification { sendToNotificationPage?: boolean; path?: string; } + +export enum MessageType { + LISTENING = 'LISTENING', + REQUEST_LINK = 'REQUEST_LINK', + LINK_ACCEPTED = 'LINK_ACCEPTED', + CREATE_PROFILE = 'CREATE_PROFILE', + PROFILE_CREATED = 'PROFILE_CREATED', + CREATE_FOLDER = 'CREATE_FOLDER', + FOLDER_CREATED = 'FOLDER_CREATED', + RETRIEVE_DATA = 'RETRIEVE_DATA', + DATA_RETRIEVED = 'DATA_RETRIEVED', + ERROR = 'ERROR', + RENEW_TOKEN = 'RENEW_TOKEN', +} + +export interface ProfileData { + name: string; + surname: string; + email: string; + phone: string; + address: string; + postalCode: string; + city: string; + country: string; + idDocument?: string; +} + +export interface ProfileMessage { + type: MessageType.CREATE_PROFILE; + data: ProfileData; + token: string; +} + +export interface FolderData { + folderNumber: string; + name: string; + deedType: string; + description: string; + archived_description: string; + status: string; + created_at: string; + updated_at: string; + customers: string[]; + documents: string[]; + motes: string[]; + stakeholders: string[]; +} + +export interface FolderMessage { + type: MessageType.CREATE_FOLDER; + data: FolderData; + token: string; +} + +export interface RetrieveMessage { + type: MessageType.RETRIEVE_DATA; + processId: string, + stateId: string, + token: string; +} + +export interface DecryptedDataMessage { + type: MessageType.DATA_RETRIEVED; + data: string, + token: string; +} + +export interface TokenData { + token: string; + origin: string; + expiration: number; + createdAt: number; +} + diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts index e2c0ef4..a940af0 100755 --- a/src/pages/home/home.ts +++ b/src/pages/home/home.ts @@ -4,7 +4,7 @@ import { addSubscription } from '../../utils/subscription.utils'; import { displayEmojis, generateQRCode, generateCreateBtn, addressToEmoji} from '../../utils/sp-address.utils'; import { getCorrectDOM } from '../../utils/html.utils'; import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component'; -import { navigate, handleSource } from '../../router'; +import { navigate, registerAllListeners } from '../../router'; export { QrScannerComponent }; export async function initHomePage(): Promise { @@ -29,17 +29,7 @@ export async function initHomePage(): Promise { // Add this line to populate the select when the page loads await populateMemberSelect(); - const endpoint = window.location.pathname; - console.log('endpoint', endpoint); - - if (endpoint && endpoint !== '/') { - try { - await handleSource(endpoint); - console.log('handleSource', endpoint); - } catch (error) { - console.error('Error handling endpoint:', error); - } - } + await registerAllListeners(); } //// Modal diff --git a/src/router.ts b/src/router.ts index 2b1f180..f3445c4 100755 --- a/src/router.ts +++ b/src/router.ts @@ -3,11 +3,12 @@ import { initHeader } from '../src/components/header/header'; /*import { initChat } from '../src/pages/chat/chat';*/ import Database from './services/database.service'; import Services from './services/service'; +import TokenService from './services/token'; import { cleanSubscriptions } from './utils/subscription.utils'; import { LoginComponent } from './pages/home/home-component'; import { prepareAndSendPairingTx } from './utils/sp-address.utils'; import ModalService from './services/modal.service'; -export { Services }; +import { MessageType, ProfileData, FolderData } from './models/process.model'; const routes: { [key: string]: string } = { home: '/src/pages/home/home.html', process: '/src/pages/process/process.html', @@ -147,14 +148,8 @@ export async function init(): Promise { await services.restoreProcessesFromDB(); await services.restoreSecretsFromDB(); - // If we have a service redirection flag, we intercept it here - const endpoint = window.location.pathname; - - if (endpoint) { - await handleSource(endpoint); - } else { - console.log('No endpoint provided'); - } + // We register all the event listeners + await registerAllListeners(); if (services.isPaired()) { await navigate('account'); @@ -167,82 +162,293 @@ export async function init(): Promise { } } -export async function handleSource(endpoint: string) { +export async function registerAllListeners() { const services = await Services.getInstance(); + + const errorResponse = (errorMsg: string, origin: string) => { + window.parent.postMessage( + { + type: MessageType.ERROR, + error: errorMsg + }, + origin + ); + } + + 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' + }); + + try { + if (!result) { + throw new Error('User refused to link'); + } + + const tokenService = await TokenService.getInstance(); + const sessionToken = tokenService.generateSessionToken(event.origin); - switch (endpoint) { - case '/link-service': - window.addEventListener('message', async (event: MessageEvent) => { - console.log("Received message:", event.data); - if (event.data.type === 'REQUEST_LINK') { - console.log("Received link request, showing confirmation modal"); - - const modalService = await ModalService.getInstance(); - const result = await modalService.showConfirmationModal({ - title: 'Confirmation de liaison', - content: ` - - `, - confirmText: 'Ajouter un service', - cancelText: 'Annuler' - }); + if (services.isPaired()) { + // Device already paired - just renew token + window.parent.postMessage( + { + type: MessageType.RENEW_TOKEN, + sessionToken + }, + event.origin + ); + } else { + // New device - do pairing process + await prepareAndSendPairingTx(); + await services.confirmPairing(); + const neuteredDevice = services.dumpNeuteredDevice(); + + if (!neuteredDevice) { + throw new Error('Failed to get neutered device'); + } + + window.parent.postMessage( + { + type: MessageType.LINK_ACCEPTED, + payload: JSON.stringify(neuteredDevice), + sessionToken + }, + event.origin + ); + } + } catch (error) { + const errorMsg = `Failed to link service: ${error}`; + errorResponse(errorMsg, event.origin); + } + } - if (result) { - try { - if (!services.isPaired()) { - await prepareAndSendPairingTx(); - await services.confirmPairing(); - } + const handleAddProfile = async (event: MessageEvent) => { + if (event.data.type !== MessageType.CREATE_PROFILE) { + return; + } + const tokenService = await TokenService.getInstance(); + const services = await Services.getInstance(); - const neuteredDevice = services.dumpNeuteredDevice(); - if (neuteredDevice) { - window.parent.postMessage( - { - type: 'RESPONSE', - success: true, - payload: JSON.stringify(neuteredDevice) - }, - event.origin - ); - } else { - throw new Error('Failed to get a neutered device'); - } - } catch (error) { - window.parent.postMessage( - { - type: 'RESPONSE', - success: false, - error: 'Failed to complete pairing process' - }, - event.origin - ); - } - } else { - // The user canceled the link request - console.log("User canceled the linking process"); - window.parent.postMessage( - { - type: 'RESPONSE', - success: false, - error: 'User canceled linking' - }, - event.origin - ); + if (!services.isPaired()) { + const errorMsg = 'Device not paired'; + errorResponse(errorMsg, event.origin); + return; + } + + try { + const { profileData, token } = event.data; + + // Validate the session token + if (!token || !tokenService.validateToken(token, event.origin)) { + throw new Error('Invalid or expired session token'); + } + + // Create profile + await services.createAndSendProfileTx(profileData); + + window.parent.postMessage( + { + type: MessageType.PROFILE_CREATED, + token // Resend the same token + }, + event.origin + ); + } catch (e) { + const errorMsg = `Failed to create profile: ${e}`; + errorResponse(errorMsg, event.origin); + } + } + + + const handleAddFolder = async (event: MessageEvent) => { + if (event.data.type !== MessageType.CREATE_FOLDER) { + return; + } + const tokenService = await TokenService.getInstance(); + const services = await Services.getInstance(); + + if (!services.isPaired()) { + const errorMsg = 'Device not paired'; + errorResponse(errorMsg, event.origin); + return; + } + + try { + const { data, token } = event.data; + + if (!token || !tokenService.validateToken(token, event.origin)) { + throw new Error('Invalid or expired session token'); + } + + await services.createAndSendFolderTx(data); + + window.parent.postMessage( + { + type: MessageType.FOLDER_CREATED, + token + }, + event.origin + ); + } catch (e) { + const errorMsg = `Failed to create folder: ${e}`; + errorResponse(errorMsg, event.origin); + } + } + + /// 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(); + const services = await Services.getInstance(); + + if (!services.isPaired()) { + const errorMsg = 'Device not paired'; + errorResponse(errorMsg, event.origin); + return; + } + + try { + const { processId, stateId, token } = event.data; + + if (!token || !tokenService.validateToken(token, event.origin)) { + throw new Error('Invalid or expired session token'); + } + + // Retrieve the state for the process + const process = await services.getProcess(processId); + const state = services.getStateFromId(process, stateId); + + let res: Record = {}; + if (state) { + // Decrypt all the data we have the key for + for (const attribute of Object.keys(state.pcd_commitment)) { + const decryptedAttribute = await services.decryptAttribute(processId, state, attribute); + if (decryptedAttribute) { + res[attribute] = decryptedAttribute; } } - }); - console.log('sending message from link-service'); - window.parent.postMessage({ type: 'LISTENING', endpoint: endpoint }, '*'); - break; + } else { + throw new Error('Unknown state for process', processId); + } - default: - console.log('Unknown endpoint:', endpoint); + window.parent.postMessage( + { + type: MessageType.DATA_RETRIEVED, + data: JSON.stringify(res), + token + }, + event.origin + ); + } catch (e) { + const errorMsg = `Failed to retrieve data: ${e}`; + errorResponse(errorMsg, event.origin); + } } + + const handleGetProfile = async (event: MessageEvent) => { + if (event.data.type !== MessageType.RETRIEVE_DATA) { + return; + } + const tokenService = await TokenService.getInstance(); + const services = await Services.getInstance(); + + if (!services.isPaired()) { + const errorMsg = 'Device not paired'; + errorResponse(errorMsg, event.origin); + return; + } + + try { + console.log('📥 [GetProfile] Réception de la demande'); + const { sessionToken } = event.data; + + if (!sessionToken || !tokenService.validateToken(sessionToken, event.origin)) { + throw new Error('Invalid or expired session token'); + } + + console.log('🔍 [GetProfile] Récupération des profils'); + const processes = await services.getProcesses(); + console.log('Processes après résolution:', processes); + + let decryptedData: Record = {}; + + const processArray = Array.isArray(processes) ? processes : Object.values(processes); + + if (processArray && processArray.length > 0) { + console.log(`📋 Traitement de ${processArray.length} processus`); + + for (const process of processArray) { + try { + console.log('Process en cours:', process); + if (process && process.id && !process.certified) { + console.log(`🔄 Traitement du processus non certifié ${process.id}`); + const states = process.states; + + if (states && states.length > 0) { + for (const state of states) { + if (state && state.pcd_commitment) { + console.log(`📝 Attributs trouvés dans l'état:`, Object.keys(state.pcd_commitment)); + for (const attribute of Object.keys(state.pcd_commitment)) { + try { + const decryptedValue = await services.decryptAttribute(process.id, state, attribute); + if (decryptedValue) { + decryptedData[attribute] = decryptedValue; + console.log(`✅ Attribut déchiffré: ${attribute} = ${decryptedValue}`); + } + } catch (e) { + console.warn(`⚠️ Échec du déchiffrement de ${attribute}:`, e); + } + } + } + } + } + } + } catch (processError) { + console.error(`❌ Erreur lors du traitement du processus:`, processError); + } + } + } else { + console.warn('❌ Aucun processus trouvé ou tableau vide'); + } + + console.log('📤 [GetProfile] Données déchiffrées:', decryptedData); + window.parent.postMessage( + { + type: MessageType.DATA_RETRIEVED, + data: decryptedData, + sessionToken + }, + event.origin + ); + } catch (e) { + console.error('❌ Erreur:', e); + errorResponse(`Failed to get profile: ${e}`, event.origin); + } + }; + + window.addEventListener('message', handleRequestLink); + window.addEventListener('message', handleAddProfile); + window.addEventListener('message', handleAddFolder); + window.addEventListener('message', handleDecryptState); + window.addEventListener('message', handleGetProfile); + + window.parent.postMessage({ type: 'LISTENING' }, '*'); } async function cleanPage() {