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, + }, }, }; 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/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..6abf276d9f926 --- /dev/null +++ b/apps/meteor/app/apps/server/bridges/listeners.ts @@ -0,0 +1,508 @@ +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'; + +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; +}; + +// IPostMessageSentToBot is an internally triggered event, based on IPostMessageSent +// so we don't add it here +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 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 { + 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); + 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(args: HandleDefaultEvent): Promise { + return this.orch.getManager().getListenerManager().executeListener(args.event, args.payload[0]); + } + + async messageEvent(args: HandleMessageEvent): Promise { + const [message] = args.payload; + + const msg = await this.orch.getConverters().get('messages').convertMessage(message); + + const result = await (() => { + switch (args.event) { + case AppInterface.IPostMessageDeleted: + 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] = 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, 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, 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] = 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] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + message: msg, + user: this.orch.getConverters().get('users').convertToApp(userReported), + reason, + }); + default: + return this.orch.getManager().getListenerManager().executeListener(args.event, msg); + } + })(); + + // TODO: weird that boolean is not returned by executeListener + if (typeof result === 'boolean' || result === undefined) { + return result ?? undefined; + } + + return this.orch + .getConverters() + .get('messages') + .convertAppMessage(result as IAppsMessage); + } + + async roomEvent(args: HandleRoomEvent): Promise { + const [room] = args.payload; + + const rm = await this.orch.getConverters().get('rooms').convertRoom(room); + + const result = await (() => { + switch (args.event) { + case AppInterface.IPreRoomUserJoined: + case AppInterface.IPostRoomUserJoined: + 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] = 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 this.orch.getManager().getListenerManager().executeListener(args.event, rm); + } + })(); + + if (typeof result === 'boolean' || result === undefined) { + return result ?? undefined; + } + + return this.orch.getConverters().get('rooms').convertAppRoom(result); + } + + async livechatEvent(args: HandleLivechatEvent): Promise { + switch (args.event) { + case AppInterface.IPostLivechatAgentAssigned: + case AppInterface.IPostLivechatAgentUnassigned: + const [agentData] = args.payload; + return this.orch + .getManager() + .getListenerManager() + .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] = args.payload; + 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 (!to) { + throw new Error(`Transfer to entity with id ${transferData.to} not found`); + } + + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, { + room, + from: from as NonNullable, // type definition in the apps-engine seems to be incorrect + to, + type: transferData.type, + }); + } + + case AppInterface.IPostLivechatGuestSaved: { + const [visitorId] = args.payload; + const visitor = await this.orch.getConverters().get('visitors').convertById(visitorId); + + if (!visitor) { + throw new Error(`Visitor with id ${visitorId} not found`); + } + + return this.orch.getManager().getListenerManager().executeListener(args.event, visitor); + } + + case AppInterface.IPostLivechatRoomSaved: { + const [roomId] = args.payload; + const room = await this.orch.getConverters().get('rooms').convertById(roomId); + + if (!room) { + throw new Error(`Room with id ${roomId} not found`); + } + + return this.orch + .getManager() + .getListenerManager() + .executeListener(args.event, room as IAppsLivechatRoom); + } + + case AppInterface.IPostLivechatDepartmentDisabled: { + const [departmentData] = args.payload; + const department = await this.orch.getConverters().get('departments').convertDepartment(departmentData); + + if (!department) { + throw new Error(`Department ${departmentData._id} not found`); + } + + return this.orch.getManager().getListenerManager().executeListener(args.event, { department }); + } + + case AppInterface.IPostLivechatDepartmentRemoved: { + const [departmentData] = args.payload; + const department = await this.orch.getConverters().get('departments').convertDepartment(departmentData); + + if (!department) { + throw new Error(`Department ${departmentData._id} not found`); + } + + return this.orch.getManager().getListenerManager().executeListener(args.event, { department }); + } + + default: + const [roomData] = args.payload; + const room = await this.orch.getConverters().get('rooms').convertRoom(roomData); + + if (!room) { + throw new Error(`Room ${roomData._id} not found`); + } + + if (!isLivechatRoom(room)) { + throw new Error(`Room ${roomData._id} is not a livechat room`); + } + + return this.orch.getManager().getListenerManager().executeListener(args.event, room); + } + } + + async userEvent(args: HandleUserEvent): Promise { + switch (args.event) { + case AppInterface.IPostUserLoggedIn: + case AppInterface.IPostUserLoggedOut: { + 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] = args.payload; + const { currentStatus, previousStatus } = statusData; + const context = { + user: this.orch.getConverters().get('users').convertToApp(statusData.user), + currentStatus, + previousStatus, + }; + + return this.orch.getManager().getListenerManager().executeListener(args.event, context); + } + case AppInterface.IPostUserCreated: + case AppInterface.IPostUserDeleted: { + 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(args.event, context); + } + case AppInterface.IPostUserUpdated: { + 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(args.event, context); + } + } + } +} diff --git a/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts b/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts index aa47be4434186..8fa9eaba04534 100644 --- a/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts +++ b/apps/meteor/app/importer/server/classes/converters/MessageConverter.ts @@ -42,7 +42,7 @@ export class MessageConverter extends RecordConverter { try { await Rooms.resetLastMessageById(rid, null); } catch (e) { - this._logger.warn(`Failed to update last message of room ${rid}`); + this._logger.warn({ msg: 'Failed to update last message of room', roomId: rid }); this._logger.error(e); } } @@ -55,7 +55,7 @@ export class MessageConverter extends RecordConverter { const creator = await this._cache.findImportedUser(data.u._id); if (!creator) { - this._logger.warn(`Imported user not found: ${data.u._id}`); + this._logger.warn({ msg: 'Imported user not found', userId: data.u._id }); throw new Error('importer-message-unknown-user'); } const rid = await this._cache.findImportedRoomId(data.rid); @@ -71,7 +71,7 @@ export class MessageConverter extends RecordConverter { try { await insertMessage(creator, msgObj as unknown as IDBMessage, rid, true); } catch (e) { - this._logger.warn(`Failed to import message with timestamp ${String(msgObj.ts)} to room ${rid}`); + this._logger.warn({ msg: 'Failed to import message', timestamp: msgObj.ts, roomId: rid }); this._logger.error(e); } } @@ -126,7 +126,7 @@ export class MessageConverter extends RecordConverter { const { name, _id } = (await this.getMentionedChannelData(importId)) || {}; if (!_id || !name) { - this._logger.warn(`Mentioned room not found: ${importId}`); + this._logger.warn({ msg: 'Mentioned room not found', importId }); continue; } @@ -162,7 +162,7 @@ export class MessageConverter extends RecordConverter { const data = await this._cache.findImportedUser(importId); if (!data) { - this._logger.warn(`Mentioned user not found: ${importId}`); + this._logger.warn({ msg: 'Mentioned user not found', importId }); continue; } diff --git a/apps/meteor/app/integrations/server/lib/triggerHandler.ts b/apps/meteor/app/integrations/server/lib/triggerHandler.ts index e3808da9ef8e9..0a29396ec2c32 100644 --- a/apps/meteor/app/integrations/server/lib/triggerHandler.ts +++ b/apps/meteor/app/integrations/server/lib/triggerHandler.ts @@ -69,7 +69,7 @@ class RocketChatIntegrationHandler { } addIntegration(record: IOutgoingIntegration): void { - outgoingLogger.debug(`Adding the integration ${record.name} of the event ${record.event}!`); + outgoingLogger.debug({ msg: 'Adding integration', integrationName: record.name, event: record.event }); let channels = []; if (record.event && !outgoingEvents[record.event].use.channel) { outgoingLogger.debug('The integration doesnt rely on channels.'); @@ -79,7 +79,7 @@ class RocketChatIntegrationHandler { outgoingLogger.debug('The integration had an empty channel property, so it is going on all the public channels.'); channels = ['all_public_channels']; } else { - outgoingLogger.debug('The integration is going on these channels:', record.channel); + outgoingLogger.debug({ msg: 'The integration is going on these channels', channels: record.channel }); channels = ([] as string[]).concat(record.channel); } @@ -140,7 +140,7 @@ class RocketChatIntegrationHandler { } if (!user) { - outgoingLogger.error(`The user "${trigger.username}" doesn't exist, so we can't send the message.`); + outgoingLogger.error({ msg: 'Integration user not found', username: trigger.username }); return; } @@ -164,7 +164,7 @@ class RocketChatIntegrationHandler { return; } - outgoingLogger.debug(`Found a room for ${trigger.name} which is: ${tmpRoom.name} with a type of ${tmpRoom.t}`); + outgoingLogger.debug({ msg: 'Found room for integration', integrationName: trigger.name, roomName: tmpRoom.name, roomType: tmpRoom.t }); message.bot = { i: trigger._id }; @@ -233,13 +233,14 @@ class RocketChatIntegrationHandler { } break; default: - outgoingLogger.warn(`An Unhandled Trigger Event was called: ${argObject.event}`); + outgoingLogger.warn({ msg: 'Unhandled trigger event', event: argObject.event }); argObject.event = undefined; break; } outgoingLogger.debug({ - msg: `Got the event arguments for the event: ${argObject.event}`, + msg: 'Got the event arguments for the event', + event: argObject.event, messageId: argObject.message?._id, roomId: argObject.room?._id, userId: argObject.user?._id || argObject.owner?._id, @@ -264,7 +265,7 @@ class RocketChatIntegrationHandler { const ownerWithoutServicesField = owner?.services ? omitServicesField(owner) : owner; if (!room || !message) { - outgoingLogger.warn(`The integration ${event} was called but the room or message was not defined.`); + outgoingLogger.warn({ msg: 'Integration called without room or message', event }); return; } @@ -317,7 +318,7 @@ class RocketChatIntegrationHandler { break; case 'roomCreated': if (!owner) { - outgoingLogger.warn(`The integration ${event} was called but the owner was not defined.`); + outgoingLogger.warn({ msg: 'Integration called without owner data', event }); return; } data.channel_id = room._id; @@ -332,7 +333,7 @@ class RocketChatIntegrationHandler { case 'roomJoined': case 'roomLeft': if (!user) { - outgoingLogger.warn(`The integration ${event} was called but the owner was not defined.`); + outgoingLogger.warn({ msg: 'Integration called without owner data', event }); return; } data.timestamp = new Date(); @@ -349,7 +350,7 @@ class RocketChatIntegrationHandler { break; case 'userCreated': if (!user) { - outgoingLogger.warn(`The integration ${event} was called but the owner was not defined.`); + outgoingLogger.warn({ msg: 'Integration called without owner data', event }); return; } data.timestamp = user.createdAt; @@ -450,7 +451,7 @@ class RocketChatIntegrationHandler { return; } - outgoingLogger.debug(`Starting search for triggers for the room: ${room ? room._id : '__any'}`); + outgoingLogger.debug({ msg: 'Starting search for triggers for room', roomId: room ? room._id : '__any' }); const triggersToExecute = this.getTriggersToExecute(room, message); @@ -461,12 +462,15 @@ class RocketChatIntegrationHandler { } } - outgoingLogger.debug(`Found ${triggersToExecute.length} to iterate over and see if the match the event.`); + outgoingLogger.debug({ msg: 'Found triggers to iterate over', triggerCount: triggersToExecute.length, event }); for await (const triggerToExecute of triggersToExecute) { - outgoingLogger.debug( - `Is "${triggerToExecute.name}" enabled, ${triggerToExecute.enabled}, and what is the event? ${triggerToExecute.event}`, - ); + outgoingLogger.debug({ + msg: 'Checking trigger execution eligibility', + triggerName: triggerToExecute.name, + isEnabled: triggerToExecute.enabled, + triggerEvent: triggerToExecute.event, + }); if (triggerToExecute.enabled === true && triggerToExecute.event === event) { await this.executeTrigger(triggerToExecute, argObject); } @@ -495,11 +499,11 @@ class RocketChatIntegrationHandler { async executeTriggerUrl(url: string, trigger: IOutgoingIntegration, { event, message, room, owner, user }: ArgumentsObject, tries = 0) { if (!this.isTriggerEnabled(trigger)) { - outgoingLogger.warn(`The trigger "${trigger.name}" is no longer enabled, stopping execution of it at try: ${tries}`); + outgoingLogger.warn({ msg: 'Trigger is not enabled', triggerName: trigger.name, tries }); return; } - outgoingLogger.debug(`Starting to execute trigger: ${trigger.name} (${trigger._id})`); + outgoingLogger.debug({ msg: 'Starting to execute trigger', triggerName: trigger.name, triggerId: trigger._id }); let word; // Not all triggers/events support triggerWords @@ -517,14 +521,14 @@ class RocketChatIntegrationHandler { // Stop if there are triggerWords but none match if (!word) { - outgoingLogger.debug(`The trigger word which "${trigger.name}" was expecting could not be found, not executing.`); + outgoingLogger.debug({ msg: 'Trigger word not found', triggerName: trigger.name }); return; } } } if (message && message.editedAt && !trigger.runOnEdits) { - outgoingLogger.debug(`The trigger "${trigger.name}"'s run on edits is disabled and the message was edited.`); + outgoingLogger.debug({ msg: 'Trigger run on edits disabled and message was edited', triggerName: trigger.name }); return; } @@ -547,7 +551,7 @@ class RocketChatIntegrationHandler { this.mapEventArgsToData(data, { event, message, room, owner, user }); await updateHistory({ historyId, step: 'mapped-args-to-data', data, triggerWord: word }); - outgoingLogger.info(`Will be executing the Integration "${trigger.name}" to the url: ${url}`); + outgoingLogger.info({ msg: 'Will be executing integration', integrationName: trigger.name, url }); outgoingLogger.debug({ data }); const scriptEngine = this.getEngine(trigger); @@ -620,9 +624,9 @@ class RocketChatIntegrationHandler { .then(async (res) => { const content = await res.text(); if (!content) { - outgoingLogger.warn(`Result for the Integration ${trigger.name} to ${url} is empty`); + outgoingLogger.warn({ msg: 'Integration result is empty', integrationName: trigger.name, url }); } else { - outgoingLogger.info(`Status code for the Integration ${trigger.name} to ${url} is ${res.status}`); + outgoingLogger.info({ msg: 'Integration HTTP status', integrationName: trigger.name, url, status: res.status }); } const data = (() => { @@ -686,13 +690,15 @@ class RocketChatIntegrationHandler { if (!content || !this.successResults.includes(res.status)) { if (content) { outgoingLogger.error({ - msg: `Error for the Integration "${trigger.name}" to ${url}`, + msg: 'Error for integration request', + integrationName: trigger.name, + url, result: content, }); if (res.status === 410) { await updateHistory({ historyId, step: 'after-process-http-status-410', error: true }); - outgoingLogger.error(`Disabling the Integration "${trigger.name}" because the status code was 401 (Gone).`); + outgoingLogger.error({ msg: 'Disabling integration due to 410 (Gone) status', integrationName: trigger.name }); await Integrations.updateOne({ _id: trigger._id }, { $set: { enabled: false } }); void notifyOnIntegrationChangedById(trigger._id); return; @@ -701,7 +707,9 @@ class RocketChatIntegrationHandler { if (res.status === 500) { await updateHistory({ historyId, step: 'after-process-http-status-500', error: true }); outgoingLogger.error({ - msg: `Error "500" for the Integration "${trigger.name}" to ${url}.`, + msg: 'Error 500 for integration request', + integrationName: trigger.name, + url, content, }); return; @@ -738,7 +746,7 @@ class RocketChatIntegrationHandler { return; } - outgoingLogger.info(`Trying the Integration ${trigger.name} to ${url} again in ${waitTime} milliseconds.`); + outgoingLogger.info({ msg: 'Retrying integration', integrationName: trigger.name, url, waitTime }); setTimeout(() => { void this.executeTriggerUrl(url, trigger, { event, message, room, owner, user }, tries + 1); }, waitTime); 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/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 8e28c3334a828..c012a8a7d8a0b 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -195,7 +195,7 @@ export class QueueManager { await Promise.all([afterInquiryQueued(inquiry), afterRoomQueued(room)]); if (defaultAgent) { - logger.debug(`Setting default agent for inquiry ${inquiry._id} to ${defaultAgent.username}`); + logger.debug({ msg: 'Setting default agent for inquiry', inquiryId: inquiry._id, agentUsername: defaultAgent.username }); await LivechatInquiry.setDefaultAgentById(inquiry._id, defaultAgent); } @@ -225,7 +225,7 @@ export class QueueManager { }); if (!newRoom) { - logger.error(`Room with id ${room._id} not found after inquiry verification.`); + logger.error({ msg: 'Room not found after inquiry verification', roomId: room._id }); throw new Error('room-not-found'); } @@ -259,7 +259,7 @@ export class QueueManager { try { session.startTransaction(); const room = await createLivechatRoom(insertionRoom, session); - logger.debug(`Room for visitor ${guest._id} created with id ${room._id}`); + logger.debug({ msg: 'Room created for visitor', visitorId: guest._id, roomId: room._id }); const inquiry = await createLivechatInquiry({ rid, name: room.fname, @@ -301,7 +301,7 @@ export class QueueManager { agent?: SelectedAgent; extraData?: IOmnichannelRoomExtraData; }) { - logger.debug(`Requesting a room for guest ${guest._id}`); + logger.debug({ msg: 'Requesting room for guest', guestId: guest._id }); check( guest, Match.ObjectIncluding({ @@ -386,7 +386,7 @@ export class QueueManager { const newRoom = await LivechatRooms.findOneById(rid); if (!newRoom) { - logger.error(`Room with id ${rid} not found`); + logger.error({ msg: 'Room not found', roomId: rid }); throw new Error('room-not-found'); } @@ -427,11 +427,11 @@ export class QueueManager { return archivedRoom; } - logger.debug(`Attempting to unarchive room with id ${rid}`); + logger.debug({ msg: 'Attempting to unarchive room', roomId: rid }); const oldInquiry = await LivechatInquiry.findOneByRoomId>(rid, { projection: { _id: 1 } }); if (oldInquiry) { - logger.debug(`Removing old inquiry (${oldInquiry._id}) for room ${rid}`); + logger.debug({ msg: 'Removing old inquiry before unarchiving room', inquiryId: oldInquiry._id, roomId: rid }); await LivechatInquiry.removeByRoomId(rid); void notifyOnLivechatInquiryChangedById(oldInquiry._id, 'removed'); } @@ -467,7 +467,7 @@ export class QueueManager { } await this.requeueInquiry(inquiry, room, defaultAgent); - logger.debug(`Inquiry ${inquiry._id} queued`); + logger.debug({ msg: 'Inquiry queued', inquiryId: inquiry._id }); return room; } @@ -477,7 +477,7 @@ export class QueueManager { return; } - logger.debug(`Notifying agents of new inquiry ${inquiry._id} queued`); + logger.debug({ msg: 'Notifying agents of queued inquiry', inquiryId: inquiry._id }); const { department, rid, v } = inquiry; // Alert only the online agents of the queued request diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index ff91d04819846..56f4f6b8a3ad6 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -95,7 +95,12 @@ export const RoutingManager: Routing = { }, async getNextAgent(department, ignoreAgentId) { - logger.debug(`Getting next available agent with method ${settings.get('Livechat_Routing_Method')}`); + logger.debug({ + msg: 'Getting next available agent with method', + routingMethod: settings.get('Livechat_Routing_Method'), + department, + ignoreAgentId, + }); return this.getMethod().getNextAgent(department, ignoreAgentId); }, @@ -114,7 +119,7 @@ export const RoutingManager: Routing = { } if (!agent) { - logger.debug(`No agents available. Unable to delegate inquiry ${inquiry._id}`); + logger.debug({ msg: 'No agents available. Unable to delegate inquiry', inquiryId: inquiry._id }); // When an inqury reaches here on CE, it will stay here as 'ready' since on CE there's no mechanism to re queue it. // When reaching this point, managers have to manually transfer the inquiry to another room. This is expected. return LivechatRooms.findOneById(rid); @@ -141,7 +146,7 @@ export const RoutingManager: Routing = { const { rid, name, v, department } = inquiry; if (!(await createLivechatSubscription(rid, name, v, agent, department))) { - logger.debug(`Cannot assign agent to inquiry ${inquiry._id}: Cannot create subscription`); + logger.debug({ msg: 'Cannot assign agent to inquiry. Cannot create subscription', inquiryId: inquiry._id, agentId: agent.agentId }); throw new Meteor.Error('error-creating-subscription', 'Error creating subscription'); } @@ -157,7 +162,7 @@ export const RoutingManager: Routing = { await dispatchAgentDelegated(rid, agent.agentId); - logger.debug(`Agent ${agent.agentId} assigned to inquiry ${inquiry._id}. Instances notified`); + logger.debug({ msg: 'Agent assigned to inquiry. Instances notified', agentId: agent.agentId, inquiryId: inquiry._id }); return { inquiry, user }; }, @@ -176,7 +181,7 @@ export const RoutingManager: Routing = { }); if (!room?.open) { - logger.debug(`Cannot unassign agent from inquiry ${inquiry._id}: Room already closed`); + logger.debug({ msg: 'Cannot unassign agent from inquiry. Room already closed', inquiryId: inquiry._id }); return false; } @@ -243,12 +248,12 @@ export const RoutingManager: Routing = { const { _id, rid } = inquiry; if (!room?.open) { - logger.debug(`Cannot take Inquiry ${inquiry._id}: Room is closed`); + logger.debug({ msg: 'Cannot take inquiry. Room is closed', inquiryId: inquiry._id }); return room; } if (room.servedBy && room.servedBy._id === agent.agentId) { - logger.debug(`Cannot take Inquiry ${inquiry._id}: Already taken by agent ${room.servedBy._id}`); + logger.debug({ msg: 'Cannot take inquiry. Already taken by agent', inquiryId: inquiry._id, agentId: room.servedBy._id }); return room; } @@ -266,7 +271,7 @@ export const RoutingManager: Routing = { } if (!agent) { - logger.debug(`Cannot take Inquiry ${inquiry._id}: Precondition failed for agent`); + logger.debug({ msg: 'Cannot take inquiry. Precondition failed for agent', inquiryId: inquiry._id }); const cbRoom = await callbacks.run<'livechat.onAgentAssignmentFailed'>('livechat.onAgentAssignmentFailed', room, { inquiry, options, @@ -291,7 +296,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', { @@ -313,11 +318,11 @@ export const RoutingManager: Routing = { } if (transferData.userId) { - logger.debug(`Transfering room ${room._id} to user ${transferData.userId}`); + logger.debug({ msg: 'Transferring room to user', roomId: room._id, userId: transferData.userId }); return forwardRoomToAgent(room, transferData); } - logger.debug(`Unable to transfer room ${room._id}: No target provided`); + logger.debug({ msg: 'Unable to transfer room. No target provided', roomId: room._id }); return false; }, diff --git a/apps/meteor/app/livechat/server/lib/closeRoom.ts b/apps/meteor/app/livechat/server/lib/closeRoom.ts index 24770e40af29c..dbaa2ad5d4b54 100644 --- a/apps/meteor/app/livechat/server/lib/closeRoom.ts +++ b/apps/meteor/app/livechat/server/lib/closeRoom.ts @@ -46,7 +46,7 @@ export async function closeRoom(params: CloseRoomParams, attempts = 2): Promise< // Dont propagate transaction errors if (shouldRetryTransaction(e)) { if (attempts > 0) { - logger.debug(`Retrying close room because of transient error. Attempts left: ${attempts}`); + logger.debug({ msg: 'Retrying close room because of transient error', attemptsLeft: attempts }); return closeRoom(params, attempts - 1); } @@ -77,7 +77,7 @@ async function afterRoomClosed( // And passing just _some_ actions to the transaction creates some deadlocks since messages are updated in the afterSaveMessages callbacks. const transcriptRequested = !!params.room.transcriptRequest || (!settings.get('Livechat_enable_transcript') && settings.get('Livechat_transcript_send_always')); - logger.debug(`Sending closing message to room ${newRoom._id}`); + logger.debug({ msg: 'Sending closing message to room', roomId: newRoom._id }); await Message.saveSystemMessageAndNotifyUser('livechat-close', newRoom._id, params.comment ?? '', chatCloser, { groupable: false, transcriptRequested, @@ -88,15 +88,15 @@ async function afterRoomClosed( await Message.saveSystemMessage('command', newRoom._id, 'promptTranscript', chatCloser); } - logger.debug(`Running callbacks for room ${newRoom._id}`); + logger.debug({ msg: 'Running callbacks for room', roomId: newRoom._id }); process.nextTick(() => { /** * @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; @@ -118,7 +118,7 @@ async function afterRoomClosed( void notifyOnLivechatInquiryChanged(inquiry, 'removed'); } - logger.debug(`Room ${newRoom._id} was closed`); + logger.debug({ msg: 'Room was closed', roomId: newRoom._id }); } async function doCloseRoom( @@ -128,9 +128,9 @@ async function doCloseRoom( const { comment } = params; const { room, forceClose } = params; - logger.debug({ msg: `Attempting to close room`, roomId: room._id, forceClose }); + logger.debug({ msg: 'Attempting to close room', roomId: room._id, forceClose }); if (!room || !isOmnichannelRoom(room) || (!forceClose && !room.open)) { - logger.debug(`Room ${room._id} is not open`); + logger.debug({ msg: 'Room is not open', roomId: room._id }); throw new Error('error-room-closed'); } @@ -140,7 +140,7 @@ async function doCloseRoom( } const { updatedOptions: options } = await resolveChatTags(room, params.options); - logger.debug(`Resolved chat tags for room ${room._id}`); + logger.debug({ msg: 'Resolved chat tags for room', roomId: room._id }); const now = new Date(); const { _id: rid, servedBy } = room; @@ -152,11 +152,11 @@ async function doCloseRoom( ...(serviceTimeDuration && { serviceTimeDuration }), ...options, }; - logger.debug(`Room ${room._id} was closed at ${closeData.closedAt} (duration ${closeData.chatDuration})`); + logger.debug({ msg: 'Room was closed', roomId: room._id, closedAt: closeData.closedAt, chatDuration: closeData.chatDuration }); if (isRoomClosedByUserParams(params)) { const { user } = params; - logger.debug(`Closing by user ${user?._id}`); + logger.debug({ msg: 'Closing by user', userId: user?._id }); closeData.closer = 'user'; closeData.closedBy = { _id: user?._id || '', @@ -164,7 +164,7 @@ async function doCloseRoom( }; } else if (isRoomClosedByVisitorParams(params)) { const { visitor } = params; - logger.debug(`Closing by visitor ${params.visitor._id}`); + logger.debug({ msg: 'Closing by visitor', visitorId: params.visitor._id }); closeData.closer = 'visitor'; closeData.closedBy = { _id: visitor._id, @@ -174,7 +174,7 @@ async function doCloseRoom( throw new Error('Error: Please provide details of the user or visitor who closed the room'); } - logger.debug(`Updating DB for room ${room._id} with close data`); + logger.debug({ msg: 'Updating DB for room with close data', roomId: room._id }); const inquiry = await LivechatInquiry.findOneByRoomId(rid, { session }); const removedInquiry = await LivechatInquiry.removeByRoomId(rid, { session }); @@ -204,7 +204,7 @@ async function doCloseRoom( } } - logger.debug(`DB updated for room ${room._id}`); + logger.debug({ msg: 'DB updated for room', roomId: room._id }); // Retrieve the closed room const newRoom = await LivechatRooms.findOneById(rid, { session }); @@ -220,7 +220,7 @@ async function resolveChatTags( room: IOmnichannelRoom, options: CloseRoomParams['options'] = {}, ): Promise<{ updatedOptions: CloseRoomParams['options'] }> { - logger.debug(`Resolving chat tags for room ${room._id}`); + logger.debug({ msg: 'Resolving chat tags for room', roomId: room._id }); const concatUnique = (...arrays: (string[] | undefined)[]): string[] => [ ...new Set(([] as string[]).concat(...arrays.filter((a): a is string[] => !!a))), @@ -281,7 +281,7 @@ async function resolveChatTags( } export async function closeOpenChats(userId: string, comment?: string) { - logger.debug(`Closing open chats for user ${userId}`); + logger.debug({ msg: 'Closing open chats for user', userId }); const user = await Users.findOneById(userId); const extraQuery = await applyDepartmentRestrictions({}, userId); diff --git a/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorIfMissingContact.ts b/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorIfMissingContact.ts index 852effa66370f..ba6851555130b 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorIfMissingContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorIfMissingContact.ts @@ -9,7 +9,7 @@ export async function migrateVisitorIfMissingContact( visitorId: ILivechatVisitor['_id'], source: IOmnichannelSource, ): Promise { - logger.debug(`Detecting visitor's contact ID`); + logger.debug({ msg: 'Detecting visitor contact ID' }); // Check if there is any contact already linking to this visitorId and source const contactId = await getContactIdByVisitor({ visitorId, source }); if (contactId) { diff --git a/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorToContactId.ts b/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorToContactId.ts index c0f8ad917d6f0..42be5926c5f2f 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorToContactId.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/migrateVisitorToContactId.ts @@ -34,12 +34,12 @@ export async function migrateVisitorToContactId({ // Search for any contact that is not yet associated with any visitor and that have the same email or phone number as this visitor. const existingContact = await LivechatContacts.findContactMatchingVisitor(visitor); if (!existingContact) { - logger.debug(`Creating a new contact for existing visitor ${visitor._id}`); + logger.debug({ msg: 'Creating a new contact for existing visitor', visitorId: visitor._id }); return createContactFromVisitor(visitor, source); } // There is already an existing contact with no linked visitors and matching this visitor's phone or email, so let's use it - logger.debug(`Adding channel to existing contact ${existingContact._id}`); + logger.debug({ msg: 'Adding channel to existing contact', contactId: existingContact._id }); await ContactMerger.mergeVisitorIntoContact(visitor, existingContact, source); // Update all existing rooms matching the visitor id and source to set the contactId to them diff --git a/apps/meteor/app/livechat/server/lib/custom-fields.ts b/apps/meteor/app/livechat/server/lib/custom-fields.ts index 64d1776316c23..09cdc4f0b7d9a 100644 --- a/apps/meteor/app/livechat/server/lib/custom-fields.ts +++ b/apps/meteor/app/livechat/server/lib/custom-fields.ts @@ -59,7 +59,7 @@ export async function setCustomFields({ overwrite: boolean; token: string; }): Promise { - livechatLogger.debug(`Setting custom fields data for visitor with token ${token}`); + livechatLogger.debug({ msg: 'Setting custom fields data for visitor with token', token }); const customField = await LivechatCustomField.findOneById(key); if (!customField) { @@ -169,7 +169,7 @@ export async function setMultipleCustomFields({ overwrite: boolean; }[] > { - livechatLogger.debug(`Setting custom fields data for visitor with token ${visitor.token}`); + livechatLogger.debug({ msg: 'Setting custom fields data for visitor with token', token: visitor.token }); const keys = customFields.map((customField) => customField.key); const dbFields = await LivechatCustomField.find( diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index d6a8acc646315..3612ba48415a6 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) { @@ -215,7 +212,7 @@ export async function setDepartmentForGuest({ visitorId, department }: { visitor } export async function removeDepartment(departmentId: string) { - livechatLogger.debug(`Removing department: ${departmentId}`); + livechatLogger.debug({ msg: 'Removing department', departmentId }); const department = await LivechatDepartment.findOneById>(departmentId, { projection: { _id: 1, businessHourId: 1, parentId: 1 }, @@ -272,7 +269,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/app/livechat/server/lib/guests.ts b/apps/meteor/app/livechat/server/lib/guests.ts index b4213d8211e91..f913ef83c05d6 100644 --- a/apps/meteor/app/livechat/server/lib/guests.ts +++ b/apps/meteor/app/livechat/server/lib/guests.ts @@ -48,7 +48,7 @@ export async function saveGuest( const customFields: Record = {}; if ((!userId || (await hasPermissionAsync(userId, 'edit-livechat-room-customfields'))) && Object.keys(livechatData).length) { - livechatLogger.debug({ msg: `Saving custom fields for visitor ${_id}`, livechatData }); + livechatLogger.debug({ msg: 'Saving custom fields for visitor', visitorId: _id, livechatData }); for await (const field of LivechatCustomField.findByScope('visitor')) { if (!livechatData.hasOwnProperty(field._id)) { continue; @@ -63,7 +63,11 @@ export async function saveGuest( customFields[field._id] = value; } updateData.livechatData = customFields; - livechatLogger.debug(`About to update ${Object.keys(customFields).length} custom fields for visitor ${_id}`); + livechatLogger.debug({ + msg: 'About to update custom fields for visitor', + visitorId: _id, + customFieldCount: Object.keys(customFields).length, + }); } const ret = await LivechatVisitors.saveGuestById(_id, updateData); diff --git a/apps/meteor/app/livechat/server/lib/rooms.ts b/apps/meteor/app/livechat/server/lib/rooms.ts index 6a7beca8da944..a4b6d0dbc22b6 100644 --- a/apps/meteor/app/livechat/server/lib/rooms.ts +++ b/apps/meteor/app/livechat/server/lib/rooms.ts @@ -54,7 +54,7 @@ export async function getRoom( if (!settings.get('Livechat_enabled')) { throw new Meteor.Error('error-omnichannel-is-disabled'); } - livechatLogger.debug(`Attempting to find or create a room for visitor ${guest._id}`); + livechatLogger.debug({ msg: 'Attempting to find or create a room for visitor', visitorId: guest._id }); const room = await LivechatRooms.findOneById(message.rid); if (room?.v._id && (await LivechatContacts.isChannelBlocked(Visitors.makeVisitorAssociation(room.v._id, room.source)))) { @@ -62,7 +62,7 @@ export async function getRoom( } if (!room?.open) { - livechatLogger.debug(`Last room for visitor ${guest._id} closed. Creating new one`); + livechatLogger.debug({ msg: 'Last room for visitor closed. Creating new one', visitorId: guest._id }); } if (!room?.open) { @@ -73,7 +73,7 @@ export async function getRoom( } if (room.v.token !== guest.token) { - livechatLogger.debug(`Visitor ${guest._id} trying to access another visitor's room`); + livechatLogger.debug({ msg: 'Visitor trying to access another visitor room', visitorId: guest._id }); throw new Meteor.Error('cannot-access-room'); } @@ -111,16 +111,16 @@ export async function createRoom({ // if no department selected verify if there is at least one active and pick the first if (!defaultAgent && !visitor.department) { const department = await getRequiredDepartment(); - livechatLogger.debug(`No department or default agent selected for ${visitor._id}`); + livechatLogger.debug({ msg: 'No department or default agent selected for visitor', visitorId: visitor._id }); if (department) { - livechatLogger.debug(`Assigning ${visitor._id} to department ${department._id}`); + livechatLogger.debug({ msg: 'Assigning visitor to department', visitorId: visitor._id, departmentId: department._id }); visitor.department = department._id; } } // delegate room creation to QueueManager - livechatLogger.debug(`Calling QueueManager to request a room for visitor ${visitor._id}`); + livechatLogger.debug({ msg: 'Calling QueueManager to request a room for visitor', visitorId: visitor._id }); const room = await QueueManager.requestRoom({ guest: visitor, @@ -131,7 +131,7 @@ export async function createRoom({ extraData, }); - livechatLogger.debug(`Room obtained for visitor ${visitor._id} -> ${room._id}`); + livechatLogger.debug({ msg: 'Room obtained for visitor', visitorId: visitor._id, roomId: room._id }); await Messages.setRoomIdByToken(visitor.token, room._id); @@ -157,7 +157,7 @@ export async function saveRoomInfo( }, userId?: string, ) { - livechatLogger.debug(`Saving room information on room ${roomData._id}`); + livechatLogger.debug({ msg: 'Saving room information', roomId: roomData._id }); const { livechatData = {} } = roomData; const customFields: Record = {}; @@ -177,7 +177,11 @@ export async function saveRoomInfo( customFields[field._id] = value; } roomData.livechatData = customFields; - livechatLogger.debug(`About to update ${Object.keys(customFields).length} custom fields on room ${roomData._id}`); + livechatLogger.debug({ + msg: 'About to update custom fields on room', + roomId: roomData._id, + customFieldCount: Object.keys(customFields).length, + }); } await LivechatRooms.saveRoomById(roomData); @@ -211,7 +215,7 @@ export async function saveRoomInfo( } export async function returnRoomAsInquiry(room: IOmnichannelRoom, departmentId?: string, overrideTransferData: Partial = {}) { - livechatLogger.debug({ msg: `Transfering room to ${departmentId ? 'department' : ''} queue`, room }); + livechatLogger.debug({ msg: 'Transferring room to queue', scope: departmentId ? 'department' : undefined, room }); if (!room.open) { throw new Meteor.Error('room-closed', 'Room closed'); } @@ -245,7 +249,7 @@ export async function returnRoomAsInquiry(room: IOmnichannelRoom, departmentId?: } const transferredBy = normalizeTransferredByData(user, room); - livechatLogger.debug(`Transfering room ${room._id} by user ${transferredBy._id}`); + livechatLogger.debug({ msg: 'Transferring room by user', roomId: room._id, transferredBy: transferredBy._id }); const transferData = { scope: 'queue' as const, departmentId, transferredBy, ...overrideTransferData }; try { await saveTransferHistory(room, transferData); @@ -261,7 +265,7 @@ export async function returnRoomAsInquiry(room: IOmnichannelRoom, departmentId?: } export async function removeOmnichannelRoom(rid: string) { - livechatLogger.debug(`Deleting room ${rid}`); + livechatLogger.debug({ msg: 'Deleting room', roomId: rid }); check(rid, String); const room = await LivechatRooms.findOneById(rid); if (!room) { @@ -299,7 +303,7 @@ export async function removeOmnichannelRoom(rid: string) { for (const r of result) { if (r.status === 'rejected') { - livechatLogger.error(`Error removing room ${rid}: ${r.reason}`); + livechatLogger.error({ msg: 'Error removing room', roomId: rid, err: r.reason }); throw new Meteor.Error('error-removing-room', 'Error removing room'); } } diff --git a/apps/meteor/app/livechat/server/lib/service-status.ts b/apps/meteor/app/livechat/server/lib/service-status.ts index fc57ff7817323..471ab72558dda 100644 --- a/apps/meteor/app/livechat/server/lib/service-status.ts +++ b/apps/meteor/app/livechat/server/lib/service-status.ts @@ -18,18 +18,18 @@ export async function getOnlineAgents(department?: string, agent?: SelectedAgent } export async function online(department?: string, skipNoAgentSetting = false, skipFallbackCheck = false): Promise { - livechatLogger.debug(`Checking online agents ${department ? `for department ${department}` : ''}`); + livechatLogger.debug({ msg: 'Checking online agents', department }); if (!skipNoAgentSetting && settings.get('Livechat_accept_chats_with_no_agents')) { livechatLogger.debug('Can accept without online agents: true'); return true; } if (settings.get('Livechat_assign_new_conversation_to_bot')) { - livechatLogger.debug(`Fetching online bot agents for department ${department}`); + livechatLogger.debug({ msg: 'Fetching online bot agents for department', department }); // get & count where doing the same, but get was getting data, while count was only counting. We only need the count here const botAgents = await countBotAgents(department); if (botAgents) { - livechatLogger.debug(`Found ${botAgents} online`); + livechatLogger.debug({ msg: 'Found online bot agents', botAgents }); if (botAgents > 0) { return true; } @@ -37,7 +37,7 @@ export async function online(department?: string, skipNoAgentSetting = false, sk } const agentsOnline = await checkOnlineAgents(department, undefined, skipFallbackCheck); - livechatLogger.debug(`Are online agents ${department ? `for department ${department}` : ''}?: ${agentsOnline}`); + livechatLogger.debug({ msg: 'Online agents status', department, agentsOnline }); return agentsOnline; } diff --git a/apps/meteor/app/livechat/server/lib/tracking.ts b/apps/meteor/app/livechat/server/lib/tracking.ts index bfbcf9912212e..ef42ff6661e53 100644 --- a/apps/meteor/app/livechat/server/lib/tracking.ts +++ b/apps/meteor/app/livechat/server/lib/tracking.ts @@ -39,7 +39,7 @@ export async function savePageHistory(token: string, roomId: string | undefined, }; if (!roomId) { - livechatLogger.warn(`Saving page history without room id for visitor with token ${token}`); + livechatLogger.warn({ msg: 'Saving page history without room id for visitor', token }); // keep history of unregistered visitors for 1 month const keepHistoryMiliseconds = 2592000000; extraData.expireAt = new Date().getTime() + keepHistoryMiliseconds; diff --git a/apps/meteor/app/livechat/server/lib/transfer.ts b/apps/meteor/app/livechat/server/lib/transfer.ts index 74dcca09893ae..61d8af806c4ca 100644 --- a/apps/meteor/app/livechat/server/lib/transfer.ts +++ b/apps/meteor/app/livechat/server/lib/transfer.ts @@ -22,7 +22,7 @@ export async function saveTransferHistory(room: IOmnichannelRoom, transferData: const { _id, username } = transferredBy; const scopeData = scope || (nextDepartment ? 'department' : 'agent'); - livechatLogger.info(`Storing new chat transfer of ${room._id} [Transfered by: ${_id} to ${scopeData}]`); + livechatLogger.info({ msg: 'Storing new chat transfer', roomId: room._id, transferredBy: _id, scope: scopeData }); const transferMessage = { ...(transferData.transferredBy.userType === 'visitor' && { token: room.v.token }), @@ -41,7 +41,7 @@ export async function saveTransferHistory(room: IOmnichannelRoom, transferData: } export async function forwardOpenChats(userId: string) { - livechatLogger.debug(`Transferring open chats for user ${userId}`); + livechatLogger.debug({ msg: 'Transferring open chats for user', userId }); const user = await Users.findOneById(userId); if (!user) { throw new Error('error-invalid-user'); @@ -63,7 +63,7 @@ export async function forwardOpenChats(userId: string) { } export async function transfer(room: IOmnichannelRoom, guest: ILivechatVisitor, transferData: TransferData) { - livechatLogger.debug(`Transfering room ${room._id} [Transfered by: ${transferData?.transferredBy?._id}]`); + livechatLogger.debug({ msg: 'Transferring room', roomId: room._id, transferredBy: transferData?.transferredBy?._id }); if (room.onHold) { throw new Error('error-room-onHold'); } @@ -77,7 +77,7 @@ export async function transfer(room: IOmnichannelRoom, guest: ILivechatVisitor, } transferData.department = department; - livechatLogger.debug(`Transfering room ${room._id} to department ${transferData.department?._id}`); + livechatLogger.debug({ msg: 'Transferring room to department', roomId: room._id, departmentId: transferData.department?._id }); } return RoutingManager.transferRoom(room, guest, transferData); diff --git a/apps/meteor/app/livechat/server/lib/webhooks.ts b/apps/meteor/app/livechat/server/lib/webhooks.ts index 25af2060c470d..e973484fe57e1 100644 --- a/apps/meteor/app/livechat/server/lib/webhooks.ts +++ b/apps/meteor/app/livechat/server/lib/webhooks.ts @@ -37,7 +37,7 @@ export async function sendRequest( if (!isRetryable(result.status)) { webhooksLogger.error({ - msg: `Non-retryable error response from webhook`, + msg: 'Non-retryable error response from webhook', webhookUrl, status: result.status, response: await result.text(), @@ -50,7 +50,13 @@ export async function sendRequest( throw new Error(await result.text()); } catch (err) { const retryAfter = timeout * 4; - webhooksLogger.debug({ msg: `Error response on ${6 - attempts} try ->`, err, newAttemptAfterSeconds: retryAfter / 1000, webhookUrl }); + webhooksLogger.debug({ + msg: 'Error response on retry', + attempt: 6 - attempts, + err, + newAttemptAfterSeconds: retryAfter / 1000, + webhookUrl, + }); const remainingAttempts = attempts - 1; // try 5 times after 20 seconds each if (!remainingAttempts) { diff --git a/apps/meteor/ee/server/apps/communication/uikit.ts b/apps/meteor/ee/server/apps/communication/uikit.ts index 7d490406d007f..e5e851ef8f2b1 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'; @@ -11,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(); @@ -193,9 +193,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); @@ -212,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, @@ -230,7 +230,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 +261,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 +286,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 +307,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 +327,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) { diff --git a/apps/meteor/ee/server/apps/orchestrator.js b/apps/meteor/ee/server/apps/orchestrator.js index b4fed41ec05c0..e59c6b2444052 100644 --- a/apps/meteor/ee/server/apps/orchestrator.js +++ b/apps/meteor/ee/server/apps/orchestrator.js @@ -369,7 +369,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'); 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 }); diff --git a/ee/packages/federation-matrix/docker-compose.test.yml b/ee/packages/federation-matrix/docker-compose.test.yml index 06e0b30d8c696..8b34c6509cec2 100644 --- a/ee/packages/federation-matrix/docker-compose.test.yml +++ b/ee/packages/federation-matrix/docker-compose.test.yml @@ -6,12 +6,9 @@ networks: services: traefik: image: traefik:v2.9 - container_name: traefik profiles: - - test-local - - test-prebuilt - - element-local - - element-prebuilt + - test + - element command: - "--api.insecure=true" # - "--log.level=DEBUG" @@ -32,7 +29,7 @@ services: networks: # Defines and isolate one network per service to prevent inter service communication which # would not happen in real life. The name clashing between the host and service in the same - # network (like rc1 service provided as rc1 host) is causing a bug on home server address + # network (like rc1 service provided as rc1 host) is causing a bug on home server address # resolution which tries to communicate with the container directly some times and which does # not provide SSL neither the correct exposed ports. hs1-net: @@ -45,17 +42,14 @@ services: # HomeServer 1 (synapse) hs1: image: matrixdotorg/synapse:latest - container_name: hs1 profiles: - - test-local - - test-prebuilt - - element-local - - element-prebuilt + - test + - element entrypoint: | sh -c - "update-ca-certificates && - mkdir -p /data/media_store && - chown -R 991:991 /data && + "update-ca-certificates && + mkdir -p /data/media_store && + chown -R 991:991 /data && /start.py & until curl -sf http://localhost:8008/_matrix/client/versions; do echo '=====> Waiting for Synapse...'; @@ -83,56 +77,15 @@ services: - "traefik.http.routers.hs1.tls=true" - "traefik.http.services.hs1.loadbalancer.server.port=8008" -# Rocket.Chat rc1 (local build) - rc1-local: +# Rocket.Chat rc1 + rc1: build: context: ${ROCKETCHAT_BUILD_CONTEXT:-./test/dist} dockerfile: ${ROCKETCHAT_DOCKERFILE:-../../../apps/meteor/.docker/Dockerfile.alpine} - image: rocket.chat:local-test - container_name: rc1 - profiles: - - test-local - - element-local - environment: - ROOT_URL: https://rc1 - PORT: 3000 - MONGO_URL: mongodb://mongo:27017/rc1?replicaSet=rs0 - NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/rootCA.pem - LOG_LEVEL: debug - ROCKETCHAT_LICENSE: ${ENTERPRISE_LICENSE_RC1} - OVERWRITE_SETTING_Show_Setup_Wizard: completed - OVERWRITE_SETTING_Federation_Service_Enabled: true - OVERWRITE_SETTING_Federation_Service_Domain: rc1 - OVERWRITE_SETTING_Cloud_Workspace_Client_Id: temp_id - OVERWRITE_SETTING_Cloud_Workspace_Client_Secret: temp_secret - ADMIN_USERNAME: admin - ADMIN_PASS: admin - ADMIN_EMAIL: admin@admin.com - TEST_MODE: true - volumes: - - ./docker-compose/traefik/certs/ca/rootCA.crt:/usr/local/share/ca-certificates/rootCA.pem - networks: - - rc1-net - depends_on: - - mongo - labels: - - "traefik.enable=true" - - "traefik.http.routers.rc1.rule=Host(`rc1`)" - - "traefik.http.routers.rc1.entrypoints=websecure" - - "traefik.http.routers.rc1.tls=true" - - "traefik.http.services.rc1.loadbalancer.server.port=3000" - # HTTPS Redirect - - "traefik.http.middlewares.rc1.redirectscheme.scheme=https" - - "traefik.http.routers.rc1-http.rule=Host(`rc1`)" - - "traefik.http.routers.rc1-http.middlewares=rc1" - -# Rocket.Chat rc1 (pre-built image) - rc1-prebuilt: image: ${ROCKETCHAT_IMAGE:-rocketchat/rocket.chat:latest} - container_name: rc1 profiles: - - test-prebuilt - - element-prebuilt + - test + - element environment: ROOT_URL: https://rc1 PORT: 3000 @@ -168,12 +121,9 @@ services: mongo: image: mongo:8.0 - container_name: mongo profiles: - - test-local - - test-prebuilt - - element-local - - element-prebuilt + - test + - element restart: on-failure ports: - "27017:27017" @@ -181,7 +131,7 @@ services: bash -c "mongod --replSet rs0 --bind_ip_all & sleep 2; - until mongosh --eval \"db.adminCommand('ping')\"; do + until mongosh --eval \"db.adminCommand('ping')\"; do echo '=====> Waiting for Mongo...'; sleep 1; done; @@ -191,13 +141,11 @@ services: wait" networks: - rc1-net - + element: image: vectorim/element-web - container_name: element profiles: - - element-local - - element-prebuilt + - element # ports: # - "8080:80" volumes: diff --git a/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh b/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh index 1963f4f87b771..b2b5e8bd7b293 100755 --- a/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh +++ b/ee/packages/federation-matrix/tests/scripts/run-integration-tests.sh @@ -33,7 +33,6 @@ INCLUDE_ELEMENT=false USE_PREBUILT_IMAGE=false PREBUILT_IMAGE="" INTERRUPTED=false -PROFILE_PREFIX="local" # Default to local build NO_TEST=false while [[ $# -gt 0 ]]; do @@ -143,17 +142,17 @@ cleanup() { log_info " - Element: https://element" fi if [ "$INCLUDE_ELEMENT" = true ]; then - log_info "To stop containers manually, run: docker compose -f $DOCKER_COMPOSE_FILE --profile element-$PROFILE_PREFIX down -v" + log_info "To stop containers manually, run: docker compose -f $DOCKER_COMPOSE_FILE --profile element down -v" else - log_info "To stop containers manually, run: docker compose -f $DOCKER_COMPOSE_FILE --profile test-$PROFILE_PREFIX down -v" + log_info "To stop containers manually, run: docker compose -f $DOCKER_COMPOSE_FILE --profile test down -v" fi else log_info "Cleaning up services..." if [ -f "$DOCKER_COMPOSE_FILE" ]; then if [ "$INCLUDE_ELEMENT" = true ]; then - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "element-$PROFILE_PREFIX" down -v 2>/dev/null || true + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "element" down -v 2>/dev/null || true else - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "test-$PROFILE_PREFIX" down -v 2>/dev/null || true + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "test" down -v 2>/dev/null || true fi fi log_success "Cleanup completed" @@ -210,27 +209,26 @@ fi log_info "🚀 Starting Federation Integration Tests" log_info "=====================================" +BUILD_PARAM="" + # Set environment variables for Docker Compose if [ "$USE_PREBUILT_IMAGE" = true ]; then export ROCKETCHAT_IMAGE="$PREBUILT_IMAGE" - PROFILE_PREFIX="prebuilt" log_info "Using pre-built image: $PREBUILT_IMAGE" else export ROCKETCHAT_BUILD_CONTEXT="$BUILD_DIR" export ROCKETCHAT_DOCKERFILE="$ROCKETCHAT_ROOT/apps/meteor/.docker/Dockerfile.alpine" - PROFILE_PREFIX="local" + BUILD_PARAM="--build" log_info "Building from local context: $BUILD_DIR" fi # Start services if [ "$INCLUDE_ELEMENT" = true ]; then - PROFILE="element-$PROFILE_PREFIX" log_info "Starting all federation services including Element web client..." - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "$PROFILE" up -d --build + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "element" up -d $BUILD_PARAM else - PROFILE="test-$PROFILE_PREFIX" log_info "Starting federation services (test profile only)..." - docker compose -f "$DOCKER_COMPOSE_FILE" --profile "$PROFILE" up -d --build + docker compose -f "$DOCKER_COMPOSE_FILE" --profile "test" up -d $BUILD_PARAM fi # Wait for rc1 container to be running 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;