skeleton/src/sdk/MessageBus.ts
2025-06-05 09:40:35 +02:00

423 lines
14 KiB
TypeScript

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<void> {
return new Promise<void>((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<void> {
return new Promise<void>((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<boolean> {
return new Promise<boolean>((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<void> {
return new Promise<void>((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<any> {
return new Promise<any>((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<any> {
return new Promise<any>((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<ProfileData> {
return new Promise<ProfileData>((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<FolderData> {
return new Promise<FolderData>((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<void> {
return new Promise<void>((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;
}
}
}