import { v4 as uuidv4 } from 'uuid'; import IframeReference from './IframeReference'; import EventBus from './EventBus'; import User from './User'; import MapUtils from './MapUtils'; import { FileBlob } from '../front/Api/Entities/types'; export default class MessageBus { private static instance: MessageBus; private isListening: boolean = false; private messagesSent: Set = new Set(); public static getInstance(): MessageBus { if (!MessageBus.instance) { MessageBus.instance = new MessageBus(); } return MessageBus.instance; } public initMessageListener(): void { window.addEventListener('message', this.handleMessage.bind(this)); } public destroyMessageListener(): void { window.removeEventListener('message', this.handleMessage.bind(this)); this.isListening = false; // Reset the flag when destroying listener } public resetListeningState(): void { this.isListening = false; // Allow external components to reset listening state } public isReady(): Promise { if (this.isListening) { return Promise.resolve(); } return new Promise((resolve: () => void) => { const unsubscribe = EventBus.getInstance().on('IS_READY', () => { unsubscribe(); resolve(); }); }); } public isWaitingForMessage(): boolean { return this.messagesSent.size > 0; } public requestLink(): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { const messageId = `REQUEST_LINK_${uuidv4()}`; console.log('[MessageBus] requestLink - waiting for messageId:', messageId); const unsubscribe = EventBus.getInstance().on('LINK_ACCEPTED', (responseId: string, message: { accessToken: string, refreshToken: string }) => { console.log('[MessageBus] LINK_ACCEPTED received with responseId:', responseId, 'expected:', messageId); if (responseId !== messageId) { return; } console.log('[MessageBus] LINK_ACCEPTED matched - resolving'); unsubscribe(); unsubscribeError(); User.getInstance().setTokens(message.accessToken, message.refreshToken); resolve(); }); const unsubscribeError = EventBus.getInstance().on('ERROR_LINK_ACCEPTED', (responseId: string, error: string) => { console.log('[MessageBus] ERROR_LINK_ACCEPTED received with responseId:', responseId, 'expected:', messageId); if (responseId !== messageId) { return; } console.log('[MessageBus] ERROR_LINK_ACCEPTED matched - rejecting with error:', error); unsubscribe(); unsubscribeError(); reject(error); }); console.log('[MessageBus] requestLink - sending REQUEST_LINK message'); this.sendMessage({ type: 'REQUEST_LINK', messageId }); }); } public createPairing(): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `CREATE_PAIRING_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('PAIRING_CREATED', (responseId: string, pairingId: string) => { if (responseId !== messageId) { return; } unsubscribe(); User.getInstance().setPairingId(pairingId); resolve(); }); const unsubscribeError = EventBus.getInstance().on('ERROR_CREATE_PAIRING', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'CREATE_PAIRING', accessToken, messageId }); }).catch(reject); }); } public getPairingId(): Promise { return new Promise((resolve: (pairingId: string) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `GET_PAIRING_ID_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('GET_PAIRING_ID', (responseId: string, pairingId: string) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(pairingId); }); const unsubscribeError = EventBus.getInstance().on('ERROR_GET_PAIRING_ID', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'GET_PAIRING_ID', accessToken, messageId }); }).catch(reject); }); } public getFileByUid(uid: string): Promise { return new Promise((resolve: (files: any[]) => void, reject: (error: string) => void) => { this.getProcesses().then(async (processes: any) => { const files: any[] = []; for (const processId of Object.keys(processes)) { const process = processes[processId]; if (!process.states) { continue; } const publicDataDecoded: { [key: string]: any } = {}; // We only care about the public data as they are in the last commited state const processTip = process.states[process.states.length - 1].commited_in; const lastCommitedState = process.states.findLast((state: any) => state.commited_in !== processTip); if (!lastCommitedState) { continue; } const publicDataEncoded = lastCommitedState.public_data; if (!publicDataEncoded) { continue; } for (const key of Object.keys(publicDataEncoded)) { publicDataDecoded[key] = await this.getPublicData(publicDataEncoded[key]); } if (!(publicDataDecoded['uid'] && publicDataDecoded['uid'] === uid && publicDataDecoded['utype'] && publicDataDecoded['utype'] === 'file' && publicDataDecoded['isDeleted'] === 'false')) { continue; } try { const lastArchivedAtFileState = process.states.findLast((state: any) => state.pcd_commitment['archived_at']); if (lastArchivedAtFileState) { const archivedAt = (await this.getPublicData(lastArchivedAtFileState.public_data['archived_at'])); if (archivedAt) { continue; } } } catch (error) { } // We take the file in it's latest commited state let file: any; // Which is the last state that updated file_blob? const lastUpdatedFileNameState = process.states.findLast((state: any) => state.pcd_commitment['file_name']); const lastUpdatedFileState = process.states.findLast((state: any) => state.pcd_commitment['file_blob']); if (!lastUpdatedFileNameState || !lastUpdatedFileState) { continue; } try { const fileName: string = (await this.getData(processId, lastUpdatedFileNameState.state_id)).file_name; const processData = await this.getData(processId, lastUpdatedFileState.state_id); const isEmpty = Object.keys(processData).length === 0; if (isEmpty) { continue; } const publicDataEncoded = lastUpdatedFileState.public_data; if (!publicDataEncoded) { continue; } if (!file) { file = { processId, lastUpdatedFileState, processData, publicDataDecoded, }; } else { for (const key of Object.keys(processData)) { file.processData[key] = processData[key]; } file.lastUpdatedFileState = lastUpdatedFileState; for (const key of Object.keys(publicDataDecoded)) { file.publicDataDecoded[key] = publicDataDecoded[key]; } } file.processData.file_name = fileName; } catch (error) { console.error(error); } files.push(file); break; } resolve(files[0]); }).catch(reject); }); } public getFileByUtype(utype: string): Promise { return new Promise((resolve: (files: any[]) => void, reject: (error: string) => void) => { this.getProcesses().then(async (processes: any) => { const files: any[] = []; for (const processId of Object.keys(processes)) { const process = processes[processId]; if (!process.states) { continue; } const publicDataDecoded: { [key: string]: any } = {}; // We only care about the public data as they are in the last commited state const processTip = process.states[process.states.length - 1].commited_in; const lastCommitedState = process.states.findLast((state: any) => state.commited_in !== processTip); if (!lastCommitedState) { continue; } const publicDataEncoded = lastCommitedState.public_data; if (!publicDataEncoded) { continue; } for (const key of Object.keys(publicDataEncoded)) { publicDataDecoded[key] = await this.getPublicData(publicDataEncoded[key]); } if (!(publicDataDecoded['uid'] && publicDataDecoded['utype'] && publicDataDecoded['utype'] === utype && publicDataDecoded['isDeleted'] === 'false')) { continue; } try { const lastArchivedAtFileState = process.states.findLast((state: any) => state.pcd_commitment['archived_at']); if (lastArchivedAtFileState) { const archivedAt = (await this.getPublicData(lastArchivedAtFileState.public_data['archived_at'])); if (archivedAt) { continue; } } } catch (error) { } // We take the file in it's latest commited state let file: any; // Which is the last state that updated file_blob? const lastUpdatedFileNameState = process.states.findLast((state: any) => state.pcd_commitment['file_name']); const lastUpdatedFileState = process.states.findLast((state: any) => state.pcd_commitment['file_blob']); if (!lastUpdatedFileNameState || !lastUpdatedFileState) { continue; } try { const fileName: string = (await this.getData(processId, lastUpdatedFileNameState.state_id)).file_name; const processData = await this.getData(processId, lastUpdatedFileState.state_id); const isEmpty = Object.keys(processData).length === 0; if (isEmpty) { continue; } const publicDataEncoded = lastUpdatedFileState.public_data; if (!publicDataEncoded) { continue; } if (!file) { file = { processId, lastUpdatedFileState, processData, publicDataDecoded, }; } else { for (const key of Object.keys(processData)) { file.processData[key] = processData[key]; } file.lastUpdatedFileState = lastUpdatedFileState; for (const key of Object.keys(publicDataDecoded)) { file.publicDataDecoded[key] = publicDataDecoded[key]; } } file.processData.file_name = fileName; } catch (error) { console.error(error); } files.push(file); break; } resolve(files[0]); }).catch(reject); }); } public getRolesForProcess(processId: string): Promise { return new Promise((resolve: (roles: any[]) => void, reject: (error: string) => void) => { this.getAllProcesses().then((processes: any) => { const process = processes[processId]; if (!process.states || process.states.length < 2) { reject('No states found for process'); } const roles = process.states[process.states.length - 2].roles; if (!roles) { reject('No roles found for process'); } resolve(roles); }); }); } // Returns all processes details, including processes we only have public data for public getAllProcessesDecoded(filterPublicValues: (publicValues: { [key: string]: any }) => boolean): Promise { return new Promise((resolve: (processesDecoded: any[]) => void, reject: (error: string) => void) => { this.getAllProcesses().then(async (processes: any) => { const processesDecoded: any[] = []; for (const processId of Object.keys(processes)) { const process = processes[processId]; if (!process.states) { continue; } const publicDataDecoded: { [key: string]: any } = {}; for (let stateId = 0; stateId < process.states.length - 1; stateId++) { const state = process.states[stateId]; if (!state) { continue; } const publicDataEncoded = state.public_data; if (!publicDataEncoded) { continue; } for (const key of Object.keys(publicDataEncoded)) { publicDataDecoded[key] = await this.getPublicData(publicDataEncoded[key]); } } if (!filterPublicValues(publicDataDecoded)) { continue; } let processDecoded: any; for (let stateId = 0; stateId < process.states.length - 1; stateId++) { const lastState = process.states[stateId]; if (!lastState) { continue; } const lastStateId = lastState.state_id; if (!lastStateId) { continue; } try { let processData = await this.getData(processId, lastStateId); if (!processData) { continue; } const isEmpty = Object.keys(processData).length === 0; if (isEmpty) { continue; } for (const key of Object.keys(publicDataDecoded)) { processData[key] = publicDataDecoded[key]; } processData = MapUtils.toJson(processData); if (!processDecoded) { processDecoded = { processId, lastStateId, processData, }; } else { for (const key of Object.keys(processData)) { processDecoded.processData[key] = processData[key]; } processDecoded.lastStateId = lastStateId; } } catch (error) { console.error(error); } } processesDecoded.push(processDecoded); } resolve(processesDecoded); }).catch(reject); }); } // Returns details about processes that we are involved in public getProcessesDecoded(filterPublicValues: (publicValues: { [key: string]: any }) => boolean): Promise { return new Promise((resolve: (processesDecoded: any[]) => void, reject: (error: string) => void) => { this.getProcesses().then(async (processes: any) => { const processesDecoded: any[] = []; for (const processId of Object.keys(processes)) { const process = processes[processId]; if (!process.states) { continue; } const publicDataDecoded: { [key: string]: any } = {}; for (let stateId = 0; stateId < process.states.length - 1; stateId++) { const state = process.states[stateId]; if (!state) { continue; } const publicDataEncoded = state.public_data; if (!publicDataEncoded) { continue; } for (const key of Object.keys(publicDataEncoded)) { publicDataDecoded[key] = await this.getPublicData(publicDataEncoded[key]); } } if (!filterPublicValues(publicDataDecoded)) { continue; } let processDecoded: any; for (let stateId = 0; stateId < process.states.length - 1; stateId++) { const lastState = process.states[stateId]; if (!lastState) { continue; } const lastStateId = lastState.state_id; if (!lastStateId) { continue; } try { let processData = await this.getData(processId, lastStateId); if (!processData) { continue; } const isEmpty = Object.keys(processData).length === 0; if (isEmpty) { continue; } for (const key of Object.keys(publicDataDecoded)) { processData[key] = publicDataDecoded[key]; } processData = MapUtils.toJson(processData); if (!processDecoded) { processDecoded = { processId, lastStateId, processData, }; } else { for (const key of Object.keys(processData)) { processDecoded.processData[key] = processData[key]; } processDecoded.lastStateId = lastStateId; } } catch (error) { console.error(error); } } processesDecoded.push(processDecoded); } resolve(processesDecoded); }).catch(reject); }); } public createProcess(processData: any, privateFields: string[], roles: {}): Promise { return new Promise((resolve: (processCreated: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `CREATE_PROCESS_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(processCreated); }); const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESS_CREATED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'CREATE_PROCESS', processData: processData, privateFields: privateFields, roles: roles, accessToken, messageId }); }).catch(reject); }); } public updateProcess(processId: string, newData: any, privateFields: string[], roles: {} | null): Promise { return new Promise((resolve: (processUpdated: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = uuidv4(); const unsubscribe = EventBus.getInstance().on('PROCESS_UPDATED', (responseId: string, processUpdated: any) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(processUpdated); }); const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESS_UPDATED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'UPDATE_PROCESS', processId, newData, privateFields, roles, accessToken, messageId }); }).catch(reject); }); } // Returns all processes, including processes that have nothing to do with us public getAllProcesses(): Promise<{ [processId: string]: any }> { return new Promise<{ [processId: string]: any }>((resolve: (processes: { [processId: string]: any }) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `GET_PROCESSES_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('PROCESSES_RETRIEVED', (responseId: string, processes: any) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(processes); }); const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESSES_RETRIEVED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'GET_PROCESSES', accessToken, messageId }); }).catch(reject); }); } // Returns processes that we are involved in public getProcesses(): Promise<{ [processId: string]: any }> { return new Promise<{ [processId: string]: any }>((resolve: (processes: { [processId: string]: any }) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `GET_PROCESSES_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('PROCESSES_RETRIEVED', (responseId: string, processes: any) => { if (responseId !== messageId) { return; } unsubscribe(); // Filter processes by my processes setTimeout(() => { this.getMyProcesses().then((myProcesses: string[]) => { const processesFiltered: { [processId: string]: any } = {}; for (const processId of myProcesses) { const process = processes[processId]; if (process) { processesFiltered[processId] = process; } } resolve(processesFiltered); }).catch(reject); }, 500); }); const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESSES_RETRIEVED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'GET_PROCESSES', accessToken, messageId }); }).catch(reject); }); } // Returns the processes id of processes we are involved in // It's meant to be used to filter processes in the getProcesses() method public getMyProcesses(): Promise { return new Promise((resolve: (processes: string[]) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `GET_MY_PROCESSES_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('GET_MY_PROCESSES', (responseId: string, processes: string[]) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(processes); }); const unsubscribeError = EventBus.getInstance().on('ERROR_GET_MY_PROCESSES', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'GET_MY_PROCESSES', accessToken, messageId }); }).catch(reject); }); } public notifyUpdate(processId: string, stateId: string): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = uuidv4(); const unsubscribe = EventBus.getInstance().on('UPDATE_NOTIFIED', (responseId: string) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(); }); const unsubscribeError = EventBus.getInstance().on('ERROR_UPDATE_NOTIFIED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'NOTIFY_UPDATE', processId, stateId, accessToken, messageId }); }).catch(reject); }); } public validateState(processId: string, stateId: string): Promise { return new Promise((resolve: (stateValidated: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `VALIDATE_STATE_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('STATE_VALIDATED', (responseId: string, stateValidated: any) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(stateValidated); }); const unsubscribeError = EventBus.getInstance().on('ERROR_STATE_VALIDATED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'VALIDATE_STATE', processId, stateId, accessToken, messageId }); }).catch(reject); }); } public getData(processId: string, stateId: string): Promise { return new Promise((resolve: (data: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `DATA_RETRIEVED_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('DATA_RETRIEVED', (responseId: string, data: any) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(data); }); const unsubscribeError = EventBus.getInstance().on('ERROR_DATA_RETRIEVED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'RETRIEVE_DATA', processId, stateId, accessToken, messageId }); }).catch(reject); }); } public getPublicData(encodedData: number[]): Promise { return new Promise((resolve: (data: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `PUBLIC_DATA_DECODED_${uuidv4()}` const unsubscribe = EventBus.getInstance().on('PUBLIC_DATA_DECODED', (responseId: string, data: any) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(data); }); const unsubscribeError = EventBus.getInstance().on('ERROR_PUBLIC_DATA_DECODED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'DECODE_PUBLIC_DATA', encodedData, accessToken, messageId }); }).catch(reject); }); } /** * Hash a document file using SHA-256 * @param fileBlob - The file blob to hash * @returns Promise - The SHA-256 hash of the file */ public hashDocument(fileBlob: FileBlob, commitedIn: string): Promise { return new Promise((resolve: (hash: string) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `HASH_VALUE_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('VALUE_HASHED', (responseId: string, hash: string) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(hash); }); const unsubscribeError = EventBus.getInstance().on('ERROR_VALUE_HASHED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; const label = 'file_blob'; this.sendMessage({ type: 'HASH_VALUE', accessToken, commitedIn, label, fileBlob, messageId }); }).catch(reject); }); } public generateMerkleProof(processState: any, attributeName: string): Promise { return new Promise((resolve: (proof: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `GET_MERKLE_PROOF_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('MERKLE_PROOF_RETRIEVED', (responseId: string, proof: any) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(proof); }); const unsubscribeError = EventBus.getInstance().on('ERROR_MERKLE_PROOF_RETRIEVED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'GET_MERKLE_PROOF', accessToken, processState, attributeName, messageId }); }).catch(reject); }); } public verifyMerkleProof(merkleProof: string, documentHash: string): Promise { return new Promise((resolve: (isValid: boolean) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const messageId = `VALIDATE_MERKLE_PROOF_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('MERKLE_PROOF_VALIDATED', (responseId: string, isValid: boolean) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(isValid); }); const unsubscribeError = EventBus.getInstance().on('ERROR_MERKLE_PROOF_VALIDATED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; this.sendMessage({ type: 'VALIDATE_MERKLE_PROOF', accessToken, merkleProof, documentHash, messageId }); }).catch(reject); }); } private validateToken(): Promise { return new Promise((resolve: (isValid: boolean) => void, reject: (error: string) => void) => { const messageId = `VALIDATE_TOKEN_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('TOKEN_VALIDATED', (responseId: string, isValid: boolean) => { if (responseId !== messageId) { return; } unsubscribe(); resolve(isValid); }); const unsubscribeError = EventBus.getInstance().on('ERROR_TOKEN_VALIDATED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const accessToken = user.getAccessToken()!; const refreshToken = user.getRefreshToken()!; this.sendMessage({ type: 'VALIDATE_TOKEN', accessToken, refreshToken, messageId }); }); } private renewToken(): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { const messageId = `RENEW_TOKEN_${uuidv4()}`; const unsubscribe = EventBus.getInstance().on('TOKEN_RENEWED', (responseId: string, message: { accessToken: string, refreshToken: string }) => { if (responseId !== messageId) { return; } unsubscribe(); User.getInstance().setTokens(message.accessToken, message.refreshToken); resolve(); }); const unsubscribeError = EventBus.getInstance().on('ERROR_TOKEN_RENEWED', (responseId: string, error: string) => { if (responseId !== messageId) { return; } unsubscribeError(); reject(error); }); const user = User.getInstance(); const refreshToken = user.getRefreshToken()!; this.sendMessage({ type: 'RENEW_TOKEN', refreshToken, messageId }); }); } private checkToken(): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { this.isReady().then(() => { this.validateToken().then((isValid: boolean) => { if (!isValid) { this.renewToken().then(resolve).catch(reject); } else { resolve(); } }).catch(reject); }).catch(reject); }); } private sendMessage(message: any): void { this.isReady().then(() => { try { const targetOrigin = IframeReference.getTargetOrigin(); const iframe = IframeReference.getIframe(); iframe.contentWindow?.postMessage(message, targetOrigin); this.messagesSent.add(message.messageId); } catch (error) { console.error('[MessageBus] sendMessage: error', error); } }).catch(console.error); } private handleMessage(event: MessageEvent): void { if (!event.data || ['PassClientScriptReady', 'PassIFrameReady', 'Pass::MainWorld::Message', 'Pass::MainWorld::Response'].includes(event.data.type)) { return; } try { const targetOrigin = IframeReference.getTargetOrigin(); if (event.origin !== targetOrigin) { throw new Error(`origin don't match: expected ${targetOrigin}, got ${event.origin} with type ${event.data.type}`); } if (!event.data || typeof event.data !== 'object') { throw new Error('data not found'); } } catch (error) { console.error('[MessageBus] handleMessage:', error); return; } const message = event.data; switch (message.type) { case 'LISTENING': this.isListening = true; EventBus.getInstance().emit('IS_READY'); break; case 'LINK_ACCEPTED': this.doHandleMessage(message.messageId, 'LINK_ACCEPTED', message, (message: any) => ({ accessToken: message.accessToken, refreshToken: message.refreshToken })); break; case 'VALIDATE_TOKEN': this.doHandleMessage(message.messageId, 'TOKEN_VALIDATED', message, (message: any) => message.isValid); break; case 'RENEW_TOKEN': this.doHandleMessage(message.messageId, 'TOKEN_RENEWED', message, (message: any) => ({ accessToken: message.accessToken, refreshToken: message.refreshToken })); break; case 'GET_PAIRING_ID': this.doHandleMessage(message.messageId, 'GET_PAIRING_ID', message, (message: any) => message.userPairingId); break; case 'PROCESS_CREATED': // CREATE_PROCESS this.doHandleMessage(message.messageId, 'PROCESS_CREATED', message, (message: any) => message.processCreated); break; case 'PROCESS_UPDATED': // UPDATE_PROCESS this.doHandleMessage(message.messageId, 'PROCESS_UPDATED', message, (message: any) => message.updatedProcess); break; case 'PROCESSES_RETRIEVED': // GET_PROCESSES this.doHandleMessage(message.messageId, 'PROCESSES_RETRIEVED', message, (message: any) => message.processes); break; case 'GET_MY_PROCESSES': // GET_MY_PROCESSES this.doHandleMessage(message.messageId, 'GET_MY_PROCESSES', message, (message: any) => message.myProcesses); break; case 'DATA_RETRIEVED': // RETRIEVE_DATA this.doHandleMessage(message.messageId, 'DATA_RETRIEVED', message, (message: any) => message.data); break; case 'PUBLIC_DATA_DECODED': // DECODE_PUBLIC_DATA this.doHandleMessage(message.messageId, 'PUBLIC_DATA_DECODED', message, (message: any) => message.decodedData); break; case 'UPDATE_NOTIFIED': // NOTIFY_UPDATE this.doHandleMessage(message.messageId, 'UPDATE_NOTIFIED', message, () => { }); break; case 'STATE_VALIDATED': // VALIDATE_STATE this.doHandleMessage(message.messageId, 'STATE_VALIDATED', message, (message: any) => message.validatedProcess); break; case 'VALUE_HASHED': // HASH_VALUE this.doHandleMessage(message.messageId, 'VALUE_HASHED', message, (message: any) => message.hash); break; case 'MERKLE_PROOF_RETRIEVED': // GET_MERKLE_PROOF this.doHandleMessage(message.messageId, 'MERKLE_PROOF_RETRIEVED', message, (message: any) => message.proof); break; case 'MERKLE_PROOF_VALIDATED': // VALIDATE_MERKLE_PROOF this.doHandleMessage(message.messageId, 'MERKLE_PROOF_VALIDATED', message, (message: any) => message.isValid); break; case 'ERROR': console.error('Error:', message); // Extract operation type from messageId by splitting on last underscore const operationType = this.extractOperationTypeFromMessageId(message.messageId); EventBus.getInstance().emit(`ERROR_${operationType}`, message.messageId, message.error); break; } } private extractOperationTypeFromMessageId(messageId: string): string { // Split on last underscore to extract operation type // e.g., "GET_PAIRING_ID_abc123" -> "GET_PAIRING_ID" const lastUnderscoreIndex = messageId.lastIndexOf('_'); if (lastUnderscoreIndex === -1) { return messageId; // No underscore found, return as-is } return messageId.substring(0, lastUnderscoreIndex); } private doHandleMessage(messageId: string, messageType: string, message: any, callback: (message: any) => any) { EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit(messageType, messageId, callback(message)); this.messagesSent.delete(messageId); } }