import IframeReference from './IframeReference'; import EventBus from './EventBus'; import UserStore from './UserStrore'; import type { ProfileData } from './models/ProfileData'; import type { FolderData } from './models/FolderData'; import { v4 as uuidv4 } from 'uuid'; export default class MessageBus { private static instance: MessageBus; private readonly origin: string; private messageListener: ((event: MessageEvent) => void) | null = null; private errors: { [key: string]: string } = {}; private constructor(origin: string) { this.origin = origin; } public static getInstance(origin: string): MessageBus { if (!MessageBus.instance) { MessageBus.instance = new MessageBus(origin); } return MessageBus.instance; } public isReady(): Promise { return new Promise((resolve: () => void) => { const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('IS_READY', (responseId: string) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); resolve(); }); }); } public requestLink(): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('LINK_ACCEPTED', (responseId: string, accessToken: string, refreshToken: string) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); UserStore.getInstance().connect(accessToken, refreshToken); resolve(); }); const unsubscribeError = EventBus.getInstance().on('ERROR_LINK_ACCEPTED', (responseId: string, error: string) => { if (responseId !== correlationId) { return; } unsubscribeError(); this.destroyMessageListener(); reject(error); }); this.sendMessage({ type: 'REQUEST_LINK' }); }); } public validateToken(): Promise { return new Promise((resolve: (isValid: boolean) => void, reject: (error: string) => void) => { const userStore = UserStore.getInstance(); if (!userStore.isConnected()) { reject('User is not connected'); return; } const accessToken = userStore.getAccessToken()!; const refreshToken = userStore.getRefreshToken()!; const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('TOKEN_VALIDATED', (responseId: string, isValid: boolean) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); resolve(isValid); }); const unsubscribeError = EventBus.getInstance().on('ERROR_TOKEN_VALIDATED', (responseId: string, error: string) => { if (responseId !== correlationId) { return; } unsubscribeError(); this.destroyMessageListener(); reject(error); }); this.sendMessage({ type: 'VALIDATE_TOKEN', accessToken, refreshToken }); }); } public renewToken(): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { const userStore = UserStore.getInstance(); if (!userStore.isConnected()) { reject('User is not connected'); return; } const refreshToken = userStore.getRefreshToken()!; const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('TOKEN_RENEWED', (responseId: string, accessToken: string, refreshToken: string) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); UserStore.getInstance().connect(accessToken, refreshToken); resolve(); }); const unsubscribeError = EventBus.getInstance().on('ERROR_TOKEN_RENEWED', (responseId: string, error: string) => { if (responseId !== correlationId) { return; } unsubscribeError(); this.destroyMessageListener(); reject(error); }); this.sendMessage({ type: 'RENEW_TOKEN', refreshToken }); }); } public getProcesses(): Promise { return new Promise((resolve: (processes: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('PROCESSES_RETRIEVED', (responseId: string, processes: any) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); resolve(processes); }); const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESSES_RETRIEVED', (responseId: string, error: string) => { if (responseId !== correlationId) { return; } unsubscribeError(); this.destroyMessageListener(); reject(error); }); this.sendMessage({ type: 'GET_PROCESSES' }); }).catch(console.error); }); } public getData(processId: string, stateId: string): Promise { return new Promise((resolve: (data: any) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const userStore = UserStore.getInstance(); const accessToken = userStore.getAccessToken()!; const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('DATA_RETRIEVED', (responseId: string, data: any) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); resolve(data); }); const unsubscribeError = EventBus.getInstance().on('ERROR_DATA_RETRIEVED', (responseId: string, error: string) => { if (responseId !== correlationId) { return; } unsubscribeError(); this.destroyMessageListener(); reject(error); }); this.sendMessage({ type: 'RETRIEVE_DATA', processId, stateId, token: accessToken }); }).catch(console.error); }); } public createProfile(profileData: ProfileData): Promise { return new Promise((resolve: (profileData: ProfileData) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const userStore = UserStore.getInstance(); const accessToken = userStore.getAccessToken()!; const refreshToken = userStore.getRefreshToken()!; const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('PROFILE_CREATED', (responseId: string, profileData: ProfileData) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); resolve(profileData); }); const unsubscribeError = EventBus.getInstance().on('ERROR_PROFILE_CREATED', (responseId: string, error: string) => { if (responseId !== correlationId) { return; } unsubscribeError(); this.destroyMessageListener(); reject(error); }); this.sendMessage({ type: 'CREATE_PROFILE', profileData, accessToken, refreshToken }); }).catch(console.error); }); } public createFolder(folderData: FolderData): Promise { return new Promise((resolve: (folderData: FolderData) => void, reject: (error: string) => void) => { this.checkToken().then(() => { const userStore = UserStore.getInstance(); const accessToken = userStore.getAccessToken()!; const correlationId = uuidv4(); this.initMessageListener(correlationId); const unsubscribe = EventBus.getInstance().on('FOLDER_CREATED', (responseId: string, folderData: FolderData) => { if (responseId !== correlationId) { return; } unsubscribe(); this.destroyMessageListener(); resolve(folderData); }); const unsubscribeError = EventBus.getInstance().on('ERROR_FOLDER_CREATED', (responseId: string, error: string) => { if (responseId !== correlationId) { return; } unsubscribeError(); this.destroyMessageListener(); reject(error); }); this.sendMessage({ type: 'CREATE_FOLDER', folderData, token: accessToken }); }).catch(console.error); }); } private checkToken(): Promise { return new Promise((resolve: () => void, reject: (error: string) => void) => { this.validateToken().then((isValid: boolean) => { if (!isValid) { this.renewToken().then(resolve).catch(reject); } else { resolve(); } }).catch(reject); }); } private sendMessage(message: any): void { const iframe = IframeReference.getIframe(); if (!iframe) { console.error('[MessageBus] sendMessage: iframe not found'); return; } console.log('[MessageBus] sendMessage:', message); iframe.contentWindow?.postMessage(message, this.origin); } private initMessageListener(correlationId: string): void { this.destroyMessageListener(); this.messageListener = this.handleMessage.bind(this, correlationId); window.addEventListener('message', this.messageListener); } private destroyMessageListener(): void { if (this.messageListener) { window.removeEventListener('message', this.messageListener); this.messageListener = null; } } private handleMessage(correlationId: string, event: MessageEvent): void { if (event.origin !== this.origin) { return; } if (!event.data || typeof event.data !== 'object') { return; } const message = event.data; switch (message.type) { case 'LISTENING': EventBus.getInstance().emit('IS_READY', correlationId); break; case 'LINK_ACCEPTED': if (this.errors[correlationId]) { const error = this.errors[correlationId]; delete this.errors[correlationId]; EventBus.getInstance().emit('ERROR_LINK_ACCEPTED', correlationId, error); return; } EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit('LINK_ACCEPTED', correlationId, message.accessToken, message.refreshToken); break; case 'VALIDATE_TOKEN': if (this.errors[correlationId]) { const error = this.errors[correlationId]; delete this.errors[correlationId]; EventBus.getInstance().emit('ERROR_TOKEN_VALIDATED', correlationId, error); return; } EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit('TOKEN_VALIDATED', correlationId, message.isValid); break; case 'RENEW_TOKEN': if (this.errors[correlationId]) { const error = this.errors[correlationId]; delete this.errors[correlationId]; EventBus.getInstance().emit('ERROR_TOKEN_RENEWED', correlationId, error); return; } EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit('TOKEN_RENEWED', correlationId, message.accessToken, message.refreshToken); break; case 'PROCESSES_RETRIEVED': // GET_PROCESSES if (this.errors[correlationId]) { const error = this.errors[correlationId]; delete this.errors[correlationId]; EventBus.getInstance().emit('ERROR_PROCESSES_RETRIEVED', correlationId, error); return; } EventBus.getInstance().emit('PROCESSES_RETRIEVED', correlationId, message.processes); break; case 'PROFILE_CREATED': // CREATE_PROFILE if (this.errors[correlationId]) { const error = this.errors[correlationId]; delete this.errors[correlationId]; EventBus.getInstance().emit('ERROR_PROFILE_CREATED', correlationId, error); return; } EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit('PROFILE_CREATED', correlationId, message.profileData); break; case 'FOLDER_CREATED': // CREATE_FOLDER if (this.errors[correlationId]) { const error = this.errors[correlationId]; delete this.errors[correlationId]; EventBus.getInstance().emit('ERROR_FOLDER_CREATED', correlationId, error); return; } EventBus.getInstance().emit('MESSAGE_RECEIVED', message); break; case 'DATA_RETRIEVED': // RETRIEVE_DATA if (this.errors[correlationId]) { const error = this.errors[correlationId]; delete this.errors[correlationId]; EventBus.getInstance().emit('ERROR_DATA_RETRIEVED', correlationId, error); return; } EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit('DATA_RETRIEVED', correlationId, message.data); break; case 'ERROR': console.error('Error:', message); this.errors[correlationId] = message.error; break; } } }