Skip to content
Merged
7 changes: 7 additions & 0 deletions .changeset/happy-nails-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/apps-engine": minor
"@rocket.chat/apps": minor
---

Adds a new IPostSystemMessageSent event, that is triggered whenever a new System Message is sent
1 change: 1 addition & 0 deletions apps/meteor/app/apps/server/bridges/listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/app/apps/server/converters/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class AppMessagesConverter {
attachments: getAttachments,
sender: 'u',
threadMsgCount: 'tcount',
type: 't',
};

return transformMappedData(message, map);
Expand Down Expand Up @@ -91,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;
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/app/lib/server/functions/sendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
5 changes: 5 additions & 0 deletions apps/meteor/server/services/messages/service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { 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';
Expand Down Expand Up @@ -152,6 +153,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().messageEvent('IPostSystemMessageSent', createdMessage);
}

void notifyOnMessageChange({ id: createdMessage._id, data: createdMessage });
void notifyOnRoomChangedById(rid);

Expand Down
6 changes: 0 additions & 6 deletions apps/meteor/tests/unit/app/apps/server/messages.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +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'));

Expand Down
2 changes: 2 additions & 0 deletions packages/apps-engine/src/definition/messages/IMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,4 +32,5 @@ export interface IMessage {
pinned?: boolean;
pinnedAt?: Date;
pinnedBy?: IUserLookup;
type?: MessageType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { IHttp, IModify, IPersistence, IRead } from '../accessors';
import type { IMessage } from './IMessage';

/**
* 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: IMessage, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise<void>;
}
143 changes: 143 additions & 0 deletions packages/apps-engine/src/definition/messages/MessageType.ts
Original file line number Diff line number Diff line change
@@ -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';
4 changes: 4 additions & 0 deletions packages/apps-engine/src/definition/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -31,6 +32,7 @@ import { IPreMessageUpdatedPrevent } from './IPreMessageUpdatedPrevent';
import { MessageActionButtonsAlignment } from './MessageActionButtonsAlignment';
import { MessageActionType } from './MessageActionType';
import { MessageProcessingType } from './MessageProcessingType';
import { MessageType } from './MessageType';

export {
IMessage,
Expand Down Expand Up @@ -68,4 +70,6 @@ export {
MessageProcessingType,
IMessageDeleteContext,
Reaction,
MessageType,
IPostSystemMessageSent,
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum AppInterface {
IPreMessageSentExtend = 'IPreMessageSentExtend',
IPreMessageSentModify = 'IPreMessageSentModify',
IPostMessageSent = 'IPostMessageSent',
IPostSystemMessageSent = 'IPostSystemMessageSent',
IPreMessageDeletePrevent = 'IPreMessageDeletePrevent',
IPostMessageDeleted = 'IPostMessageDeleted',
IPreMessageUpdatedPrevent = 'IPreMessageUpdatedPrevent',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum AppMethod {
EXECUTEPREMESSAGESENTMODIFY = 'executePreMessageSentModify',
CHECKPOSTMESSAGESENT = 'checkPostMessageSent',
EXECUTEPOSTMESSAGESENT = 'executePostMessageSent',
EXECUTEPOSTSYSTEMMESSAGESENT = 'executePostSystemMessageSent',

EXECUTEPOSTMESSAGESENTTOBOT = 'executePostMessageSentToBot',

Expand Down
14 changes: 14 additions & 0 deletions packages/apps-engine/src/server/managers/AppListenerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ interface IListenerExecutor {
args: [IMessage];
result: IMessage;
};
[AppInterface.IPostSystemMessageSent]: {
args: [IMessage];
result: void;
};
[AppInterface.IPostMessageSent]: {
args: [IMessage];
result: void;
Expand Down Expand Up @@ -338,6 +342,9 @@ export class AppListenerManager {
case AppInterface.IPostMessageSent:
this.executePostMessageSent(data as IMessage);
return;
case AppInterface.IPostSystemMessageSent:
this.executePostSystemMessageSent(data as IMessage);
return;
case AppInterface.IPreMessageDeletePrevent:
return this.executePreMessageDeletePrevent(data as IMessage);
case AppInterface.IPostMessageDeleted:
Expand Down Expand Up @@ -560,6 +567,13 @@ export class AppListenerManager {
}
}

private async executePostSystemMessageSent(data: IMessage): Promise<void> {
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<boolean> {
let prevented = false;

Expand Down
2 changes: 1 addition & 1 deletion packages/apps/src/bridges/IListenerBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ declare module '@rocket.chat/apps-engine/server/bridges' {
int: 'IPreMessageSentExtend' | 'IPreMessageSentModify' | 'IPreMessageUpdatedExtend' | 'IPreMessageUpdatedModify',
message: IMessage,
): Promise<IMessage>;
messageEvent(int: 'IPostMessageSent' | 'IPostMessageUpdated', message: IMessage): Promise<void>;
messageEvent(int: 'IPostMessageSent' | 'IPostMessageUpdated' | 'IPostSystemMessageSent', message: IMessage): Promise<void>;

roomEvent(int: 'IPreRoomUserJoined' | 'IPostRoomUserJoined', room: IRoom, joiningUser: IUser, invitingUser?: IUser): Promise<void>;
roomEvent(int: 'IPreRoomUserLeave' | 'IPostRoomUserLeave', room: IRoom, leavingUser: IUser): Promise<void>;
Expand Down
Loading