From 9c22b825917641fda94f7df0dbd8a461888d855f Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 11 Feb 2025 13:28:39 -0300 Subject: [PATCH 1/8] feat: add IPostSystemMessageSent event --- .../app/apps/server/bridges/listeners.js | 8 ++ .../app/apps/server/converters/messages.js | 11 ++- .../server/services/messages/service.ts | 6 ++ .../messages/IPostSystemMessageSent.ts | 13 +++ .../src/definition/messages/ISystemMessage.ts | 79 +++++++++++++++++++ .../src/definition/messages/index.ts | 4 + .../src/definition/metadata/AppInterface.ts | 1 + .../src/definition/metadata/AppMethod.ts | 1 + .../src/server/bridges/IListenerBridge.ts | 1 + .../src/server/managers/AppListenerManager.ts | 15 ++++ packages/apps/src/bridges/IListenerBridge.ts | 2 + 11 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts create mode 100644 packages/apps-engine/src/definition/messages/ISystemMessage.ts diff --git a/apps/meteor/app/apps/server/bridges/listeners.js b/apps/meteor/app/apps/server/bridges/listeners.js index 13db1179310c5..3aa1be4d3edbf 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.js +++ b/apps/meteor/app/apps/server/bridges/listeners.js @@ -26,6 +26,8 @@ export class AppListenerBridge { case AppInterface.IPostMessageStarred: case AppInterface.IPostMessageReported: return 'messageEvent'; + case AppInterface.IPostSystemMessageSent: + return 'systemMessageEvent'; case AppInterface.IPreRoomCreatePrevent: case AppInterface.IPreRoomCreateExtend: case AppInterface.IPreRoomCreateModify: @@ -68,6 +70,12 @@ export class AppListenerBridge { return this.orch.getManager().getListenerManager().executeListener(inte, payload); } + async systemMessageEvent(inte, payload) { + const msg = await this.orch.getConverters().get('messages').convertSystemMessage(payload); + console.log('systemMessageEvent', inte, msg); + return this.orch.getManager().getListenerManager().executeListener(inte, msg); + } + async messageEvent(inte, message, ...payload) { const msg = await this.orch.getConverters().get('messages').convertMessage(message); diff --git a/apps/meteor/app/apps/server/converters/messages.js b/apps/meteor/app/apps/server/converters/messages.js index 704dde1850490..5c96a3604ccde 100644 --- a/apps/meteor/app/apps/server/converters/messages.js +++ b/apps/meteor/app/apps/server/converters/messages.js @@ -18,7 +18,7 @@ export class AppMessagesConverter { return this.convertMessage(msg); } - async convertMessageRaw(msgObj) { + async convertMessageRaw(msgObj, extraFields = {}) { if (!msgObj) { return undefined; } @@ -48,6 +48,7 @@ export class AppMessagesConverter { attachments: getAttachments, sender: 'u', threadMsgCount: 'tcount', + ...extraFields, }; return transformMappedData(message, map); @@ -243,6 +244,14 @@ export class AppMessagesConverter { ); } + async convertSystemMessage(message) { + if (!message) { + return undefined; + } + + return this.convertMessageRaw(message, { type: 't' }); + } + async _convertAttachmentsToApp(attachments) { if (typeof attachments === 'undefined' || !Array.isArray(attachments)) { return undefined; diff --git a/apps/meteor/server/services/messages/service.ts b/apps/meteor/server/services/messages/service.ts index 85afcf394f28b..0a4f879acba59 100644 --- a/apps/meteor/server/services/messages/service.ts +++ b/apps/meteor/server/services/messages/service.ts @@ -1,3 +1,5 @@ +import { Apps } from '@rocket.chat/apps'; +import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; 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'; @@ -152,6 +154,10 @@ export class MessageService extends ServiceClassInternal implements IMessageServ throw new Error('Failed to find the created message.'); } + if (Apps.self?.isLoaded()) { + void Apps.getBridges()?.getListenerBridge().systemMessageEvent(AppInterface.IPostSystemMessageSent, createdMessage); + } + void notifyOnMessageChange({ id: createdMessage._id, data: createdMessage }); void notifyOnRoomChangedById(rid); diff --git a/packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts b/packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts new file mode 100644 index 0000000000000..a7aca43efdbc9 --- /dev/null +++ b/packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts @@ -0,0 +1,13 @@ +import type { IHttp, IModify, IPersistence, IRead } from '../accessors'; +import type { ISystemMessage } from './ISystemMessage'; + +/** + * Handler for when a System message is sent. + * System messages are messages that are not sent by a user, but by the system itself. + */ +export interface IPostSystemMessageSent { + /** + * Method called *after* the system message is sent to the other clients. + */ + executePostSystemMessageSent(message: ISystemMessage, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise; +} diff --git a/packages/apps-engine/src/definition/messages/ISystemMessage.ts b/packages/apps-engine/src/definition/messages/ISystemMessage.ts new file mode 100644 index 0000000000000..f0d5bf30ebc47 --- /dev/null +++ b/packages/apps-engine/src/definition/messages/ISystemMessage.ts @@ -0,0 +1,79 @@ +import type { IMessage } from '.'; + +export type SystemMessageType = + | 'voip-call-started' + | 'voip-call-declined' + | 'voip-call-on-hold' + | 'voip-call-unhold' + | 'voip-call-ended' + | 'voip-call-duration' + | 'voip-call-wrapup' + | 'voip-call-ended-unexpectedly' + | 'removed-user-from-team' + | 'added-user-to-team' + | 'ult' + | 'user-converted-to-team' + | 'user-converted-to-channel' + | 'user-removed-room-from-team' + | 'user-deleted-room-from-team' + | 'user-added-room-to-team' + | 'ujt' + | 'livechat_navigation_history' + | 'livechat_transfer_history' + | 'livechat_transcript_history' + | 'livechat_video_call' + | 'livechat_transfer_history_fallback' + | 'livechat-close' + | 'livechat_webrtc_video_call' + | 'livechat-started' + | 'omnichannel_priority_change_history' + | 'omnichannel_sla_change_history' + | 'omnichannel_placed_chat_on_hold' + | 'omnichannel_on_hold_chat_resumed' + | 'otr' + | 'otr-ack' + | 'user_joined_otr' + | 'user_requested_otr_key_refresh' + | 'user_key_refreshed_successfully' + | 'e2e' + | 'uj' + | 'ul' + | 'ru' + | 'au' + | 'mute_unmute' + | 'r' + | 'ut' + | 'wm' + | 'rm' + | 'subscription-role-added' + | 'subscription-role-removed' + | 'room-archived' + | 'room-unarchived' + | 'room_changed_privacy' + | 'room_changed_description' + | 'room_changed_announcement' + | 'room_changed_avatar' + | 'room_changed_topic' + | 'room_e2e_enabled' + | 'room_e2e_disabled' + | 'user-muted' + | 'user-unmuted' + | 'room-removed-read-only' + | 'room-set-read-only' + | 'room-allowed-reacting' + | 'room-disallowed-reacting' + | 'command' + | 'videoconf' + | 'message_pinned' + | 'message_pinned_e2e' + | 'new-moderator' + | 'moderator-removed' + | 'new-owner' + | 'owner-removed' + | 'new-leader' + | 'leader-removed' + | 'discussion-created'; + +export interface ISystemMessage extends IMessage { + type?: SystemMessageType; +} diff --git a/packages/apps-engine/src/definition/messages/index.ts b/packages/apps-engine/src/definition/messages/index.ts index 61bd32fb2b697..b718f8094127d 100644 --- a/packages/apps-engine/src/definition/messages/index.ts +++ b/packages/apps-engine/src/definition/messages/index.ts @@ -21,6 +21,7 @@ import { IPostMessageReported } from './IPostMessageReported'; import { IPostMessageSent } from './IPostMessageSent'; import { IPostMessageStarred } from './IPostMessageStarred'; import { IPostMessageUpdated } from './IPostMessageUpdated'; +import { IPostSystemMessageSent } from './IPostSystemMessageSent'; import { IPreMessageDeletePrevent } from './IPreMessageDeletePrevent'; import { IPreMessageSentExtend } from './IPreMessageSentExtend'; import { IPreMessageSentModify } from './IPreMessageSentModify'; @@ -28,6 +29,7 @@ import { IPreMessageSentPrevent } from './IPreMessageSentPrevent'; import { IPreMessageUpdatedExtend } from './IPreMessageUpdatedExtend'; import { IPreMessageUpdatedModify } from './IPreMessageUpdatedModify'; import { IPreMessageUpdatedPrevent } from './IPreMessageUpdatedPrevent'; +import { ISystemMessage } from './ISystemMessage'; import { MessageActionButtonsAlignment } from './MessageActionButtonsAlignment'; import { MessageActionType } from './MessageActionType'; import { MessageProcessingType } from './MessageProcessingType'; @@ -68,4 +70,6 @@ export { MessageProcessingType, IMessageDeleteContext, Reaction, + ISystemMessage, + IPostSystemMessageSent, }; diff --git a/packages/apps-engine/src/definition/metadata/AppInterface.ts b/packages/apps-engine/src/definition/metadata/AppInterface.ts index 9b85bdb8d275e..23afb7efb0799 100644 --- a/packages/apps-engine/src/definition/metadata/AppInterface.ts +++ b/packages/apps-engine/src/definition/metadata/AppInterface.ts @@ -7,6 +7,7 @@ export enum AppInterface { IPreMessageSentExtend = 'IPreMessageSentExtend', IPreMessageSentModify = 'IPreMessageSentModify', IPostMessageSent = 'IPostMessageSent', + IPostSystemMessageSent = 'IPostSystemMessageSent', IPreMessageDeletePrevent = 'IPreMessageDeletePrevent', IPostMessageDeleted = 'IPostMessageDeleted', IPreMessageUpdatedPrevent = 'IPreMessageUpdatedPrevent', diff --git a/packages/apps-engine/src/definition/metadata/AppMethod.ts b/packages/apps-engine/src/definition/metadata/AppMethod.ts index b7fcf306f6aa4..9cf7fb22aa4fa 100644 --- a/packages/apps-engine/src/definition/metadata/AppMethod.ts +++ b/packages/apps-engine/src/definition/metadata/AppMethod.ts @@ -30,6 +30,7 @@ export enum AppMethod { EXECUTEPREMESSAGESENTMODIFY = 'executePreMessageSentModify', CHECKPOSTMESSAGESENT = 'checkPostMessageSent', EXECUTEPOSTMESSAGESENT = 'executePostMessageSent', + EXECUTEPOSTSYSTEMMESSAGESENT = 'executePostSystemMessageSent', EXECUTEPOSTMESSAGESENTTOBOT = 'executePostMessageSentToBot', diff --git a/packages/apps-engine/src/server/bridges/IListenerBridge.ts b/packages/apps-engine/src/server/bridges/IListenerBridge.ts index c29674ae257e0..7b0e17efe4ed1 100644 --- a/packages/apps-engine/src/server/bridges/IListenerBridge.ts +++ b/packages/apps-engine/src/server/bridges/IListenerBridge.ts @@ -4,6 +4,7 @@ import type { IRoom } from '../../definition/rooms'; import type { UIKitIncomingInteraction } from '../../definition/uikit'; export interface IListenerBridge { + systemMessageEvent(int: AppInterface, message: IMessage): Promise; messageEvent(int: AppInterface, message: IMessage): Promise; roomEvent(int: AppInterface, room: IRoom): Promise; uiKitInteractionEvent(int: AppInterface, action: UIKitIncomingInteraction): Promise; diff --git a/packages/apps-engine/src/server/managers/AppListenerManager.ts b/packages/apps-engine/src/server/managers/AppListenerManager.ts index f51adca31a921..3c7e9d2f7e531 100644 --- a/packages/apps-engine/src/server/managers/AppListenerManager.ts +++ b/packages/apps-engine/src/server/managers/AppListenerManager.ts @@ -11,6 +11,7 @@ import type { IMessageReactionContext, IMessageReportContext, IMessageStarContext, + ISystemMessage, } from '../../definition/messages'; import { AppInterface, AppMethod } from '../../definition/metadata'; import type { IRoom, IRoomUserJoinedContext, IRoomUserLeaveContext } from '../../definition/rooms'; @@ -44,6 +45,10 @@ interface IListenerExecutor { args: [IMessage]; result: IMessage; }; + [AppInterface.IPostSystemMessageSent]: { + args: [ISystemMessage]; + result: void; + }; [AppInterface.IPostMessageSent]: { args: [IMessage]; result: void; @@ -338,6 +343,9 @@ export class AppListenerManager { case AppInterface.IPostMessageSent: this.executePostMessageSent(data as IMessage); return; + case AppInterface.IPostSystemMessageSent: + this.executePostSystemMessageSent(data as ISystemMessage); + return; case AppInterface.IPreMessageDeletePrevent: return this.executePreMessageDeletePrevent(data as IMessage); case AppInterface.IPostMessageDeleted: @@ -560,6 +568,13 @@ export class AppListenerManager { } } + private async executePostSystemMessageSent(data: ISystemMessage): Promise { + for (const appId of this.listeners.get(AppInterface.IPostSystemMessageSent)) { + const app = this.manager.getOneById(appId); + await app.call(AppMethod.EXECUTEPOSTSYSTEMMESSAGESENT, data); + } + } + private async executePreMessageDeletePrevent(data: IMessage): Promise { let prevented = false; diff --git a/packages/apps/src/bridges/IListenerBridge.ts b/packages/apps/src/bridges/IListenerBridge.ts index faf34118cd30b..569cb84d21a76 100644 --- a/packages/apps/src/bridges/IListenerBridge.ts +++ b/packages/apps/src/bridges/IListenerBridge.ts @@ -21,6 +21,8 @@ declare module '@rocket.chat/apps-engine/server/bridges' { ): Promise; messageEvent(int: 'IPostMessageSent' | 'IPostMessageUpdated', message: IMessage): Promise; + systemMessageEvent(int: 'IPostSystemMessageSent', message: IMessage): Promise; + roomEvent(int: 'IPreRoomUserJoined' | 'IPostRoomUserJoined', room: IRoom, joiningUser: IUser, invitingUser?: IUser): Promise; roomEvent(int: 'IPreRoomUserLeave' | 'IPostRoomUserLeave', room: IRoom, leavingUser: IUser): Promise; From 8c7b5490ce1b34213836c85bca85fd0b6178557d Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 11 Feb 2025 13:33:21 -0300 Subject: [PATCH 2/8] chore: remove console.log --- apps/meteor/app/apps/server/bridges/listeners.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/app/apps/server/bridges/listeners.js b/apps/meteor/app/apps/server/bridges/listeners.js index 3aa1be4d3edbf..5147ee534f141 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.js +++ b/apps/meteor/app/apps/server/bridges/listeners.js @@ -72,7 +72,6 @@ export class AppListenerBridge { async systemMessageEvent(inte, payload) { const msg = await this.orch.getConverters().get('messages').convertSystemMessage(payload); - console.log('systemMessageEvent', inte, msg); return this.orch.getManager().getListenerManager().executeListener(inte, msg); } From ba5ce4118465c3c17083bbc1cafbff26c010b646 Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 12 Feb 2025 09:04:35 -0300 Subject: [PATCH 3/8] chore: apply suggestions --- .../app/apps/server/bridges/listeners.js | 8 +- .../app/apps/server/converters/messages.js | 13 +- .../server/services/messages/service.ts | 3 +- .../src/definition/messages/IMessage.ts | 2 + .../messages/IPostSystemMessageSent.ts | 4 +- .../src/definition/messages/ISystemMessage.ts | 79 ---------- .../src/definition/messages/MessageType.ts | 143 ++++++++++++++++++ .../src/definition/messages/index.ts | 4 +- .../src/server/bridges/IListenerBridge.ts | 1 - .../src/server/managers/AppListenerManager.ts | 7 +- packages/apps/src/bridges/IListenerBridge.ts | 4 +- 11 files changed, 158 insertions(+), 110 deletions(-) delete mode 100644 packages/apps-engine/src/definition/messages/ISystemMessage.ts create mode 100644 packages/apps-engine/src/definition/messages/MessageType.ts diff --git a/apps/meteor/app/apps/server/bridges/listeners.js b/apps/meteor/app/apps/server/bridges/listeners.js index 5147ee534f141..d18d5bdceb656 100644 --- a/apps/meteor/app/apps/server/bridges/listeners.js +++ b/apps/meteor/app/apps/server/bridges/listeners.js @@ -10,6 +10,7 @@ export class AppListenerBridge { // eslint-disable-next-line complexity const method = (() => { switch (event) { + case AppInterface.IPostSystemMessageSent: case AppInterface.IPreMessageSentPrevent: case AppInterface.IPreMessageSentExtend: case AppInterface.IPreMessageSentModify: @@ -26,8 +27,6 @@ export class AppListenerBridge { case AppInterface.IPostMessageStarred: case AppInterface.IPostMessageReported: return 'messageEvent'; - case AppInterface.IPostSystemMessageSent: - return 'systemMessageEvent'; case AppInterface.IPreRoomCreatePrevent: case AppInterface.IPreRoomCreateExtend: case AppInterface.IPreRoomCreateModify: @@ -70,11 +69,6 @@ export class AppListenerBridge { return this.orch.getManager().getListenerManager().executeListener(inte, payload); } - async systemMessageEvent(inte, payload) { - const msg = await this.orch.getConverters().get('messages').convertSystemMessage(payload); - return this.orch.getManager().getListenerManager().executeListener(inte, msg); - } - async messageEvent(inte, message, ...payload) { const msg = await this.orch.getConverters().get('messages').convertMessage(message); diff --git a/apps/meteor/app/apps/server/converters/messages.js b/apps/meteor/app/apps/server/converters/messages.js index 5c96a3604ccde..530a8aed6e4a5 100644 --- a/apps/meteor/app/apps/server/converters/messages.js +++ b/apps/meteor/app/apps/server/converters/messages.js @@ -18,7 +18,7 @@ export class AppMessagesConverter { return this.convertMessage(msg); } - async convertMessageRaw(msgObj, extraFields = {}) { + async convertMessageRaw(msgObj) { if (!msgObj) { return undefined; } @@ -48,7 +48,7 @@ export class AppMessagesConverter { attachments: getAttachments, sender: 'u', threadMsgCount: 'tcount', - ...extraFields, + type: 't', }; return transformMappedData(message, map); @@ -92,6 +92,7 @@ export class AppMessagesConverter { groupable: 'groupable', token: 'token', blocks: 'blocks', + type: 't', room: async (message) => { const result = await cache.get('room')(message.rid); delete message.rid; @@ -244,14 +245,6 @@ export class AppMessagesConverter { ); } - async convertSystemMessage(message) { - if (!message) { - return undefined; - } - - return this.convertMessageRaw(message, { type: 't' }); - } - async _convertAttachmentsToApp(attachments) { if (typeof attachments === 'undefined' || !Array.isArray(attachments)) { return undefined; diff --git a/apps/meteor/server/services/messages/service.ts b/apps/meteor/server/services/messages/service.ts index 0a4f879acba59..828d2da19ead4 100644 --- a/apps/meteor/server/services/messages/service.ts +++ b/apps/meteor/server/services/messages/service.ts @@ -1,5 +1,4 @@ import { Apps } from '@rocket.chat/apps'; -import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; 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'; @@ -155,7 +154,7 @@ export class MessageService extends ServiceClassInternal implements IMessageServ } if (Apps.self?.isLoaded()) { - void Apps.getBridges()?.getListenerBridge().systemMessageEvent(AppInterface.IPostSystemMessageSent, createdMessage); + void Apps.getBridges()?.getListenerBridge().messageEvent('IPostSystemMessageSent', createdMessage); } void notifyOnMessageChange({ id: createdMessage._id, data: createdMessage }); diff --git a/packages/apps-engine/src/definition/messages/IMessage.ts b/packages/apps-engine/src/definition/messages/IMessage.ts index d7ea6357497a8..50d36f9d5db2f 100644 --- a/packages/apps-engine/src/definition/messages/IMessage.ts +++ b/packages/apps-engine/src/definition/messages/IMessage.ts @@ -6,6 +6,7 @@ import type { IUser, IUserLookup } from '../users'; import type { IMessageAttachment } from './IMessageAttachment'; import type { IMessageFile } from './IMessageFile'; import type { IMessageReactions } from './IMessageReaction'; +import type { MessageType } from './MessageType'; export interface IMessage { id?: string; @@ -31,4 +32,5 @@ export interface IMessage { pinned?: boolean; pinnedAt?: Date; pinnedBy?: IUserLookup; + type?: MessageType; } diff --git a/packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts b/packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts index a7aca43efdbc9..053350f1a32bc 100644 --- a/packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts +++ b/packages/apps-engine/src/definition/messages/IPostSystemMessageSent.ts @@ -1,5 +1,5 @@ import type { IHttp, IModify, IPersistence, IRead } from '../accessors'; -import type { ISystemMessage } from './ISystemMessage'; +import type { IMessage } from './IMessage'; /** * Handler for when a System message is sent. @@ -9,5 +9,5 @@ export interface IPostSystemMessageSent { /** * Method called *after* the system message is sent to the other clients. */ - executePostSystemMessageSent(message: ISystemMessage, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise; + executePostSystemMessageSent(message: IMessage, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise; } diff --git a/packages/apps-engine/src/definition/messages/ISystemMessage.ts b/packages/apps-engine/src/definition/messages/ISystemMessage.ts deleted file mode 100644 index f0d5bf30ebc47..0000000000000 --- a/packages/apps-engine/src/definition/messages/ISystemMessage.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { IMessage } from '.'; - -export type SystemMessageType = - | 'voip-call-started' - | 'voip-call-declined' - | 'voip-call-on-hold' - | 'voip-call-unhold' - | 'voip-call-ended' - | 'voip-call-duration' - | 'voip-call-wrapup' - | 'voip-call-ended-unexpectedly' - | 'removed-user-from-team' - | 'added-user-to-team' - | 'ult' - | 'user-converted-to-team' - | 'user-converted-to-channel' - | 'user-removed-room-from-team' - | 'user-deleted-room-from-team' - | 'user-added-room-to-team' - | 'ujt' - | 'livechat_navigation_history' - | 'livechat_transfer_history' - | 'livechat_transcript_history' - | 'livechat_video_call' - | 'livechat_transfer_history_fallback' - | 'livechat-close' - | 'livechat_webrtc_video_call' - | 'livechat-started' - | 'omnichannel_priority_change_history' - | 'omnichannel_sla_change_history' - | 'omnichannel_placed_chat_on_hold' - | 'omnichannel_on_hold_chat_resumed' - | 'otr' - | 'otr-ack' - | 'user_joined_otr' - | 'user_requested_otr_key_refresh' - | 'user_key_refreshed_successfully' - | 'e2e' - | 'uj' - | 'ul' - | 'ru' - | 'au' - | 'mute_unmute' - | 'r' - | 'ut' - | 'wm' - | 'rm' - | 'subscription-role-added' - | 'subscription-role-removed' - | 'room-archived' - | 'room-unarchived' - | 'room_changed_privacy' - | 'room_changed_description' - | 'room_changed_announcement' - | 'room_changed_avatar' - | 'room_changed_topic' - | 'room_e2e_enabled' - | 'room_e2e_disabled' - | 'user-muted' - | 'user-unmuted' - | 'room-removed-read-only' - | 'room-set-read-only' - | 'room-allowed-reacting' - | 'room-disallowed-reacting' - | 'command' - | 'videoconf' - | 'message_pinned' - | 'message_pinned_e2e' - | 'new-moderator' - | 'moderator-removed' - | 'new-owner' - | 'owner-removed' - | 'new-leader' - | 'leader-removed' - | 'discussion-created'; - -export interface ISystemMessage extends IMessage { - type?: SystemMessageType; -} diff --git a/packages/apps-engine/src/definition/messages/MessageType.ts b/packages/apps-engine/src/definition/messages/MessageType.ts new file mode 100644 index 0000000000000..3afa6eb614b77 --- /dev/null +++ b/packages/apps-engine/src/definition/messages/MessageType.ts @@ -0,0 +1,143 @@ +/** + * Usually, normal messages won't have a type, but system messages will, end those are the types that are available. + */ +export type MessageType = + /** Sent when a voip call has started */ + | 'voip-call-started' + /** Sent when a voip call has been declined */ + | 'voip-call-declined' + /** Sent when a voip call is put on hold */ + | 'voip-call-on-hold' + /** Sent when a voip call is unhold */ + | 'voip-call-unhold' + /** Sent when a voip call is paused */ + | 'voip-call-ended' + /** Sent when a voip call is over, contains the duration of the call */ + | 'voip-call-duration' + /** Sent when a voip call is ended */ + | 'voip-call-wrapup' + /** Sent when a voip call is ended unexpectedly */ + | 'voip-call-ended-unexpectedly' + /** Sent when a user is removed from main room of a team */ + | 'removed-user-from-team' + /** Sent when a user is added to a team */ + | 'added-user-to-team' + /** Sent when a user leaves a team */ + | 'ult' + /** Sent when a user converts a room into a team */ + | 'user-converted-to-team' + /** Sent when a user converts a room into a channel */ + | 'user-converted-to-channel' + /** Sent when a user removes a room from a team */ + | 'user-removed-room-from-team' + /** Sent when a user deletes a room inside a team */ + | 'user-deleted-room-from-team' + /** Sent when a user adds a room to a team */ + | 'user-added-room-to-team' + /** Sent when a user joins a team */ + | 'ujt' + /** Sent when the navigation history of a livechat conversation is requested */ + | 'livechat_navigation_history' + /** Sent when the conversation is transferred */ + | 'livechat_transfer_history' + /** Sent when the transcript is requested */ + | 'livechat_transcript_history' + /** Sent when a video call is requested */ + | 'livechat_video_call' + /** Sent when there is a history fallback */ + | 'livechat_transfer_history_fallback' + /** Sent when a livechat conversation is closed */ + | 'livechat-close' + /** Sent when a livechat conversation is started */ + | 'livechat_webrtc_video_call' + /** Sent when a livechat conversation is started */ + | 'livechat-started' + /** Sent when the priority of omnichannel is changed */ + | 'omnichannel_priority_change_history' + /** Sent when the sla of omnichannel is changed */ + | 'omnichannel_sla_change_history' + /** Sent when a chat is placed on hold */ + | 'omnichannel_placed_chat_on_hold' + /** Sent when a chat is resumed */ + | 'omnichannel_on_hold_chat_resumed' + | 'otr' + | 'otr-ack' + | 'user_joined_otr' + | 'user_requested_otr_key_refresh' + | 'user_key_refreshed_successfully' + /** Sent when the message came through e2e */ + | 'e2e' + /** Sent when a user has joined a room */ + | 'uj' + /** Sent when a user has left a room */ + | 'ul' + /** Sent when a user was removed */ + | 'ru' + /** Sent when a user was added */ + | 'au' + /** Sent when system messages were muted */ + | 'mute_unmute' + /** Sent when a room name was changed */ + | 'r' + /** Sent when a user joined a conversation */ + | 'ut' + /** Welcome message */ + | 'wm' + /** Sent when a message was removed */ + | 'rm' + /** Sent when the role of a subscription has added */ + | 'subscription-role-added' + /** Sent when the role of a subscription has removed */ + | 'subscription-role-removed' + /** Sent when the room was archived */ + | 'room-archived' + /** Sent when the room was unarchived */ + | 'room-unarchived' + /** Sent when the privacy of the room has changed */ + | 'room_changed_privacy' + /** Sent when the description of a room was changed */ + | 'room_changed_description' + /** Sent when the announcement of a room was changed */ + | 'room_changed_announcement' + /** Sent when the avatar of a room was changed */ + | 'room_changed_avatar' + /** Sent when the topic of a room was changed */ + | 'room_changed_topic' + /** Sent when e2e was enabled in a room */ + | 'room_e2e_enabled' + /** Sent when e2e was disabled in a room */ + | 'room_e2e_disabled' + /** Sent when a user was muted */ + | 'user-muted' + /** Sent when a user was unmuted */ + | 'user-unmuted' + /** Sent when a room is not readonly anymore */ + | 'room-removed-read-only' + /** Sent when a room is set to readonly */ + | 'room-set-read-only' + /** Sent when a room is set to allow reacting */ + | 'room-allowed-reacting' + /** Sent when a room is set to disallow reacting */ + | 'room-disallowed-reacting' + /** Sent on several instances when, such as when a livechat room is started */ + | 'command' + /** Sent when a message is the start of a videoconf */ + | 'videoconf' + /** Sent when a message was pinned */ + | 'message_pinned' + /** Sent when an e2e message was pinned */ + | 'message_pinned_e2e' + /** Sent when the room has a new moderator */ + | 'new-moderator' + /** Sent when a moderator was removed */ + | 'moderator-removed' + /** Sent when a room has a new owner */ + | 'new-owner' + /** Sent when an owner was removed */ + | 'owner-removed' + /** Sent when a room has a new leader */ + | 'new-leader' + /** Sent when a leader was removed */ + | 'leader-removed' + /** Sent when a user was added to a room */ + | 'discussion-created'; diff --git a/packages/apps-engine/src/definition/messages/index.ts b/packages/apps-engine/src/definition/messages/index.ts index b718f8094127d..78f8a6ebe2153 100644 --- a/packages/apps-engine/src/definition/messages/index.ts +++ b/packages/apps-engine/src/definition/messages/index.ts @@ -29,10 +29,10 @@ import { IPreMessageSentPrevent } from './IPreMessageSentPrevent'; import { IPreMessageUpdatedExtend } from './IPreMessageUpdatedExtend'; import { IPreMessageUpdatedModify } from './IPreMessageUpdatedModify'; import { IPreMessageUpdatedPrevent } from './IPreMessageUpdatedPrevent'; -import { ISystemMessage } from './ISystemMessage'; import { MessageActionButtonsAlignment } from './MessageActionButtonsAlignment'; import { MessageActionType } from './MessageActionType'; import { MessageProcessingType } from './MessageProcessingType'; +import { MessageType } from './MessageType'; export { IMessage, @@ -70,6 +70,6 @@ export { MessageProcessingType, IMessageDeleteContext, Reaction, - ISystemMessage, + MessageType, IPostSystemMessageSent, }; diff --git a/packages/apps-engine/src/server/bridges/IListenerBridge.ts b/packages/apps-engine/src/server/bridges/IListenerBridge.ts index 7b0e17efe4ed1..c29674ae257e0 100644 --- a/packages/apps-engine/src/server/bridges/IListenerBridge.ts +++ b/packages/apps-engine/src/server/bridges/IListenerBridge.ts @@ -4,7 +4,6 @@ import type { IRoom } from '../../definition/rooms'; import type { UIKitIncomingInteraction } from '../../definition/uikit'; export interface IListenerBridge { - systemMessageEvent(int: AppInterface, message: IMessage): Promise; messageEvent(int: AppInterface, message: IMessage): Promise; roomEvent(int: AppInterface, room: IRoom): Promise; uiKitInteractionEvent(int: AppInterface, action: UIKitIncomingInteraction): Promise; diff --git a/packages/apps-engine/src/server/managers/AppListenerManager.ts b/packages/apps-engine/src/server/managers/AppListenerManager.ts index 3c7e9d2f7e531..2d6cd25e62ad6 100644 --- a/packages/apps-engine/src/server/managers/AppListenerManager.ts +++ b/packages/apps-engine/src/server/managers/AppListenerManager.ts @@ -11,7 +11,6 @@ import type { IMessageReactionContext, IMessageReportContext, IMessageStarContext, - ISystemMessage, } from '../../definition/messages'; import { AppInterface, AppMethod } from '../../definition/metadata'; import type { IRoom, IRoomUserJoinedContext, IRoomUserLeaveContext } from '../../definition/rooms'; @@ -46,7 +45,7 @@ interface IListenerExecutor { result: IMessage; }; [AppInterface.IPostSystemMessageSent]: { - args: [ISystemMessage]; + args: [IMessage]; result: void; }; [AppInterface.IPostMessageSent]: { @@ -344,7 +343,7 @@ export class AppListenerManager { this.executePostMessageSent(data as IMessage); return; case AppInterface.IPostSystemMessageSent: - this.executePostSystemMessageSent(data as ISystemMessage); + this.executePostSystemMessageSent(data as IMessage); return; case AppInterface.IPreMessageDeletePrevent: return this.executePreMessageDeletePrevent(data as IMessage); @@ -568,7 +567,7 @@ export class AppListenerManager { } } - private async executePostSystemMessageSent(data: ISystemMessage): Promise { + private async executePostSystemMessageSent(data: IMessage): Promise { for (const appId of this.listeners.get(AppInterface.IPostSystemMessageSent)) { const app = this.manager.getOneById(appId); await app.call(AppMethod.EXECUTEPOSTSYSTEMMESSAGESENT, data); diff --git a/packages/apps/src/bridges/IListenerBridge.ts b/packages/apps/src/bridges/IListenerBridge.ts index 569cb84d21a76..313ecf90f5b09 100644 --- a/packages/apps/src/bridges/IListenerBridge.ts +++ b/packages/apps/src/bridges/IListenerBridge.ts @@ -19,9 +19,7 @@ declare module '@rocket.chat/apps-engine/server/bridges' { int: 'IPreMessageSentExtend' | 'IPreMessageSentModify' | 'IPreMessageUpdatedExtend' | 'IPreMessageUpdatedModify', message: IMessage, ): Promise; - messageEvent(int: 'IPostMessageSent' | 'IPostMessageUpdated', message: IMessage): Promise; - - systemMessageEvent(int: 'IPostSystemMessageSent', message: IMessage): Promise; + messageEvent(int: 'IPostMessageSent' | 'IPostMessageUpdated' | 'IPostSystemMessageSent', message: IMessage): Promise; roomEvent(int: 'IPreRoomUserJoined' | 'IPostRoomUserJoined', room: IRoom, joiningUser: IUser, invitingUser?: IUser): Promise; roomEvent(int: 'IPreRoomUserLeave' | 'IPostRoomUserLeave', room: IRoom, leavingUser: IUser): Promise; From c84283906157a5ad4eb05bfe98b045a95d28b0cb Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 12 Feb 2025 11:14:03 -0300 Subject: [PATCH 4/8] fix: wrong test --- apps/meteor/tests/unit/app/apps/server/messages.tests.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/meteor/tests/unit/app/apps/server/messages.tests.js b/apps/meteor/tests/unit/app/apps/server/messages.tests.js index 04866f6440b6d..e7f468e3d4756 100644 --- a/apps/meteor/tests/unit/app/apps/server/messages.tests.js +++ b/apps/meteor/tests/unit/app/apps/server/messages.tests.js @@ -101,11 +101,6 @@ describe('The AppMessagesConverter instance', () => { }); }); - it('should add an `_unmappedProperties_` field to the converted message which contains the `t` property of the message', async () => { - const appMessage = await messagesConverter.convertMessage(messagesMock.findOneById('SimpleMessageMock')); - - expect(appMessage).to.have.property('_unmappedProperties_').which.has.property('t', 'uj'); - }); it("should return basic sender info when it's not a Rocket.Chat user (e.g. Livechat Guest)", async () => { const appMessage = await messagesConverter.convertMessage(messagesMock.findOneById('LivechatGuestMessageMock')); From 8c3243090a0f86777467767c0ed5eac6b5ec91e1 Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 12 Feb 2025 11:17:45 -0300 Subject: [PATCH 5/8] chore: empty line --- apps/meteor/tests/unit/app/apps/server/messages.tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/tests/unit/app/apps/server/messages.tests.js b/apps/meteor/tests/unit/app/apps/server/messages.tests.js index e7f468e3d4756..195f02bbe8930 100644 --- a/apps/meteor/tests/unit/app/apps/server/messages.tests.js +++ b/apps/meteor/tests/unit/app/apps/server/messages.tests.js @@ -101,7 +101,6 @@ describe('The AppMessagesConverter instance', () => { }); }); - it("should return basic sender info when it's not a Rocket.Chat user (e.g. Livechat Guest)", async () => { const appMessage = await messagesConverter.convertMessage(messagesMock.findOneById('LivechatGuestMessageMock')); From 1a252d622278ac0e0fa25c65ee5a9af40f8f45f9 Mon Sep 17 00:00:00 2001 From: Gustavo Reis Bauer Date: Wed, 12 Feb 2025 14:22:00 -0300 Subject: [PATCH 6/8] Create happy-nails-fry.md --- .changeset/happy-nails-fry.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/happy-nails-fry.md diff --git a/.changeset/happy-nails-fry.md b/.changeset/happy-nails-fry.md new file mode 100644 index 0000000000000..19c52d378315b --- /dev/null +++ b/.changeset/happy-nails-fry.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/apps-engine": minor +"@rocket.chat/apps": minor +--- + +Adds a new IPostSystemMessageSent event, that is triggered whenever a new System Message is sent From 423c7f877d9e9884b5a3f168acf804b0eba32bab Mon Sep 17 00:00:00 2001 From: Gustavo Reis Bauer Date: Fri, 14 Feb 2025 10:14:46 -0300 Subject: [PATCH 7/8] Update .changeset/happy-nails-fry.md Co-authored-by: Marcos Spessatto Defendi --- .changeset/happy-nails-fry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/happy-nails-fry.md b/.changeset/happy-nails-fry.md index 19c52d378315b..ebc17ea2dfe8b 100644 --- a/.changeset/happy-nails-fry.md +++ b/.changeset/happy-nails-fry.md @@ -1,5 +1,5 @@ --- -"@rocket.chat/meteor": patch +"@rocket.chat/meteor": minor "@rocket.chat/apps-engine": minor "@rocket.chat/apps": minor --- From 26d5711daff81892d1fb453f1be6812532c46bb5 Mon Sep 17 00:00:00 2001 From: gustrb Date: Fri, 14 Feb 2025 10:35:34 -0300 Subject: [PATCH 8/8] chore: trigger system messages event in sendMessage --- apps/meteor/app/lib/server/functions/sendMessage.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/sendMessage.ts b/apps/meteor/app/lib/server/functions/sendMessage.ts index 828de8451a217..599e75987eda4 100644 --- a/apps/meteor/app/lib/server/functions/sendMessage.ts +++ b/apps/meteor/app/lib/server/functions/sendMessage.ts @@ -284,9 +284,9 @@ export const sendMessage = async function (user: any, message: any, room: any, u } if (Apps.self?.isLoaded()) { - // This returns a promise, but it won't mutate anything about the message - // so, we don't really care if it is successful or fails - void Apps.getBridges()?.getListenerBridge().messageEvent('IPostMessageSent', message); + // If the message has a type (system message), we should notify the listener about it + const messageEvent = message.t ? 'IPostSystemMessageSent' : 'IPostMessageSent'; + void Apps.getBridges()?.getListenerBridge().messageEvent(messageEvent, message); } // TODO: is there an opportunity to send returned data to notifyOnMessageChange?