apps/meteor/server/services/federation/infrastructure/rocket-chat/hooks/index.ts
import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
import { isMessageFromMatrixFederation, isRoomFederated, isEditedMessage } from '@rocket.chat/core-typings';
import { callbacks } from '../../../../../../lib/callbacks';
import { afterLeaveRoomCallback } from '../../../../../../lib/callbacks/afterLeaveRoomCallback';
import { afterRemoveFromRoomCallback } from '../../../../../../lib/callbacks/afterRemoveFromRoomCallback';
import type { FederationRoomServiceSender } from '../../../application/room/sender/RoomServiceSender';
import { isFederationEnabled, throwIfFederationNotEnabledOrNotReady, throwIfFederationNotReady } from '../../../utils';
export class FederationHooks {
public static afterUserLeaveRoom(callback: (user: IUser, room: IRoom) => Promise<void>): void {
afterLeaveRoomCallback.add(
async (user: IUser, room?: IRoom): Promise<void> => {
if (!room || !isRoomFederated(room) || !user) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(user, room);
},
callbacks.priority.HIGH,
'federation-v2-after-leave-room',
);
}
public static onUserRemovedFromRoom(callback: (removedUser: IUser, room: IRoom, userWhoRemoved: IUser) => Promise<void>): void {
afterRemoveFromRoomCallback.add(
async (params, room): Promise<void> => {
if (!room || !isRoomFederated(room) || !params || !params.removedUser || !params.userWhoRemoved) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(params.removedUser, room, params.userWhoRemoved);
},
callbacks.priority.HIGH,
'federation-v2-after-remove-from-room',
);
}
public static canAddFederatedUserToNonFederatedRoom(callback: (user: IUser | string, room: IRoom) => Promise<void>): void {
callbacks.add(
'federation.beforeAddUserToARoom',
async (params: { user: IUser | string; inviter?: IUser }, room: IRoom): Promise<void> => {
if (!params?.user || !room || !isFederationEnabled()) {
return;
}
await callback(params.user, room);
},
callbacks.priority.HIGH,
'federation-v2-can-add-federated-user-to-non-federated-room',
);
}
public static canAddFederatedUserToFederatedRoom(callback: (user: IUser | string, inviter: IUser, room: IRoom) => Promise<void>): void {
callbacks.add(
'federation.beforeAddUserToARoom',
async (params: { user: IUser | string; inviter: IUser }, room: IRoom): Promise<void> => {
if (!params?.user || !params.inviter || !room || !isFederationEnabled()) {
return;
}
await callback(params.user, params.inviter, room);
},
callbacks.priority.HIGH,
'federation-v2-can-add-federated-user-to-federated-room',
);
}
public static canCreateDirectMessageFromUI(callback: (members: IUser[]) => Promise<void>): void {
callbacks.add(
'federation.beforeCreateDirectMessage',
async (members: IUser[]): Promise<void> => {
if (!members) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(members);
},
callbacks.priority.HIGH,
'federation-v2-can-create-direct-message-from-ui-ce',
);
}
public static afterMessageReacted(callback: (message: IMessage, user: IUser, reaction: string) => Promise<void>): void {
callbacks.add(
'afterSetReaction',
async (message: IMessage, params: { user: IUser; reaction: string }): Promise<void> => {
if (!message || !isMessageFromMatrixFederation(message) || !params || !params.user || !params.reaction) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(message, params.user, params.reaction);
},
callbacks.priority.HIGH,
'federation-v2-after-message-reacted',
);
}
public static afterMessageunReacted(callback: (message: IMessage, user: IUser, reaction: string) => Promise<void>): void {
callbacks.add(
'afterUnsetReaction',
async (message: IMessage, params: { user: IUser; reaction: string; oldMessage: IMessage }): Promise<void> => {
if (!message || !isMessageFromMatrixFederation(message) || !params || !params.user || !params.reaction || !params.oldMessage) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(params.oldMessage, params.user, params.reaction);
},
callbacks.priority.HIGH,
'federation-v2-after-message-unreacted',
);
}
public static afterMessageDeleted(callback: (message: IMessage, roomId: IRoom['_id']) => Promise<void>): void {
callbacks.add(
'afterDeleteMessage',
async (message: IMessage, room: IRoom): Promise<void> => {
if (!room || !message || !isRoomFederated(room) || !isMessageFromMatrixFederation(message)) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(message, room._id);
},
callbacks.priority.HIGH,
'federation-v2-after-room-message-deleted',
);
}
public static afterMessageUpdated(callback: (message: IMessage, roomId: IRoom['_id'], userId: string) => Promise<void>): void {
callbacks.add(
'afterSaveMessage',
async (message: IMessage, { room }): Promise<IMessage> => {
if (!room || !isRoomFederated(room) || !message || !isMessageFromMatrixFederation(message)) {
return message;
}
throwIfFederationNotEnabledOrNotReady();
if (!isEditedMessage(message)) {
return message;
}
await callback(message, room._id, message.editedBy._id);
return message;
},
callbacks.priority.HIGH,
'federation-v2-after-room-message-updated',
);
}
public static afterMessageSent(callback: (message: IMessage, roomId: IRoom['_id'], userId: string) => Promise<void>): void {
callbacks.add(
'afterSaveMessage',
async (message: IMessage, { room }): Promise<IMessage> => {
if (!room || !isRoomFederated(room) || !message) {
return message;
}
throwIfFederationNotEnabledOrNotReady();
if (isEditedMessage(message)) {
return message;
}
await callback(message, room._id, message.u?._id);
return message;
},
callbacks.priority.HIGH,
'federation-v2-after-room-message-sent',
);
}
public static async afterRoomRoleChanged(federationRoomService: FederationRoomServiceSender, data?: Record<string, any>) {
if (!data) {
return;
}
if (!isFederationEnabled()) {
return;
}
throwIfFederationNotReady();
const {
_id: role,
type: action,
scope: internalRoomId,
u: { _id: internalTargetUserId = undefined } = {},
givenByUserId: internalUserId,
} = data;
const roleEventsInterestedIn = ['moderator', 'owner'];
if (!roleEventsInterestedIn.includes(role)) {
return;
}
const handlers: Record<string, (internalUserId: string, internalTargetUserId: string, internalRoomId: string) => Promise<void>> = {
'owner-added': (internalUserId: string, internalTargetUserId: string, internalRoomId: string): Promise<void> =>
federationRoomService.onRoomOwnerAdded(internalUserId, internalTargetUserId, internalRoomId),
'owner-removed': (internalUserId: string, internalTargetUserId: string, internalRoomId: string): Promise<void> =>
federationRoomService.onRoomOwnerRemoved(internalUserId, internalTargetUserId, internalRoomId),
'moderator-added': (internalUserId: string, internalTargetUserId: string, internalRoomId: string): Promise<void> =>
federationRoomService.onRoomModeratorAdded(internalUserId, internalTargetUserId, internalRoomId),
'moderator-removed': (internalUserId: string, internalTargetUserId: string, internalRoomId: string): Promise<void> =>
federationRoomService.onRoomModeratorRemoved(internalUserId, internalTargetUserId, internalRoomId),
};
if (!handlers[`${role}-${action}`]) {
return;
}
await handlers[`${role}-${action}`](internalUserId, internalTargetUserId, internalRoomId);
}
public static afterRoomNameChanged(callback: (roomId: string, changedRoomName: string) => Promise<void>): void {
callbacks.add(
'afterRoomNameChange',
async (params: Record<string, any>): Promise<void> => {
if (!params?.rid || !params.name) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(params.rid, params.name);
},
callbacks.priority.HIGH,
'federation-v2-after-room-name-changed',
);
}
public static afterRoomTopicChanged(callback: (roomId: string, changedRoomTopic: string) => Promise<void>): void {
callbacks.add(
'afterRoomTopicChange',
async (params: Record<string, any>): Promise<void> => {
if (!params?.rid || !params.topic) {
return;
}
throwIfFederationNotEnabledOrNotReady();
await callback(params.rid, params.topic);
},
callbacks.priority.HIGH,
'federation-v2-after-room-topic-changed',
);
}
public static removeCEValidation(): void {
callbacks.remove('federation.beforeAddUserToARoom', 'federation-v2-can-add-federated-user-to-federated-room');
callbacks.remove('federation.beforeCreateDirectMessage', 'federation-v2-can-create-direct-message-from-ui-ce');
}
public static removeAllListeners(): void {
afterLeaveRoomCallback.remove('federation-v2-after-leave-room');
afterRemoveFromRoomCallback.remove('federation-v2-after-remove-from-room');
callbacks.remove('federation.beforeAddUserToARoom', 'federation-v2-can-add-federated-user-to-non-federated-room');
callbacks.remove('federation.beforeAddUserToARoom', 'federation-v2-can-add-federated-user-to-federated-room');
callbacks.remove('federation.beforeCreateDirectMessage', 'federation-v2-can-create-direct-message-from-ui-ce');
callbacks.remove('afterSetReaction', 'federation-v2-after-message-reacted');
callbacks.remove('afterUnsetReaction', 'federation-v2-after-message-unreacted');
callbacks.remove('afterDeleteMessage', 'federation-v2-after-room-message-deleted');
callbacks.remove('afterSaveMessage', 'federation-v2-after-room-message-updated');
callbacks.remove('afterSaveMessage', 'federation-v2-after-room-message-sent');
callbacks.remove('afterSaveMessage', 'federation-v2-after-room-message-sent');
callbacks.remove('afterRoomNameChange', 'federation-v2-after-room-name-changed');
callbacks.remove('afterRoomTopicChange', 'federation-v2-after-room-topic-changed');
}
}