From b9756bc66a2ee877474d9bce69a401ab5c01dac3 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Fri, 26 Dec 2025 20:06:38 -0300 Subject: [PATCH 01/11] chore: refactor listeners.js to typescript --- .../app/apps/server/bridges/listeners.js | 248 ------------ .../app/apps/server/bridges/listeners.ts | 356 ++++++++++++++++++ .../src/server/managers/AppListenerManager.ts | 2 +- 3 files changed, 357 insertions(+), 249 deletions(-) delete mode 100644 apps/meteor/app/apps/server/bridges/listeners.js create mode 100644 apps/meteor/app/apps/server/bridges/listeners.ts diff --git a/apps/meteor/app/apps/server/bridges/listeners.js b/apps/meteor/app/apps/server/bridges/listeners.js deleted file mode 100644 index 31aa2c0052695..0000000000000 --- a/apps/meteor/app/apps/server/bridges/listeners.js +++ /dev/null @@ -1,248 +0,0 @@ -import { LivechatTransferEventType } from '@rocket.chat/apps-engine/definition/livechat'; -import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; - -export class AppListenerBridge { - constructor(orch) { - this.orch = orch; - } - - async handleEvent(event, ...payload) { - // eslint-disable-next-line complexity - const method = (() => { - switch (event) { - case AppInterface.IPostSystemMessageSent: - case AppInterface.IPreMessageSentPrevent: - case AppInterface.IPreMessageSentExtend: - case AppInterface.IPreMessageSentModify: - case AppInterface.IPostMessageSent: - case AppInterface.IPreMessageDeletePrevent: - case AppInterface.IPostMessageDeleted: - case AppInterface.IPreMessageUpdatedPrevent: - case AppInterface.IPreMessageUpdatedExtend: - case AppInterface.IPreMessageUpdatedModify: - case AppInterface.IPostMessageUpdated: - case AppInterface.IPostMessageReacted: - case AppInterface.IPostMessageFollowed: - case AppInterface.IPostMessagePinned: - case AppInterface.IPostMessageStarred: - case AppInterface.IPostMessageReported: - return 'messageEvent'; - case AppInterface.IPreRoomCreatePrevent: - case AppInterface.IPreRoomCreateExtend: - case AppInterface.IPreRoomCreateModify: - case AppInterface.IPostRoomCreate: - case AppInterface.IPreRoomDeletePrevent: - case AppInterface.IPostRoomDeleted: - case AppInterface.IPreRoomUserJoined: - case AppInterface.IPostRoomUserJoined: - case AppInterface.IPreRoomUserLeave: - case AppInterface.IPostRoomUserLeave: - return 'roomEvent'; - /** - * @deprecated please prefer the AppInterface.IPostLivechatRoomClosed event - */ - case AppInterface.ILivechatRoomClosedHandler: - case AppInterface.IPreLivechatRoomCreatePrevent: - case AppInterface.IPostLivechatRoomStarted: - case AppInterface.IPostLivechatRoomClosed: - case AppInterface.IPostLivechatAgentAssigned: - case AppInterface.IPostLivechatAgentUnassigned: - case AppInterface.IPostLivechatRoomTransferred: - case AppInterface.IPostLivechatGuestSaved: - case AppInterface.IPostLivechatRoomSaved: - case AppInterface.IPostLivechatDepartmentRemoved: - case AppInterface.IPostLivechatDepartmentDisabled: - return 'livechatEvent'; - case AppInterface.IPostUserCreated: - case AppInterface.IPostUserUpdated: - case AppInterface.IPostUserDeleted: - case AppInterface.IPostUserLogin: - case AppInterface.IPostUserLogout: - case AppInterface.IPostUserStatusChanged: - return 'userEvent'; - default: - return 'defaultEvent'; - } - })(); - - return this[method](event, ...payload); - } - - async defaultEvent(inte, payload) { - return this.orch.getManager().getListenerManager().executeListener(inte, payload); - } - - async messageEvent(inte, message, ...payload) { - const msg = await this.orch.getConverters().get('messages').convertMessage(message); - - const params = (() => { - switch (inte) { - case AppInterface.IPostMessageDeleted: - const [userDeleted] = payload; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userDeleted), - }; - case AppInterface.IPostMessageReacted: - const [userReacted, reaction, isReacted] = payload; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userReacted), - reaction, - isReacted, - }; - case AppInterface.IPostMessageFollowed: - const [userFollowed, isUnfollow] = payload; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userFollowed), - isUnfollow, - }; - case AppInterface.IPostMessagePinned: - const [userPinned, isUnpinned] = payload; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userPinned), - isUnpinned, - }; - case AppInterface.IPostMessageStarred: - const [userStarred, isStarred] = payload; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userStarred), - isStarred, - }; - case AppInterface.IPostMessageReported: - const [userReported, reason] = payload; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userReported), - reason, - }; - default: - return msg; - } - })(); - - const result = await this.orch.getManager().getListenerManager().executeListener(inte, params); - - if (typeof result === 'boolean') { - return result; - } - return this.orch.getConverters().get('messages').convertAppMessage(result); - } - - async roomEvent(inte, room, ...payload) { - const rm = await this.orch.getConverters().get('rooms').convertRoom(room); - - const params = (() => { - switch (inte) { - case AppInterface.IPreRoomUserJoined: - case AppInterface.IPostRoomUserJoined: - const [joiningUser, invitingUser] = payload; - return { - room: rm, - joiningUser: this.orch.getConverters().get('users').convertToApp(joiningUser), - invitingUser: this.orch.getConverters().get('users').convertToApp(invitingUser), - }; - case AppInterface.IPreRoomUserLeave: - case AppInterface.IPostRoomUserLeave: - const [leavingUser, removedBy] = payload; - return { - room: rm, - leavingUser: this.orch.getConverters().get('users').convertToApp(leavingUser), - removedBy: this.orch.getConverters().get('users').convertToApp(removedBy), - }; - default: - return rm; - } - })(); - - const result = await this.orch.getManager().getListenerManager().executeListener(inte, params); - - if (typeof result === 'boolean') { - return result; - } - return this.orch.getConverters().get('rooms').convertAppRoom(result); - } - - async livechatEvent(inte, data) { - switch (inte) { - case AppInterface.IPostLivechatAgentAssigned: - case AppInterface.IPostLivechatAgentUnassigned: - return this.orch - .getManager() - .getListenerManager() - .executeListener(inte, { - room: await this.orch.getConverters().get('rooms').convertRoom(data.room), - agent: this.orch.getConverters().get('users').convertToApp(data.user), - }); - case AppInterface.IPostLivechatRoomTransferred: - const converter = data.type === LivechatTransferEventType.AGENT ? 'users' : 'departments'; - - return this.orch - .getManager() - .getListenerManager() - .executeListener(inte, { - type: data.type, - room: await this.orch.getConverters().get('rooms').convertById(data.room), - from: await this.orch.getConverters().get(converter).convertById(data.from), - to: await this.orch.getConverters().get(converter).convertById(data.to), - }); - case AppInterface.IPostLivechatGuestSaved: - return this.orch - .getManager() - .getListenerManager() - .executeListener(inte, await this.orch.getConverters().get('visitors').convertById(data)); - case AppInterface.IPostLivechatRoomSaved: - return this.orch - .getManager() - .getListenerManager() - .executeListener(inte, await this.orch.getConverters().get('rooms').convertById(data)); - case AppInterface.IPostLivechatDepartmentDisabled: - return this.orch - .getManager() - .getListenerManager() - .executeListener(inte, await this.orch.getConverters().get('departments').convertDepartment(data)); - case AppInterface.IPostLivechatDepartmentRemoved: - return this.orch - .getManager() - .getListenerManager() - .executeListener(inte, await this.orch.getConverters().get('departments').convertDepartment(data)); - default: - const room = await this.orch.getConverters().get('rooms').convertRoom(data); - - return this.orch.getManager().getListenerManager().executeListener(inte, room); - } - } - - async userEvent(inte, data) { - let context; - switch (inte) { - case AppInterface.IPostUserLoggedIn: - case AppInterface.IPostUserLogout: - context = this.orch.getConverters().get('users').convertToApp(data.user); - return this.orch.getManager().getListenerManager().executeListener(inte, context); - case AppInterface.IPostUserStatusChanged: - const { currentStatus, previousStatus } = data; - context = { - user: this.orch.getConverters().get('users').convertToApp(data.user), - currentStatus, - previousStatus, - }; - - return this.orch.getManager().getListenerManager().executeListener(inte, context); - case AppInterface.IPostUserCreated: - case AppInterface.IPostUserUpdated: - case AppInterface.IPostUserDeleted: - context = { - user: this.orch.getConverters().get('users').convertToApp(data.user), - performedBy: this.orch.getConverters().get('users').convertToApp(data.performedBy), - }; - if (inte === AppInterface.IPostUserUpdated) { - context.previousData = this.orch.getConverters().get('users').convertToApp(data.previousUser); - } - return this.orch.getManager().getListenerManager().executeListener(inte, context); - } - } -} diff --git a/apps/meteor/app/apps/server/bridges/listeners.ts b/apps/meteor/app/apps/server/bridges/listeners.ts new file mode 100644 index 0000000000000..b2f972f2c7ca7 --- /dev/null +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -0,0 +1,356 @@ +import type { IAppServerOrchestrator, IAppsMessage, IAppsRoom, IAppsUser, IAppsLivechatRoom, AppEvents } from '@rocket.chat/apps'; +import { LivechatTransferEventType } from '@rocket.chat/apps-engine/definition/livechat'; +import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; +import type { IUserContext, IUserUpdateContext } from '@rocket.chat/apps-engine/definition/users'; +import type { IListenerExecutor } from '@rocket.chat/apps-engine/server/managers/AppListenerManager'; +import type { IMessage, IRoom, IUser, ILivechatDepartment } from '@rocket.chat/core-typings'; + +type LivechatTransferData = { + type: LivechatTransferEventType; + room: string; + from: string; + to: string; +}; + +type LivechatAgentData = { + room: IRoom; + user: IUser; +}; + +type UserStatusChangedData = { + user: IUser; + currentStatus: string; + previousStatus: string; +}; + +type UserCrudData = { + user: IUser; + performedBy: IUser; + previousUser?: IUser; +}; + +export class AppListenerBridge { + constructor(private readonly orch: IAppServerOrchestrator) {} + + async handleEvent(event: AppEvents, ...payload: unknown[]): Promise { + // eslint-disable-next-line complexity + const method = ((): keyof Omit => { + switch (event) { + case AppInterface.IPostSystemMessageSent: + case AppInterface.IPreMessageSentPrevent: + case AppInterface.IPreMessageSentExtend: + case AppInterface.IPreMessageSentModify: + case AppInterface.IPostMessageSent: + case AppInterface.IPreMessageDeletePrevent: + case AppInterface.IPostMessageDeleted: + case AppInterface.IPreMessageUpdatedPrevent: + case AppInterface.IPreMessageUpdatedExtend: + case AppInterface.IPreMessageUpdatedModify: + case AppInterface.IPostMessageUpdated: + case AppInterface.IPostMessageReacted: + case AppInterface.IPostMessageFollowed: + case AppInterface.IPostMessagePinned: + case AppInterface.IPostMessageStarred: + case AppInterface.IPostMessageReported: + return 'messageEvent'; + case AppInterface.IPreRoomCreatePrevent: + case AppInterface.IPreRoomCreateExtend: + case AppInterface.IPreRoomCreateModify: + case AppInterface.IPostRoomCreate: + case AppInterface.IPreRoomDeletePrevent: + case AppInterface.IPostRoomDeleted: + case AppInterface.IPreRoomUserJoined: + case AppInterface.IPostRoomUserJoined: + case AppInterface.IPreRoomUserLeave: + case AppInterface.IPostRoomUserLeave: + return 'roomEvent'; + /** + * @deprecated please prefer the AppInterface.IPostLivechatRoomClosed event + */ + case AppInterface.ILivechatRoomClosedHandler: + case AppInterface.IPreLivechatRoomCreatePrevent: + case AppInterface.IPostLivechatRoomStarted: + case AppInterface.IPostLivechatRoomClosed: + case AppInterface.IPostLivechatAgentAssigned: + case AppInterface.IPostLivechatAgentUnassigned: + case AppInterface.IPostLivechatRoomTransferred: + case AppInterface.IPostLivechatGuestSaved: + case AppInterface.IPostLivechatRoomSaved: + case AppInterface.IPostLivechatDepartmentRemoved: + case AppInterface.IPostLivechatDepartmentDisabled: + return 'livechatEvent'; + case AppInterface.IPostUserCreated: + case AppInterface.IPostUserUpdated: + case AppInterface.IPostUserDeleted: + case AppInterface.IPostUserLoggedIn: + case AppInterface.IPostUserLoggedOut: + case AppInterface.IPostUserStatusChanged: + return 'userEvent'; + default: + return 'defaultEvent'; + } + })(); + + // Using type assertion here because TypeScript doesn't understand that method is a valid method name + return this[method](event as keyof IListenerExecutor, Array.isArray(payload) ? payload : [payload]); + } + + async defaultEvent(inte: keyof IListenerExecutor, payload: unknown): Promise { + return this.orch + .getManager() + .getListenerManager() + .executeListener(inte, payload as any); // We're delegating the payload validation to the method being called + } + + async messageEvent(inte: keyof IListenerExecutor, data: unknown): Promise { + const [message, ...payload] = data as [IMessage, ...unknown[]]; + + const msg = await this.orch.getConverters().get('messages').convertMessage(message); + + const params = ((): IAppsMessage | { message: IAppsMessage; user: IAppsUser; [key: string]: any } => { + switch (inte) { + case AppInterface.IPostMessageDeleted: + const [userDeleted] = payload as [IUser]; + return { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userDeleted), + }; + case AppInterface.IPostMessageReacted: + const [userReacted, reaction, isReacted] = payload as [IUser, string, boolean]; + return { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userReacted), + reaction, + isReacted, + }; + case AppInterface.IPostMessageFollowed: + const [userFollowed, isUnfollow] = payload as [IUser, boolean]; + return { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userFollowed), + isUnfollow, + }; + case AppInterface.IPostMessagePinned: + const [userPinned, isUnpinned] = payload as [IUser, boolean]; + return { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userPinned), + isUnpinned, + }; + case AppInterface.IPostMessageStarred: + const [userStarred, isStarred] = payload as [IUser, boolean]; + return { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userStarred), + isStarred, + }; + case AppInterface.IPostMessageReported: + const [userReported, reason] = payload as [IUser, string]; + return { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userReported), + reason, + }; + default: + return msg; + } + })(); + + const result: unknown = await this.orch.getManager().getListenerManager().executeListener(inte, params); + + if (typeof result === 'boolean') { + return result; + } + + return this.orch + .getConverters() + .get('messages') + .convertAppMessage(result as IAppsMessage); + } + + async roomEvent(inte: keyof IListenerExecutor, data: unknown): Promise { + const [room, ...payload] = data as [IRoom, ...unknown[]]; + + const rm = await this.orch.getConverters().get('rooms').convertRoom(room); + + const params = ((): IAppsRoom | IAppsLivechatRoom | { room: IAppsRoom | IAppsLivechatRoom; [key: string]: any } => { + switch (inte) { + case AppInterface.IPreRoomUserJoined: + case AppInterface.IPostRoomUserJoined: + const [joiningUser, invitingUser] = payload as [IUser, IUser]; + return { + room: rm, + joiningUser: this.orch.getConverters().get('users').convertToApp(joiningUser), + invitingUser: this.orch.getConverters().get('users').convertToApp(invitingUser), + }; + case AppInterface.IPreRoomUserLeave: + case AppInterface.IPostRoomUserLeave: + const [leavingUser, removedBy] = payload as [IUser, IUser]; + return { + room: rm, + leavingUser: this.orch.getConverters().get('users').convertToApp(leavingUser), + removedBy: this.orch.getConverters().get('users').convertToApp(removedBy), + }; + default: + return rm; + } + })(); + + const result: unknown = await this.orch + .getManager() + .getListenerManager() + .executeListener(inte, params as any); // We're delegating the payload validation to the method being called + + if (typeof result === 'boolean') { + return result; + } + + return this.orch + .getConverters() + .get('rooms') + .convertAppRoom(result as IAppsRoom); + } + + async livechatEvent(inte: keyof IListenerExecutor, data: unknown): Promise { + switch (inte) { + case AppInterface.IPostLivechatAgentAssigned: + case AppInterface.IPostLivechatAgentUnassigned: + const agentData = data as LivechatAgentData; + return this.orch + .getManager() + .getListenerManager() + .executeListener(inte, { + room: (await this.orch.getConverters().get('rooms').convertRoom(agentData.room)) as IAppsLivechatRoom, + agent: this.orch.getConverters().get('users').convertToApp(agentData.user), + }); + + case AppInterface.IPostLivechatRoomTransferred: { + const transferData = data as LivechatTransferData; + const converter = transferData.type === LivechatTransferEventType.AGENT ? 'users' : 'departments'; + + const room = await this.orch.getConverters().get('rooms').convertById(transferData.room); + const from = await this.orch.getConverters().get(converter).convertById(transferData.from); + const to = await this.orch.getConverters().get(converter).convertById(transferData.to); + + if (!room) { + throw new Error(`Room with id ${transferData.room} not found`); + } + + if (!from) { + throw new Error(`Transfer from entity with id ${transferData.from} not found`); + } + + if (!to) { + throw new Error(`Transfer to entity with id ${transferData.to} not found`); + } + + return this.orch.getManager().getListenerManager().executeListener(inte, { + room, + from, + to, + type: transferData.type, + }); + } + + case AppInterface.IPostLivechatGuestSaved: { + const visitor = await this.orch + .getConverters() + .get('visitors') + .convertById(data as string); + + if (!visitor) { + throw new Error(`Visitor with id ${data as string} not found`); + } + + return this.orch.getManager().getListenerManager().executeListener(inte, visitor); + } + + case AppInterface.IPostLivechatRoomSaved: { + const room = await this.orch + .getConverters() + .get('rooms') + .convertById(data as string); + + if (!room) { + throw new Error(`Room with id ${data as string} not found`); + } + + return this.orch + .getManager() + .getListenerManager() + .executeListener(inte, room as IAppsLivechatRoom); + } + + case AppInterface.IPostLivechatDepartmentDisabled: { + const department = await this.orch + .getConverters() + .get('departments') + .convertDepartment(data as ILivechatDepartment); + + if (!department) { + throw new Error(`Department ${data} not found`); + } + + return this.orch.getManager().getListenerManager().executeListener(inte, { department }); + } + + case AppInterface.IPostLivechatDepartmentRemoved: { + const department = await this.orch + .getConverters() + .get('departments') + .convertDepartment(data as ILivechatDepartment); + + if (!department) { + throw new Error(`Department ${data} not found`); + } + + return this.orch.getManager().getListenerManager().executeListener(inte, { department }); + } + + default: + const room = await this.orch + .getConverters() + .get('rooms') + .convertRoom(data as IRoom); + + return this.orch.getManager().getListenerManager().executeListener(inte, room); + } + } + + async userEvent(inte: keyof IListenerExecutor, data: unknown): Promise { + switch (inte) { + case AppInterface.IPostUserLoggedIn: + case AppInterface.IPostUserLoggedOut: { + const loginData = data as { user: IUser }; + const context = this.orch.getConverters().get('users').convertToApp(loginData.user); + return this.orch.getManager().getListenerManager().executeListener(inte, context); + } + case AppInterface.IPostUserStatusChanged: { + const statusData = data as UserStatusChangedData; + const { currentStatus, previousStatus } = statusData; + const context = { + user: this.orch.getConverters().get('users').convertToApp(statusData.user), + currentStatus, + previousStatus, + }; + + return this.orch.getManager().getListenerManager().executeListener(inte, context); + } + case AppInterface.IPostUserCreated: + case AppInterface.IPostUserUpdated: + case AppInterface.IPostUserDeleted: { + const crudData = data as UserCrudData; + const context: IUserContext | IUserUpdateContext = { + user: this.orch.getConverters().get('users').convertToApp(crudData.user), + performedBy: this.orch.getConverters().get('users').convertToApp(crudData.performedBy), + }; + + if (inte === AppInterface.IPostUserUpdated && crudData.previousUser) { + (context as IUserUpdateContext).previousData = this.orch.getConverters().get('users').convertToApp(crudData.previousUser); + } + + return this.orch.getManager().getListenerManager().executeListener(inte, context); + } + } + } +} diff --git a/packages/apps-engine/src/server/managers/AppListenerManager.ts b/packages/apps-engine/src/server/managers/AppListenerManager.ts index 273f969ce1045..e2ce55691b88b 100644 --- a/packages/apps-engine/src/server/managers/AppListenerManager.ts +++ b/packages/apps-engine/src/server/managers/AppListenerManager.ts @@ -32,7 +32,7 @@ import type { ProxiedApp } from '../ProxiedApp'; import { Utilities } from '../misc/Utilities'; import { JSONRPC_METHOD_NOT_FOUND } from '../runtime/deno/AppsEngineDenoRuntime'; -interface IListenerExecutor { +export interface IListenerExecutor { [AppInterface.IPreMessageSentPrevent]: { args: [IMessage]; result: boolean; From 90b150c552fe5da453f165f630e47a98b45a3635 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 29 Dec 2025 15:18:03 -0300 Subject: [PATCH 02/11] refactor: direct listener bridge calls to orch.triggerEvent calls --- apps/meteor/app/api/server/lib/eraseTeam.ts | 4 ++-- .../app/lib/server/functions/deleteMessage.ts | 13 +++++-------- .../app/lib/server/functions/sendMessage.ts | 15 +++++++-------- .../app/lib/server/functions/updateMessage.ts | 8 ++++---- .../app/livechat/server/lib/RoutingManager.ts | 2 +- apps/meteor/app/livechat/server/lib/closeRoom.ts | 4 ++-- .../app/livechat/server/lib/departmentsLib.ts | 2 +- apps/meteor/server/lib/eraseRoom.ts | 4 ++-- apps/meteor/server/services/messages/service.ts | 4 ++-- 9 files changed, 26 insertions(+), 30 deletions(-) diff --git a/apps/meteor/app/api/server/lib/eraseTeam.ts b/apps/meteor/app/api/server/lib/eraseTeam.ts index 65b289ab9982b..5fd47f782539a 100644 --- a/apps/meteor/app/api/server/lib/eraseTeam.ts +++ b/apps/meteor/app/api/server/lib/eraseTeam.ts @@ -75,7 +75,7 @@ export async function eraseRoomLooseValidation(rid: string): Promise { } if (Apps.self?.isLoaded()) { - const prevent = await Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPreRoomDeletePrevent, room); + const prevent = await Apps.self?.triggerEvent(AppEvents.IPreRoomDeletePrevent, room); if (prevent) { return false; } @@ -89,7 +89,7 @@ export async function eraseRoomLooseValidation(rid: string): Promise { } if (Apps.self?.isLoaded()) { - void Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPostRoomDeleted, room); + void Apps.self?.triggerEvent(AppEvents.IPostRoomDeleted, room); } return true; diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts index 1c3fb32ef28bd..09e7e631a8c41 100644 --- a/apps/meteor/app/lib/server/functions/deleteMessage.ts +++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts @@ -33,16 +33,13 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise 0; const keepHistory = settings.get('Message_KeepHistory') || isThread; const showDeletedStatus = settings.get('Message_ShowDeletedStatus') || isThread; - const bridges = Apps.self?.isLoaded() && Apps.getBridges(); const room = await Rooms.findOneById(message.rid, { projection: { lastMessage: 1, prid: 1, mid: 1, federated: 1, federation: 1 } }); if (deletedMsg) { - if (bridges) { - const prevent = await bridges.getListenerBridge().messageEvent(AppEvents.IPreMessageDeletePrevent, deletedMsg); - if (prevent) { - throw new Meteor.Error('error-app-prevented-deleting', 'A Rocket.Chat App prevented the message deleting.'); - } + const prevent = await Apps.self?.triggerEvent(AppEvents.IPreMessageDeletePrevent, deletedMsg); + if (prevent) { + throw new Meteor.Error('error-app-prevented-deleting', 'A Rocket.Chat App prevented the message deleting.'); } if (room) { @@ -103,8 +100,8 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise { diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index ff91d04819846..7ea586f5ce38a 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -291,7 +291,7 @@ export const RoutingManager: Routing = { throw new Error('error-room-not-found'); } - void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room: roomAfterUpdate, user }); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatAgentAssigned, { room: roomAfterUpdate, user }); void afterTakeInquiry({ inquiry: returnedInquiry, room: roomAfterUpdate, agent }); void notifyOnLivechatInquiryChangedById(inquiry._id, 'updated', { diff --git a/apps/meteor/app/livechat/server/lib/closeRoom.ts b/apps/meteor/app/livechat/server/lib/closeRoom.ts index 24770e40af29c..0c96adc42ad79 100644 --- a/apps/meteor/app/livechat/server/lib/closeRoom.ts +++ b/apps/meteor/app/livechat/server/lib/closeRoom.ts @@ -95,8 +95,8 @@ async function afterRoomClosed( * @deprecated the `AppEvents.ILivechatRoomClosedHandler` event will be removed * in the next major version of the Apps-Engine */ - void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); - void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); + void Apps.self?.triggerEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomClosed, newRoom); }); const visitor = isRoomClosedByVisitorParams(params) ? params.visitor : undefined; diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index d6a8acc646315..88ec6f0989541 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -272,7 +272,7 @@ export async function removeDepartment(departmentId: string) { } await callbacks.run('livechat.afterRemoveDepartment', { department, agentsIds: removedAgents.map(({ agentId }) => agentId) }); - void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatDepartmentRemoved, { department }); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatDepartmentRemoved, { department }); return ret; } diff --git a/apps/meteor/server/lib/eraseRoom.ts b/apps/meteor/server/lib/eraseRoom.ts index 4b2d618643f0c..72933a1d34a82 100644 --- a/apps/meteor/server/lib/eraseRoom.ts +++ b/apps/meteor/server/lib/eraseRoom.ts @@ -41,7 +41,7 @@ export async function eraseRoom(roomOrId: string | IRoom, uid: string): Promise< } if (Apps.self?.isLoaded()) { - const prevent = await Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPreRoomDeletePrevent, room); + const prevent = await Apps.self?.triggerEvent(AppEvents.IPreRoomDeletePrevent, room); if (prevent) { throw new Meteor.Error('error-app-prevented-deleting', 'A Rocket.Chat App prevented the room erasing.'); } @@ -57,6 +57,6 @@ export async function eraseRoom(roomOrId: string | IRoom, uid: string): Promise< } if (Apps.self?.isLoaded()) { - void Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPostRoomDeleted, room); + void Apps.self?.triggerEvent(AppEvents.IPostRoomDeleted, room); } } diff --git a/apps/meteor/server/services/messages/service.ts b/apps/meteor/server/services/messages/service.ts index 669441a63c186..df64348d00a99 100644 --- a/apps/meteor/server/services/messages/service.ts +++ b/apps/meteor/server/services/messages/service.ts @@ -1,4 +1,4 @@ -import { Apps } from '@rocket.chat/apps'; +import { AppEvents, Apps } from '@rocket.chat/apps'; import type { IMessageService } from '@rocket.chat/core-services'; import { Authorization, ServiceClassInternal } from '@rocket.chat/core-services'; import { type IMessage, type MessageTypesValues, type IUser, type IRoom, isEditedMessage, type AtLeast } from '@rocket.chat/core-typings'; @@ -202,7 +202,7 @@ export class MessageService extends ServiceClassInternal implements IMessageServ } if (Apps.self?.isLoaded()) { - void Apps.getBridges()?.getListenerBridge().messageEvent('IPostSystemMessageSent', createdMessage); + void Apps.self?.triggerEvent(AppEvents.IPostSystemMessageSent, createdMessage); } void notifyOnMessageChange({ id: createdMessage._id, data: createdMessage }); From a015424d05b760bdb615640be5b00a36555064d2 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 29 Dec 2025 15:57:09 -0300 Subject: [PATCH 03/11] fix: unit test --- .../app/api/server/lib/eraseTeam.spec.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/meteor/app/api/server/lib/eraseTeam.spec.ts b/apps/meteor/app/api/server/lib/eraseTeam.spec.ts index 041f5b90e2b2c..8e5da6b4b59c8 100644 --- a/apps/meteor/app/api/server/lib/eraseTeam.spec.ts +++ b/apps/meteor/app/api/server/lib/eraseTeam.spec.ts @@ -40,12 +40,10 @@ describe('eraseTeam (TypeScript) module', () => { IPostRoomDeleted: 'IPostRoomDeleted', }, Apps: { - self: { isLoaded: () => false }, - getBridges: () => ({ - getListenerBridge: () => ({ - roomEvent: sandbox.stub().resolves(false), - }), - }), + self: { + isLoaded: () => false, + triggerEvent: sandbox.stub().resolves(false), + }, }, }, '@rocket.chat/models': { @@ -194,8 +192,10 @@ describe('eraseTeam (TypeScript) module', () => { const AppsStub = { AppEvents: stubs['@rocket.chat/apps'].AppEvents, Apps: { - self: { isLoaded: () => true }, - getBridges: () => ({ getListenerBridge: () => ({ roomEvent: listenerStub }) }), + self: { + isLoaded: () => true, + triggerEvent: listenerStub, + }, }, }; @@ -244,8 +244,10 @@ describe('eraseTeam (TypeScript) module', () => { const AppsStub = { AppEvents: stubs['@rocket.chat/apps'].AppEvents, Apps: { - self: { isLoaded: () => true }, - getBridges: () => ({ getListenerBridge: () => ({ roomEvent: roomEventStub }) }), + self: { + isLoaded: () => true, + triggerEvent: roomEventStub, + }, }, }; From c2e82a7d5ad924078af58f7294d5552b35b3641e Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 29 Dec 2025 17:06:20 -0300 Subject: [PATCH 04/11] refactor: triggerEvent on departments --- apps/meteor/app/livechat/server/lib/departmentsLib.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index 88ec6f0989541..9011704905e9e 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -139,10 +139,7 @@ export async function saveDepartment( // Disable event if (department?.enabled && !departmentDB?.enabled) { await callbacks.run('livechat.afterDepartmentDisabled', departmentDB); - void Apps.self - ?.getBridges() - ?.getListenerBridge() - .livechatEvent(AppEvents.IPostLivechatDepartmentDisabled, { department: departmentDB }); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatDepartmentDisabled, { department: departmentDB }); } if (departmentUnit) { From 46dfd65f621d0cbfc4fa374aa2c71ed1b743a93f Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 29 Dec 2025 17:06:35 -0300 Subject: [PATCH 05/11] fix: payload handling on livechat events --- apps/meteor/app/apps/server/bridges/listeners.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/apps/server/bridges/listeners.ts b/apps/meteor/app/apps/server/bridges/listeners.ts index b2f972f2c7ca7..d5990d1af6e6e 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.ts +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -211,7 +211,9 @@ export class AppListenerBridge { .convertAppRoom(result as IAppsRoom); } - async livechatEvent(inte: keyof IListenerExecutor, data: unknown): Promise { + async livechatEvent(inte: keyof IListenerExecutor, payload: unknown): Promise { + const [data] = payload as [unknown]; + switch (inte) { case AppInterface.IPostLivechatAgentAssigned: case AppInterface.IPostLivechatAgentUnassigned: From 9af2fe4e8efa39e50fefaed351f9b16787b36f3e Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 29 Dec 2025 22:34:55 -0300 Subject: [PATCH 06/11] fix: over correction over incorrect type definition --- .../app/apps/server/bridges/listeners.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/listeners.ts b/apps/meteor/app/apps/server/bridges/listeners.ts index d5990d1af6e6e..db7469118ccc2 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.ts +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -238,20 +238,19 @@ export class AppListenerBridge { throw new Error(`Room with id ${transferData.room} not found`); } - if (!from) { - throw new Error(`Transfer from entity with id ${transferData.from} not found`); - } - if (!to) { throw new Error(`Transfer to entity with id ${transferData.to} not found`); } - return this.orch.getManager().getListenerManager().executeListener(inte, { - room, - from, - to, - type: transferData.type, - }); + return this.orch + .getManager() + .getListenerManager() + .executeListener(inte, { + room, + from: from as NonNullable, // type definition in the apps-engine seems to be incorrect + to, + type: transferData.type, + }); } case AppInterface.IPostLivechatGuestSaved: { From 090ec8f5b50cf3e95465de98d1ff0835b21ecc88 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 30 Dec 2025 11:01:47 -0300 Subject: [PATCH 07/11] fix: wrong type and further enhancements --- .../app/apps/server/bridges/listeners.ts | 57 +++++++++++-------- .../ee/server/apps/communication/uikit.ts | 17 +++--- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/listeners.ts b/apps/meteor/app/apps/server/bridges/listeners.ts index db7469118ccc2..5f8ffd59b0b0a 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.ts +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -25,7 +25,7 @@ type UserStatusChangedData = { type UserCrudData = { user: IUser; - performedBy: IUser; + performedBy?: IUser; previousUser?: IUser; }; @@ -92,31 +92,33 @@ export class AppListenerBridge { })(); // Using type assertion here because TypeScript doesn't understand that method is a valid method name - return this[method](event as keyof IListenerExecutor, Array.isArray(payload) ? payload : [payload]); + return this[method](event as keyof IListenerExecutor, payload); } - async defaultEvent(inte: keyof IListenerExecutor, payload: unknown): Promise { + async defaultEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { + const [data] = payload; + return this.orch .getManager() .getListenerManager() - .executeListener(inte, payload as any); // We're delegating the payload validation to the method being called + .executeListener(inte, data as any); // We're delegating the payload validation to the method being called } - async messageEvent(inte: keyof IListenerExecutor, data: unknown): Promise { - const [message, ...payload] = data as [IMessage, ...unknown[]]; + async messageEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { + const [message, ...data] = payload as [IMessage, ...unknown[]]; const msg = await this.orch.getConverters().get('messages').convertMessage(message); const params = ((): IAppsMessage | { message: IAppsMessage; user: IAppsUser; [key: string]: any } => { switch (inte) { case AppInterface.IPostMessageDeleted: - const [userDeleted] = payload as [IUser]; + const [userDeleted] = data as [IUser]; return { message: msg, user: this.orch.getConverters().get('users').convertToApp(userDeleted), }; case AppInterface.IPostMessageReacted: - const [userReacted, reaction, isReacted] = payload as [IUser, string, boolean]; + const [userReacted, reaction, isReacted] = data as [IUser, string, boolean]; return { message: msg, user: this.orch.getConverters().get('users').convertToApp(userReacted), @@ -124,28 +126,28 @@ export class AppListenerBridge { isReacted, }; case AppInterface.IPostMessageFollowed: - const [userFollowed, isUnfollow] = payload as [IUser, boolean]; + const [userFollowed, isUnfollow] = data as [IUser, boolean]; return { message: msg, user: this.orch.getConverters().get('users').convertToApp(userFollowed), isUnfollow, }; case AppInterface.IPostMessagePinned: - const [userPinned, isUnpinned] = payload as [IUser, boolean]; + const [userPinned, isUnpinned] = data as [IUser, boolean]; return { message: msg, user: this.orch.getConverters().get('users').convertToApp(userPinned), isUnpinned, }; case AppInterface.IPostMessageStarred: - const [userStarred, isStarred] = payload as [IUser, boolean]; + const [userStarred, isStarred] = data as [IUser, boolean]; return { message: msg, user: this.orch.getConverters().get('users').convertToApp(userStarred), isStarred, }; case AppInterface.IPostMessageReported: - const [userReported, reason] = payload as [IUser, string]; + const [userReported, reason] = data as [IUser, string]; return { message: msg, user: this.orch.getConverters().get('users').convertToApp(userReported), @@ -168,8 +170,8 @@ export class AppListenerBridge { .convertAppMessage(result as IAppsMessage); } - async roomEvent(inte: keyof IListenerExecutor, data: unknown): Promise { - const [room, ...payload] = data as [IRoom, ...unknown[]]; + async roomEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { + const [room, ...data] = payload as [IRoom, ...unknown[]]; const rm = await this.orch.getConverters().get('rooms').convertRoom(room); @@ -177,7 +179,7 @@ export class AppListenerBridge { switch (inte) { case AppInterface.IPreRoomUserJoined: case AppInterface.IPostRoomUserJoined: - const [joiningUser, invitingUser] = payload as [IUser, IUser]; + const [joiningUser, invitingUser] = data as [IUser, IUser]; return { room: rm, joiningUser: this.orch.getConverters().get('users').convertToApp(joiningUser), @@ -185,7 +187,7 @@ export class AppListenerBridge { }; case AppInterface.IPreRoomUserLeave: case AppInterface.IPostRoomUserLeave: - const [leavingUser, removedBy] = payload as [IUser, IUser]; + const [leavingUser, removedBy] = data as [IUser, IUser]; return { room: rm, leavingUser: this.orch.getConverters().get('users').convertToApp(leavingUser), @@ -211,8 +213,8 @@ export class AppListenerBridge { .convertAppRoom(result as IAppsRoom); } - async livechatEvent(inte: keyof IListenerExecutor, payload: unknown): Promise { - const [data] = payload as [unknown]; + async livechatEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { + const [data] = payload; switch (inte) { case AppInterface.IPostLivechatAgentAssigned: @@ -318,7 +320,9 @@ export class AppListenerBridge { } } - async userEvent(inte: keyof IListenerExecutor, data: unknown): Promise { + async userEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { + const [data] = payload; + switch (inte) { case AppInterface.IPostUserLoggedIn: case AppInterface.IPostUserLoggedOut: { @@ -338,17 +342,22 @@ export class AppListenerBridge { return this.orch.getManager().getListenerManager().executeListener(inte, context); } case AppInterface.IPostUserCreated: - case AppInterface.IPostUserUpdated: case AppInterface.IPostUserDeleted: { const crudData = data as UserCrudData; - const context: IUserContext | IUserUpdateContext = { + const context: IUserContext = { user: this.orch.getConverters().get('users').convertToApp(crudData.user), performedBy: this.orch.getConverters().get('users').convertToApp(crudData.performedBy), }; - if (inte === AppInterface.IPostUserUpdated && crudData.previousUser) { - (context as IUserUpdateContext).previousData = this.orch.getConverters().get('users').convertToApp(crudData.previousUser); - } + return this.orch.getManager().getListenerManager().executeListener(inte, context); + } + case AppInterface.IPostUserUpdated: { + const crudData = data as UserCrudData; + const context: IUserUpdateContext = { + user: this.orch.getConverters().get('users').convertToApp(crudData.user), + performedBy: this.orch.getConverters().get('users').convertToApp(crudData.performedBy), + previousData: this.orch.getConverters().get('users').convertToApp(crudData.previousUser), + }; return this.orch.getManager().getListenerManager().executeListener(inte, context); } diff --git a/apps/meteor/ee/server/apps/communication/uikit.ts b/apps/meteor/ee/server/apps/communication/uikit.ts index 7d490406d007f..65b019a908b0e 100644 --- a/apps/meteor/ee/server/apps/communication/uikit.ts +++ b/apps/meteor/ee/server/apps/communication/uikit.ts @@ -1,3 +1,4 @@ +import { AppEvents, type IAppServerOrchestrator } from '@rocket.chat/apps'; import type { UiKitCoreAppPayload } from '@rocket.chat/core-services'; import { UiKitCoreApp } from '@rocket.chat/core-services'; import type { OperationParams, UrlParams } from '@rocket.chat/rest-typings'; @@ -193,9 +194,9 @@ router.post('/:id', async (req: UiKitUserInteractionRequest, res, next) => { }); export class AppUIKitInteractionApi { - orch: AppServerOrchestrator; + orch: IAppServerOrchestrator; - constructor(orch: AppServerOrchestrator) { + constructor(orch: IAppServerOrchestrator) { this.orch = orch; router.post('/:id', this.routeHandler); @@ -230,7 +231,7 @@ export class AppUIKitInteractionApi { }; try { - const eventInterface = !visitor ? 'IUIKitInteractionHandler' : 'IUIKitLivechatInteractionHandler'; + const eventInterface = !visitor ? AppEvents.IUIKitInteractionHandler : AppEvents.IUIKitLivechatInteractionHandler; const result = await orch.triggerEvent(eventInterface, action); @@ -261,7 +262,7 @@ export class AppUIKitInteractionApi { }; try { - const result = await orch.triggerEvent('IUIKitInteractionHandler', action); + const result = await orch.triggerEvent(AppEvents.IUIKitInteractionHandler, action); res.send(result); } catch (e) { @@ -286,7 +287,7 @@ export class AppUIKitInteractionApi { }; try { - const result = await orch.triggerEvent('IUIKitInteractionHandler', action); + const result = await orch.triggerEvent(AppEvents.IUIKitInteractionHandler, action); res.send(result); } catch (e) { @@ -307,9 +308,9 @@ export class AppUIKitInteractionApi { payload: { context, message: msgText }, } = req.body; - const room = await orch.getConverters()?.get('rooms').convertById(rid); const user = orch.getConverters()?.get('users').convertToApp(req.user); - const message = mid && (await orch.getConverters()?.get('messages').convertById(mid)); + const room = rid ? await orch.getConverters()?.get('rooms').convertById(rid) : undefined; + const message = mid ? await orch.getConverters()?.get('messages').convertById(mid) : undefined; const action = { type, @@ -327,7 +328,7 @@ export class AppUIKitInteractionApi { }; try { - const result = await orch.triggerEvent('IUIKitInteractionHandler', action); + const result = await orch.triggerEvent(AppEvents.IUIKitInteractionHandler, action); res.send(result); } catch (e) { From b6ff5319b69c9f6d71f7379e7c82afa589cfa08c Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 30 Dec 2025 15:50:14 -0300 Subject: [PATCH 08/11] fix: lint / typecheck --- apps/meteor/ee/server/apps/communication/uikit.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/meteor/ee/server/apps/communication/uikit.ts b/apps/meteor/ee/server/apps/communication/uikit.ts index 65b019a908b0e..e5e851ef8f2b1 100644 --- a/apps/meteor/ee/server/apps/communication/uikit.ts +++ b/apps/meteor/ee/server/apps/communication/uikit.ts @@ -12,7 +12,6 @@ import { WebApp } from 'meteor/webapp'; import { authenticationMiddleware } from '../../../../app/api/server/middlewares/authentication'; import { settings } from '../../../../app/settings/server'; -import type { AppServerOrchestrator } from '../orchestrator'; import { Apps } from '../orchestrator'; const apiServer = express(); @@ -213,9 +212,9 @@ export class AppUIKitInteractionApi { const rid = 'rid' in req.body ? req.body.rid : undefined; const { visitor } = req.body; - const room = await orch.getConverters()?.get('rooms').convertById(rid); const user = orch.getConverters()?.get('users').convertToApp(req.user); - const message = mid && (await orch.getConverters()?.get('messages').convertById(mid)); + const message = mid ? await orch.getConverters()?.get('messages').convertById(mid) : undefined; + const room = rid ? await orch.getConverters()?.get('rooms').convertById(rid) : undefined; const action = { type, From 69b79aa5426fdee745d03cce09e53f5cf514a4ee Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 13 Jan 2026 12:34:22 -0300 Subject: [PATCH 09/11] fix: add missing IAppsMessage type and improve logging in AppListenerBridge --- .../app/apps/server/bridges/listeners.ts | 496 +++++++++++------- apps/meteor/ee/server/apps/orchestrator.js | 2 +- 2 files changed, 308 insertions(+), 190 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/listeners.ts b/apps/meteor/app/apps/server/bridges/listeners.ts index 5f8ffd59b0b0a..be7ef2320dd6d 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.ts +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -1,8 +1,8 @@ -import type { IAppServerOrchestrator, IAppsMessage, IAppsRoom, IAppsUser, IAppsLivechatRoom, AppEvents } from '@rocket.chat/apps'; +import type { IAppServerOrchestrator, IAppsRoom, IAppsLivechatRoom, IAppsMessage } from '@rocket.chat/apps'; import { LivechatTransferEventType } from '@rocket.chat/apps-engine/definition/livechat'; +import { isLivechatRoom } from '@rocket.chat/apps-engine/definition/livechat/ILivechatRoom'; import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; import type { IUserContext, IUserUpdateContext } from '@rocket.chat/apps-engine/definition/users'; -import type { IListenerExecutor } from '@rocket.chat/apps-engine/server/managers/AppListenerManager'; import type { IMessage, IRoom, IUser, ILivechatDepartment } from '@rocket.chat/core-typings'; type LivechatTransferData = { @@ -29,139 +29,265 @@ type UserCrudData = { previousUser?: IUser; }; +type HandleMessageEvent = + | { + event: AppInterface.IPostMessageDeleted; + payload: [IMessage, IUser]; + } + | { + event: AppInterface.IPostMessageReacted; + payload: [IMessage, IUser, string, boolean]; + } + | { + event: AppInterface.IPostMessageFollowed; + payload: [IMessage, IUser, boolean]; + } + | { + event: AppInterface.IPostMessagePinned; + payload: [IMessage, IUser, boolean]; + } + | { + event: AppInterface.IPostMessageStarred; + payload: [IMessage, IUser, boolean]; + } + | { + event: AppInterface.IPostMessageReported; + payload: [IMessage, IUser, string]; + } + | { + event: + | AppInterface.IPostSystemMessageSent + | AppInterface.IPreMessageSentPrevent + | AppInterface.IPreMessageSentExtend + | AppInterface.IPreMessageSentModify + | AppInterface.IPostMessageSent + | AppInterface.IPreMessageDeletePrevent + | AppInterface.IPreMessageUpdatedPrevent + | AppInterface.IPreMessageUpdatedExtend + | AppInterface.IPreMessageUpdatedModify + | AppInterface.IPostMessageUpdated; + payload: [IMessage]; + }; + +type HandleRoomEvent = + | { + event: AppInterface.IPreRoomUserJoined | AppInterface.IPostRoomUserJoined; + payload: [IRoom, IUser, IUser]; + } + | { + event: AppInterface.IPreRoomUserLeave | AppInterface.IPostRoomUserLeave; + payload: [IRoom, IUser, IUser]; + } + | { + event: + | AppInterface.IPreRoomCreatePrevent + | AppInterface.IPreRoomCreateExtend + | AppInterface.IPreRoomCreateModify + | AppInterface.IPostRoomCreate + | AppInterface.IPreRoomDeletePrevent + | AppInterface.IPostRoomDeleted + | AppInterface.IPreRoomUserJoined + | AppInterface.IPostRoomUserJoined + | AppInterface.IPreRoomUserLeave + | AppInterface.IPostRoomUserLeave; + payload: [IRoom]; + }; + +type HandleLivechatEvent = + | { + event: AppInterface.IPostLivechatAgentAssigned | AppInterface.IPostLivechatAgentUnassigned; + payload: [LivechatAgentData]; + } + | { + event: AppInterface.IPostLivechatRoomTransferred; + payload: [LivechatTransferData]; + } + | { + event: AppInterface.IPostLivechatGuestSaved; + payload: [string]; + } + | { + event: AppInterface.IPostLivechatRoomSaved; + payload: [string]; + } + | { + event: AppInterface.IPostLivechatDepartmentRemoved; + payload: [ILivechatDepartment]; + } + | { + event: AppInterface.IPostLivechatDepartmentDisabled; + payload: [ILivechatDepartment]; + } + | { + event: + | AppInterface.ILivechatRoomClosedHandler + | AppInterface.IPreLivechatRoomCreatePrevent + | AppInterface.IPostLivechatRoomStarted + | AppInterface.IPostLivechatRoomClosed; + payload: [IRoom]; + }; + +type HandleUserEvent = + | { + event: AppInterface.IPostUserLoggedIn | AppInterface.IPostUserLoggedOut; + payload: [IUser]; + } + | { + event: AppInterface.IPostUserStatusChanged; + payload: [UserStatusChangedData]; + } + | { + event: AppInterface.IPostUserDeleted | AppInterface.IPostUserCreated | AppInterface.IPostUserUpdated; + payload: [UserCrudData]; + }; + +type HandleEvent = HandleMessageEvent | HandleRoomEvent | HandleLivechatEvent | HandleUserEvent; + export class AppListenerBridge { constructor(private readonly orch: IAppServerOrchestrator) {} - async handleEvent(event: AppEvents, ...payload: unknown[]): Promise { - // eslint-disable-next-line complexity - const method = ((): keyof Omit => { - switch (event) { - case AppInterface.IPostSystemMessageSent: - case AppInterface.IPreMessageSentPrevent: - case AppInterface.IPreMessageSentExtend: - case AppInterface.IPreMessageSentModify: - case AppInterface.IPostMessageSent: - case AppInterface.IPreMessageDeletePrevent: - case AppInterface.IPostMessageDeleted: - case AppInterface.IPreMessageUpdatedPrevent: - case AppInterface.IPreMessageUpdatedExtend: - case AppInterface.IPreMessageUpdatedModify: - case AppInterface.IPostMessageUpdated: - case AppInterface.IPostMessageReacted: - case AppInterface.IPostMessageFollowed: - case AppInterface.IPostMessagePinned: - case AppInterface.IPostMessageStarred: - case AppInterface.IPostMessageReported: - return 'messageEvent'; - case AppInterface.IPreRoomCreatePrevent: - case AppInterface.IPreRoomCreateExtend: - case AppInterface.IPreRoomCreateModify: - case AppInterface.IPostRoomCreate: - case AppInterface.IPreRoomDeletePrevent: - case AppInterface.IPostRoomDeleted: - case AppInterface.IPreRoomUserJoined: - case AppInterface.IPostRoomUserJoined: - case AppInterface.IPreRoomUserLeave: - case AppInterface.IPostRoomUserLeave: - return 'roomEvent'; - /** - * @deprecated please prefer the AppInterface.IPostLivechatRoomClosed event - */ - case AppInterface.ILivechatRoomClosedHandler: - case AppInterface.IPreLivechatRoomCreatePrevent: - case AppInterface.IPostLivechatRoomStarted: - case AppInterface.IPostLivechatRoomClosed: - case AppInterface.IPostLivechatAgentAssigned: - case AppInterface.IPostLivechatAgentUnassigned: - case AppInterface.IPostLivechatRoomTransferred: - case AppInterface.IPostLivechatGuestSaved: - case AppInterface.IPostLivechatRoomSaved: - case AppInterface.IPostLivechatDepartmentRemoved: - case AppInterface.IPostLivechatDepartmentDisabled: - return 'livechatEvent'; - case AppInterface.IPostUserCreated: - case AppInterface.IPostUserUpdated: - case AppInterface.IPostUserDeleted: - case AppInterface.IPostUserLoggedIn: - case AppInterface.IPostUserLoggedOut: - case AppInterface.IPostUserStatusChanged: - return 'userEvent'; - default: - return 'defaultEvent'; + // eslint-disable-next-line complexity + async handleEvent(args: HandleEvent): Promise { + console.log('args', args); + switch (args.event) { + case AppInterface.IPostMessageDeleted: + case AppInterface.IPostMessageReacted: + case AppInterface.IPostMessageFollowed: + case AppInterface.IPostMessagePinned: + case AppInterface.IPostMessageStarred: + case AppInterface.IPostMessageReported: + case AppInterface.IPostSystemMessageSent: + case AppInterface.IPreMessageSentPrevent: + case AppInterface.IPreMessageSentExtend: + case AppInterface.IPreMessageSentModify: + case AppInterface.IPostMessageSent: + case AppInterface.IPreMessageDeletePrevent: + case AppInterface.IPreMessageUpdatedPrevent: + case AppInterface.IPreMessageUpdatedExtend: + case AppInterface.IPreMessageUpdatedModify: + case AppInterface.IPostMessageUpdated: { + return this.messageEvent(args); } - })(); - - // Using type assertion here because TypeScript doesn't understand that method is a valid method name - return this[method](event as keyof IListenerExecutor, payload); + case AppInterface.IPreRoomCreatePrevent: + case AppInterface.IPreRoomCreateExtend: + case AppInterface.IPreRoomCreateModify: + case AppInterface.IPostRoomCreate: + case AppInterface.IPreRoomDeletePrevent: + case AppInterface.IPostRoomDeleted: + case AppInterface.IPreRoomUserJoined: + case AppInterface.IPostRoomUserJoined: + case AppInterface.IPreRoomUserLeave: + case AppInterface.IPostRoomUserLeave: + return this.roomEvent(args); + /** + * @deprecated please prefer the AppInterface.IPostLivechatRoomClosed event + */ + case AppInterface.ILivechatRoomClosedHandler: + case AppInterface.IPreLivechatRoomCreatePrevent: + case AppInterface.IPostLivechatRoomStarted: + case AppInterface.IPostLivechatRoomClosed: + case AppInterface.IPostLivechatAgentAssigned: + case AppInterface.IPostLivechatAgentUnassigned: + case AppInterface.IPostLivechatRoomTransferred: + case AppInterface.IPostLivechatGuestSaved: + case AppInterface.IPostLivechatRoomSaved: + case AppInterface.IPostLivechatDepartmentRemoved: + case AppInterface.IPostLivechatDepartmentDisabled: + return this.livechatEvent(args); + case AppInterface.IPostUserCreated: + case AppInterface.IPostUserUpdated: + case AppInterface.IPostUserDeleted: + case AppInterface.IPostUserLoggedIn: + case AppInterface.IPostUserLoggedOut: + case AppInterface.IPostUserStatusChanged: + return this.userEvent(args); + default: + return this.defaultEvent(args); + } } - async defaultEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { - const [data] = payload; - + async defaultEvent(args: HandleEvent): Promise { return this.orch .getManager() .getListenerManager() - .executeListener(inte, data as any); // We're delegating the payload validation to the method being called + .executeListener(args.event, args.payload[0] as any); } - async messageEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { - const [message, ...data] = payload as [IMessage, ...unknown[]]; + async messageEvent(args: HandleMessageEvent): Promise { + const [message] = args.payload; const msg = await this.orch.getConverters().get('messages').convertMessage(message); - const params = ((): IAppsMessage | { message: IAppsMessage; user: IAppsUser; [key: string]: any } => { - switch (inte) { + const result = await (() => { + switch (args.event) { case AppInterface.IPostMessageDeleted: - const [userDeleted] = data as [IUser]; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userDeleted), - }; + const [, userDeleted] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userDeleted), + }); case AppInterface.IPostMessageReacted: - const [userReacted, reaction, isReacted] = data as [IUser, string, boolean]; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userReacted), - reaction, - isReacted, - }; + const [, userReacted, reaction, isReacted] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userReacted), + reaction, + isReacted, + }); case AppInterface.IPostMessageFollowed: - const [userFollowed, isUnfollow] = data as [IUser, boolean]; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userFollowed), - isUnfollow, - }; + const [, userFollowed, isFollowed] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userFollowed), + isFollowed, + }); case AppInterface.IPostMessagePinned: - const [userPinned, isUnpinned] = data as [IUser, boolean]; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userPinned), - isUnpinned, - }; + const [, userPinned, isPinned] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userPinned), + isPinned, + }); case AppInterface.IPostMessageStarred: - const [userStarred, isStarred] = data as [IUser, boolean]; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userStarred), - isStarred, - }; + const [, userStarred, isStarred] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userStarred), + isStarred, + }); case AppInterface.IPostMessageReported: - const [userReported, reason] = data as [IUser, string]; - return { - message: msg, - user: this.orch.getConverters().get('users').convertToApp(userReported), - reason, - }; + const [, userReported, reason] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userReported), + reason, + }); default: - return msg; + return this.orch.getManager().getListenerManager().executeListener(args.event, msg); } })(); - const result: unknown = await this.orch.getManager().getListenerManager().executeListener(inte, params); - - if (typeof result === 'boolean') { - return result; + // TODO: weird that boolean is not returned by executeListener + if (typeof result === 'boolean' || result === undefined) { + return result ?? undefined; } return this.orch @@ -170,66 +296,62 @@ export class AppListenerBridge { .convertAppMessage(result as IAppsMessage); } - async roomEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { - const [room, ...data] = payload as [IRoom, ...unknown[]]; + async roomEvent(args: HandleRoomEvent): Promise { + const [room] = args.payload; const rm = await this.orch.getConverters().get('rooms').convertRoom(room); - const params = ((): IAppsRoom | IAppsLivechatRoom | { room: IAppsRoom | IAppsLivechatRoom; [key: string]: any } => { - switch (inte) { + const result = await (() => { + switch (args.event) { case AppInterface.IPreRoomUserJoined: case AppInterface.IPostRoomUserJoined: - const [joiningUser, invitingUser] = data as [IUser, IUser]; - return { - room: rm, - joiningUser: this.orch.getConverters().get('users').convertToApp(joiningUser), - invitingUser: this.orch.getConverters().get('users').convertToApp(invitingUser), - }; + const [, joiningUser, invitingUser] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + room: rm, + joiningUser: this.orch.getConverters().get('users').convertToApp(joiningUser)!, + inviter: this.orch.getConverters().get('users').convertToApp(invitingUser), + }); case AppInterface.IPreRoomUserLeave: case AppInterface.IPostRoomUserLeave: - const [leavingUser, removedBy] = data as [IUser, IUser]; - return { - room: rm, - leavingUser: this.orch.getConverters().get('users').convertToApp(leavingUser), - removedBy: this.orch.getConverters().get('users').convertToApp(removedBy), - }; + const [, leavingUser, removedBy] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + room: rm, + leavingUser: this.orch.getConverters().get('users').convertToApp(leavingUser)!, + removedBy: this.orch.getConverters().get('users').convertToApp(removedBy), + }); default: - return rm; + return this.orch.getManager().getListenerManager().executeListener(args.event, rm); } })(); - const result: unknown = await this.orch - .getManager() - .getListenerManager() - .executeListener(inte, params as any); // We're delegating the payload validation to the method being called - - if (typeof result === 'boolean') { - return result; + if (typeof result === 'boolean' || result === undefined) { + return result ?? undefined; } - return this.orch - .getConverters() - .get('rooms') - .convertAppRoom(result as IAppsRoom); + return this.orch.getConverters().get('rooms').convertAppRoom(result); } - async livechatEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { - const [data] = payload; - - switch (inte) { + async livechatEvent(args: HandleLivechatEvent): Promise { + switch (args.event) { case AppInterface.IPostLivechatAgentAssigned: case AppInterface.IPostLivechatAgentUnassigned: - const agentData = data as LivechatAgentData; + const [agentData] = args.payload; return this.orch .getManager() .getListenerManager() - .executeListener(inte, { + .executeListener(args.event, { room: (await this.orch.getConverters().get('rooms').convertRoom(agentData.room)) as IAppsLivechatRoom, agent: this.orch.getConverters().get('users').convertToApp(agentData.user), }); case AppInterface.IPostLivechatRoomTransferred: { - const transferData = data as LivechatTransferData; + const [transferData] = args.payload; const converter = transferData.type === LivechatTransferEventType.AGENT ? 'users' : 'departments'; const room = await this.orch.getConverters().get('rooms').convertById(transferData.room); @@ -247,7 +369,7 @@ export class AppListenerBridge { return this.orch .getManager() .getListenerManager() - .executeListener(inte, { + .executeListener(args.event, { room, from: from as NonNullable, // type definition in the apps-engine seems to be incorrect to, @@ -256,82 +378,78 @@ export class AppListenerBridge { } case AppInterface.IPostLivechatGuestSaved: { - const visitor = await this.orch - .getConverters() - .get('visitors') - .convertById(data as string); + const [visitorId] = args.payload; + const visitor = await this.orch.getConverters().get('visitors').convertById(visitorId); if (!visitor) { - throw new Error(`Visitor with id ${data as string} not found`); + throw new Error(`Visitor with id ${visitorId} not found`); } - return this.orch.getManager().getListenerManager().executeListener(inte, visitor); + return this.orch.getManager().getListenerManager().executeListener(args.event, visitor); } case AppInterface.IPostLivechatRoomSaved: { - const room = await this.orch - .getConverters() - .get('rooms') - .convertById(data as string); + const [roomId] = args.payload; + const room = await this.orch.getConverters().get('rooms').convertById(roomId); if (!room) { - throw new Error(`Room with id ${data as string} not found`); + throw new Error(`Room with id ${roomId} not found`); } return this.orch .getManager() .getListenerManager() - .executeListener(inte, room as IAppsLivechatRoom); + .executeListener(args.event, room as IAppsLivechatRoom); } case AppInterface.IPostLivechatDepartmentDisabled: { - const department = await this.orch - .getConverters() - .get('departments') - .convertDepartment(data as ILivechatDepartment); + const [departmentData] = args.payload; + const department = await this.orch.getConverters().get('departments').convertDepartment(departmentData); if (!department) { - throw new Error(`Department ${data} not found`); + throw new Error(`Department ${departmentData} not found`); } - return this.orch.getManager().getListenerManager().executeListener(inte, { department }); + return this.orch.getManager().getListenerManager().executeListener(args.event, { department }); } case AppInterface.IPostLivechatDepartmentRemoved: { - const department = await this.orch - .getConverters() - .get('departments') - .convertDepartment(data as ILivechatDepartment); + const [departmentData] = args.payload; + const department = await this.orch.getConverters().get('departments').convertDepartment(departmentData); if (!department) { - throw new Error(`Department ${data} not found`); + throw new Error(`Department ${departmentData} not found`); } - return this.orch.getManager().getListenerManager().executeListener(inte, { department }); + return this.orch.getManager().getListenerManager().executeListener(args.event, { department }); } default: - const room = await this.orch - .getConverters() - .get('rooms') - .convertRoom(data as IRoom); + const [roomData] = args.payload; + const room = await this.orch.getConverters().get('rooms').convertRoom(roomData); + + if (!room) { + throw new Error(`Room ${roomData} not found`); + } + + if (!isLivechatRoom(room)) { + throw new Error(`Room ${roomData} is not a livechat room`); + } - return this.orch.getManager().getListenerManager().executeListener(inte, room); + return this.orch.getManager().getListenerManager().executeListener(args.event, room); } } - async userEvent(inte: keyof IListenerExecutor, payload: unknown[]): Promise { - const [data] = payload; - - switch (inte) { + async userEvent(args: HandleUserEvent): Promise { + switch (args.event) { case AppInterface.IPostUserLoggedIn: case AppInterface.IPostUserLoggedOut: { - const loginData = data as { user: IUser }; - const context = this.orch.getConverters().get('users').convertToApp(loginData.user); - return this.orch.getManager().getListenerManager().executeListener(inte, context); + const [loggedInUser] = args.payload; + const context = this.orch.getConverters().get('users').convertToApp(loggedInUser); + return this.orch.getManager().getListenerManager().executeListener(args.event, context); } case AppInterface.IPostUserStatusChanged: { - const statusData = data as UserStatusChangedData; + const [statusData] = args.payload; const { currentStatus, previousStatus } = statusData; const context = { user: this.orch.getConverters().get('users').convertToApp(statusData.user), @@ -339,27 +457,27 @@ export class AppListenerBridge { previousStatus, }; - return this.orch.getManager().getListenerManager().executeListener(inte, context); + return this.orch.getManager().getListenerManager().executeListener(args.event, context); } case AppInterface.IPostUserCreated: case AppInterface.IPostUserDeleted: { - const crudData = data as UserCrudData; + const [crudData] = args.payload; const context: IUserContext = { user: this.orch.getConverters().get('users').convertToApp(crudData.user), performedBy: this.orch.getConverters().get('users').convertToApp(crudData.performedBy), }; - return this.orch.getManager().getListenerManager().executeListener(inte, context); + return this.orch.getManager().getListenerManager().executeListener(args.event, context); } case AppInterface.IPostUserUpdated: { - const crudData = data as UserCrudData; + const [crudData] = args.payload; const context: IUserUpdateContext = { user: this.orch.getConverters().get('users').convertToApp(crudData.user), performedBy: this.orch.getConverters().get('users').convertToApp(crudData.performedBy), previousData: this.orch.getConverters().get('users').convertToApp(crudData.previousUser), }; - return this.orch.getManager().getListenerManager().executeListener(inte, context); + return this.orch.getManager().getListenerManager().executeListener(args.event, context); } } } diff --git a/apps/meteor/ee/server/apps/orchestrator.js b/apps/meteor/ee/server/apps/orchestrator.js index 743b27639f050..c8bcb1cfed5b9 100644 --- a/apps/meteor/ee/server/apps/orchestrator.js +++ b/apps/meteor/ee/server/apps/orchestrator.js @@ -344,7 +344,7 @@ export class AppServerOrchestrator { return this.getBridges() .getListenerBridge() - .handleEvent(event, ...payload) + .handleEvent({ event, payload }) .catch((error) => { if (error instanceof EssentialAppDisabledException) { throw new Meteor.Error('error-essential-app-disabled'); From 5272784f1540d581ad193f7a5186deed30bde387 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Wed, 14 Jan 2026 11:10:12 -0300 Subject: [PATCH 10/11] refactor: add types for default event method --- .../app/apps/server/bridges/listeners.ts | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/listeners.ts b/apps/meteor/app/apps/server/bridges/listeners.ts index be7ef2320dd6d..53c890174a25d 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.ts +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -1,7 +1,12 @@ import type { IAppServerOrchestrator, IAppsRoom, IAppsLivechatRoom, IAppsMessage } from '@rocket.chat/apps'; +import type { IPreEmailSentContext } from '@rocket.chat/apps-engine/definition/email'; +import type { IExternalComponent } from '@rocket.chat/apps-engine/definition/externalComponent'; import { LivechatTransferEventType } from '@rocket.chat/apps-engine/definition/livechat'; import { isLivechatRoom } from '@rocket.chat/apps-engine/definition/livechat/ILivechatRoom'; import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; +import type { UIKitIncomingInteraction } from '@rocket.chat/apps-engine/definition/uikit'; +import type { IUIKitLivechatIncomingInteraction } from '@rocket.chat/apps-engine/definition/uikit/livechat'; +import type { IFileUploadContext } from '@rocket.chat/apps-engine/definition/uploads'; import type { IUserContext, IUserUpdateContext } from '@rocket.chat/apps-engine/definition/users'; import type { IMessage, IRoom, IUser, ILivechatDepartment } from '@rocket.chat/core-typings'; @@ -29,6 +34,8 @@ type UserCrudData = { previousUser?: IUser; }; +// IPostMessageSentToBot is an internally triggered event, based on IPostMessageSent +// so we don't add it here type HandleMessageEvent = | { event: AppInterface.IPostMessageDeleted; @@ -141,14 +148,35 @@ type HandleUserEvent = payload: [UserCrudData]; }; -type HandleEvent = HandleMessageEvent | HandleRoomEvent | HandleLivechatEvent | HandleUserEvent; +type HandleDefaultEvent = + | { + event: AppInterface.IPostExternalComponentOpened | AppInterface.IPostExternalComponentClosed; + payload: [IExternalComponent]; + } + | { + event: AppInterface.IUIKitInteractionHandler; + payload: [UIKitIncomingInteraction]; + } + | { + event: AppInterface.IUIKitLivechatInteractionHandler; + payload: [IUIKitLivechatIncomingInteraction]; + } + | { + event: AppInterface.IPreFileUpload; + payload: [IFileUploadContext]; + } + | { + event: AppInterface.IPreEmailSent; + payload: [IPreEmailSentContext]; + }; + +type HandleEvent = HandleMessageEvent | HandleRoomEvent | HandleLivechatEvent | HandleUserEvent | HandleDefaultEvent; export class AppListenerBridge { constructor(private readonly orch: IAppServerOrchestrator) {} // eslint-disable-next-line complexity async handleEvent(args: HandleEvent): Promise { - console.log('args', args); switch (args.event) { case AppInterface.IPostMessageDeleted: case AppInterface.IPostMessageReacted: @@ -165,9 +193,8 @@ export class AppListenerBridge { case AppInterface.IPreMessageUpdatedPrevent: case AppInterface.IPreMessageUpdatedExtend: case AppInterface.IPreMessageUpdatedModify: - case AppInterface.IPostMessageUpdated: { + case AppInterface.IPostMessageUpdated: return this.messageEvent(args); - } case AppInterface.IPreRoomCreatePrevent: case AppInterface.IPreRoomCreateExtend: case AppInterface.IPreRoomCreateModify: @@ -206,11 +233,8 @@ export class AppListenerBridge { } } - async defaultEvent(args: HandleEvent): Promise { - return this.orch - .getManager() - .getListenerManager() - .executeListener(args.event, args.payload[0] as any); + async defaultEvent(args: HandleDefaultEvent): Promise { + return this.orch.getManager().getListenerManager().executeListener(args.event, args.payload[0]); } async messageEvent(args: HandleMessageEvent): Promise { From aa67c536fdad3be77417da06ba9fffd5022a4523 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Wed, 14 Jan 2026 11:16:40 -0300 Subject: [PATCH 11/11] refactor: fix [object Object] serialization on error strings --- apps/meteor/app/apps/server/bridges/listeners.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/listeners.ts b/apps/meteor/app/apps/server/bridges/listeners.ts index 53c890174a25d..6abf276d9f926 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.ts +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -431,7 +431,7 @@ export class AppListenerBridge { const department = await this.orch.getConverters().get('departments').convertDepartment(departmentData); if (!department) { - throw new Error(`Department ${departmentData} not found`); + throw new Error(`Department ${departmentData._id} not found`); } return this.orch.getManager().getListenerManager().executeListener(args.event, { department }); @@ -442,7 +442,7 @@ export class AppListenerBridge { const department = await this.orch.getConverters().get('departments').convertDepartment(departmentData); if (!department) { - throw new Error(`Department ${departmentData} not found`); + throw new Error(`Department ${departmentData._id} not found`); } return this.orch.getManager().getListenerManager().executeListener(args.event, { department }); @@ -453,11 +453,11 @@ export class AppListenerBridge { const room = await this.orch.getConverters().get('rooms').convertRoom(roomData); if (!room) { - throw new Error(`Room ${roomData} not found`); + throw new Error(`Room ${roomData._id} not found`); } if (!isLivechatRoom(room)) { - throw new Error(`Room ${roomData} is not a livechat room`); + throw new Error(`Room ${roomData._id} is not a livechat room`); } return this.orch.getManager().getListenerManager().executeListener(args.event, room);