From de2ff309609e6242cbbf1c60c4286aa43c4d65f8 Mon Sep 17 00:00:00 2001 From: Sosthene Date: Wed, 27 Aug 2025 17:39:56 +0200 Subject: [PATCH] MessageBus refactoring * better error management * Keep tracks of when messages sent don't have answers * New convenient methods --- src/sdk/MessageBus.ts | 251 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 228 insertions(+), 23 deletions(-) diff --git a/src/sdk/MessageBus.ts b/src/sdk/MessageBus.ts index cb0297c3..5ae9ef9b 100644 --- a/src/sdk/MessageBus.ts +++ b/src/sdk/MessageBus.ts @@ -11,8 +11,8 @@ import { FileBlob } from '../front/Api/Entities/types'; export default class MessageBus { private static instance: MessageBus; - private errors: { [key: string]: string } = {}; private isListening: boolean = false; + private messagesSent: Set = new Set(); public static getInstance(): MessageBus { if (!MessageBus.instance) { @@ -27,6 +27,11 @@ export default class MessageBus { 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 { @@ -41,27 +46,39 @@ export default class MessageBus { }); } + 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 @@ -69,6 +86,40 @@ export default class MessageBus { }); } + 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(() => { @@ -98,7 +149,7 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } @@ -298,6 +349,114 @@ export default class MessageBus { }); } + 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) => { @@ -417,7 +576,7 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } @@ -454,12 +613,48 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } - public getProcesses(): Promise { - return new Promise((resolve: (processes: any) => void, reject: (error: string) => void) => { + // 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()}`; @@ -471,7 +666,7 @@ export default class MessageBus { // Filter processes by my processes setTimeout(() => { - this.getMyProcesses().then((myProcesses: any) => { + this.getMyProcesses().then((myProcesses: string[]) => { const processesFiltered: { [processId: string]: any } = {}; for (const processId of myProcesses) { const process = processes[processId]; @@ -500,16 +695,18 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } - public getMyProcesses(): Promise { - return new Promise((resolve: (processes: any) => void, reject: (error: string) => void) => { + // 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: any) => { + const unsubscribe = EventBus.getInstance().on('GET_MY_PROCESSES', (responseId: string, processes: string[]) => { if (responseId !== messageId) { return; } @@ -533,7 +730,7 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } @@ -568,7 +765,7 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } @@ -603,7 +800,7 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } @@ -638,7 +835,7 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } @@ -672,7 +869,7 @@ export default class MessageBus { accessToken, messageId }); - }).catch(console.error); + }).catch(reject); }); } @@ -874,6 +1071,7 @@ export default class MessageBus { 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); } @@ -975,19 +1173,26 @@ export default class MessageBus { case 'ERROR': console.error('Error:', message); - this.errors[message.messageId] = message.error; + // 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 doHandleMessage(messageId: string, messageType: string, message: any, callback: (message: any) => any) { - if (this.errors[messageId]) { - const error = this.errors[messageId]; - delete this.errors[messageId]; - EventBus.getInstance().emit(`ERROR_${messageType}`, messageId, error); - return; + 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); } }