Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/meteor/ee/server/hooks/federation/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import { FederationMatrix } from '@rocket.chat/core-services';
import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
import { Messages } from '@rocket.chat/models';

import { callbacks } from '../../../../lib/callbacks';
import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback';
import { afterRemoveFromRoomCallback } from '../../../../lib/callbacks/afterRemoveFromRoomCallback';

callbacks.add(
'afterDeleteMessage',
async (message: IMessage) => {
if (!message.federation?.eventId) {
return;
}
const isEchoMessage = !(await Messages.findOneByFederationId(message.federation?.eventId));
if (isEchoMessage) {
return;
}
await FederationMatrix.deleteMessage(message);
},
callbacks.priority.MEDIUM,
'native-federation-after-delete-message',
);

callbacks.add(
'native-federation.onAddUsersToRoom',
async ({ invitees, inviter }, room) => FederationMatrix.inviteUsersToRoom(room, invitees, inviter),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { isMessageFromMatrixFederation, isRoomFederated } from '@rocket.chat/core-typings';
import type { AtLeast, IMessage, IRoom } from '@rocket.chat/core-typings';

import { isFederationEnabled, isFederationReady } from '../../federation/utils';
import { getFederationVersion, isFederationEnabled, isFederationReady } from '../../federation/utils';

export class FederationActions {
public static shouldPerformAction(message: IMessage, room: AtLeast<IRoom, 'federated'>): boolean {
if (isMessageFromMatrixFederation(message) || isRoomFederated(room)) {
return isFederationEnabled() && isFederationReady();
return getFederationVersion() === 'native' || (isFederationEnabled() && isFederationReady());
}

return true;
Expand Down
35 changes: 34 additions & 1 deletion ee/packages/federation-matrix/src/FederationMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'reflect-metadata';
import { ConfigService, createFederationContainer, getAllServices } from '@hs/federation-sdk';
import type { HomeserverEventSignatures, HomeserverServices, FederationContainerOptions } from '@hs/federation-sdk';
import { type IFederationMatrixService, Room, ServiceClass, Settings } from '@rocket.chat/core-services';
import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
import { isDeletedMessage, isMessageFromMatrixFederation, type IMessage, type IRoom, type IUser } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';
import { Router } from '@rocket.chat/http-router';
import { Logger } from '@rocket.chat/logger';
Expand Down Expand Up @@ -195,6 +195,39 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
}
}

async deleteMessage(message: IMessage): Promise<void> {
try {
if (!isMessageFromMatrixFederation(message) || isDeletedMessage(message)) {
return;
}
const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(message.rid);
if (!matrixRoomId) {
throw new Error(`No Matrix room mapping found for room ${message.rid}`);
}
const matrixDomain = await this.getMatrixDomain();
const matrixUserId = `@${message.u.username}:${matrixDomain}`;
const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(message.u._id);
if (!existingMatrixUserId) {
await MatrixBridgedUser.createOrUpdateByLocalId(message.u._id, matrixUserId, true, matrixDomain);
}

if (!this.homeserverServices) {
this.logger.warn('Homeserver services not available, skipping message redaction');
return;
}
const matrixEventId = message.federation?.eventId;
if (!matrixEventId) {
throw new Error(`No Matrix event ID mapping found for message ${message._id}`);
}
const eventId = await this.homeserverServices.message.redactMessage(matrixRoomId, matrixEventId, matrixUserId);

this.logger.debug('Message Redaction sent to Matrix successfully:', eventId);
} catch (error) {
this.logger.error('Failed to send redaction to Matrix:', error);
throw error;
}
}

async inviteUsersToRoom(room: IRoom, usersUserName: string[], inviter: IUser): Promise<void> {
try {
const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(room._id);
Expand Down
36 changes: 34 additions & 2 deletions ee/packages/federation-matrix/src/events/message.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { HomeserverEventSignatures } from '@hs/federation-sdk';
import { Message } from '@rocket.chat/core-services';
import { FederationMatrix, Message } from '@rocket.chat/core-services';
import { UserStatus } from '@rocket.chat/core-typings';
import type { IUser } from '@rocket.chat/core-typings';
import type { Emitter } from '@rocket.chat/emitter';
import { Logger } from '@rocket.chat/logger';
import { Users, MatrixBridgedUser, MatrixBridgedRoom, Rooms, Subscriptions } from '@rocket.chat/models';
import { Users, MatrixBridgedUser, MatrixBridgedRoom, Rooms, Subscriptions, Messages } from '@rocket.chat/models';

const logger = new Logger('federation-matrix:message');

Expand Down Expand Up @@ -126,4 +126,36 @@ export function message(emitter: Emitter<HomeserverEventSignatures>) {
logger.error('Error processing Matrix message:', error);
}
});

emitter.on('homeserver.matrix.redaction', async (data) => {
try {
const redactedEventId = data.redacts;
if (!redactedEventId) {
logger.debug('No redacts field in redaction event');
return;
}

const messageEvent = await FederationMatrix.getEventById(redactedEventId);
if (!messageEvent || messageEvent.type !== 'm.room.message') {
logger.debug(`Event ${redactedEventId} is not a message event`);
return;
}

const rcMessage = await Messages.findOneByFederationId(data.redacts);
if (!rcMessage) {
logger.debug(`No RC message found for event ${data.redacts}`);
return;
}

const user = await Users.findOneByUsername(data.sender);
if (!user) {
logger.debug(`User not found: ${data.sender}`);
return;
}

await Message.deleteMessage(user, rcMessage);
} catch (error) {
logger.error('Failed to process Matrix removal redaction:', error);
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface IFederationMatrixService {
};
createRoom(room: IRoom, owner: IUser, members: string[]): Promise<void>;
sendMessage(message: IMessage, room: IRoom, user: IUser): Promise<void>;
deleteMessage(message: IMessage): Promise<void>;
sendReaction(messageId: string, reaction: string, user: IUser): Promise<void>;
removeReaction(messageId: string, reaction: string, user: IUser, oldMessage: IMessage): Promise<void>;
getEventById(eventId: string): Promise<any | null>;
Expand Down
9 changes: 8 additions & 1 deletion packages/core-typings/src/IMessage/IMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,14 @@ export const isSystemMessage = (message: IMessage): message is ISystemMessage =>
message.t !== undefined && MessageTypes.includes(message.t);

export const isDeletedMessage = (message: IMessage): message is IEditedMessage => isEditedMessage(message) && message.t === 'rm';
export const isMessageFromMatrixFederation = (message: IMessage): boolean =>

export interface IFederatedMessage extends IMessage {
federation: {
eventId: string;
};
}

export const isMessageFromMatrixFederation = (message: IMessage): message is IFederatedMessage =>
'federation' in message && Boolean(message.federation?.eventId);

export interface ITranslatedMessage extends IMessage {
Expand Down
Loading