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
2 changes: 1 addition & 1 deletion ee/packages/federation-matrix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@rocket.chat/core-services": "workspace:^",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/emitter": "^0.31.25",
"@rocket.chat/federation-sdk": "0.1.25",
"@rocket.chat/federation-sdk": "0.1.26",
"@rocket.chat/http-router": "workspace:^",
"@rocket.chat/license": "workspace:^",
"@rocket.chat/models": "workspace:^",
Expand Down
93 changes: 32 additions & 61 deletions ee/packages/federation-matrix/src/FederationMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
return null;
}

const replyToMessage = await this.handleThreadedMessage(message, matrixRoomId, matrixUserId, matrixDomain);
const quoteMessage = await this.handleQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain);
try {
let lastEventId: { eventId: string } | null = null;

Expand All @@ -388,6 +390,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
roomIdSchema.parse(matrixRoomId),
fileContent,
userIdSchema.parse(matrixUserId),
replyToMessage || quoteMessage,
);

return lastEventId;
Expand All @@ -412,94 +415,62 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
homeServerDomain: matrixDomain,
});

if (message.tmid) {
return this.handleThreadedMessage(message, matrixRoomId, matrixUserId, matrixDomain, parsedMessage);
}

if (message.attachments?.some((attachment) => isQuoteAttachment(attachment) && Boolean(attachment.message_link))) {
return this.handleQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain);
}
const replyToMessage = await this.handleThreadedMessage(message, matrixRoomId, matrixUserId, matrixDomain);
const quoteMessage = await this.handleQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain);

return this.homeserverServices.message.sendMessage(
roomIdSchema.parse(matrixRoomId),
message.msg,
parsedMessage,
userIdSchema.parse(matrixUserId),
replyToMessage || quoteMessage,
);
}

private async handleThreadedMessage(
message: IMessage,
matrixRoomId: string,
matrixUserId: string,
matrixDomain: string,
parsedMessage: string,
): Promise<{ eventId: string } | null> {
private async handleThreadedMessage(message: IMessage, matrixRoomId: string, matrixUserId: string, matrixDomain: string) {
if (!message.tmid) {
throw new Error('Thread message ID not found');
return;
}

const threadRootMessage = await Messages.findOneById(message.tmid);
const threadRootEventId = threadRootMessage?.federation?.eventId;

if (!threadRootEventId) {
this.logger.warn('Thread root event ID not found, sending as regular message');
if (message.attachments?.some((attachment) => isQuoteAttachment(attachment) && Boolean(attachment.message_link))) {
return this.handleQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain);
}
return this.homeserverServices.message.sendMessage(
roomIdSchema.parse(matrixRoomId),
message.msg,
parsedMessage,
userIdSchema.parse(matrixUserId),
);
throw new Error('Thread root event ID not found');
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should be more descriptive and include context about which thread message failed to find its root event ID.

Suggested change
throw new Error('Thread root event ID not found');
throw new Error(`Thread root event ID not found for thread message ID: ${message.tmid}, message ID: ${message._id}, room ID: ${matrixRoomId}`);

Copilot uses AI. Check for mistakes.
}

const latestThreadMessage = await Messages.findLatestFederationThreadMessageByTmid(message.tmid, message._id);
const latestThreadEventId = latestThreadMessage?.federation?.eventId;
const quoteMessageEventId = message.attachments?.some((attachment) => isQuoteAttachment(attachment) && Boolean(attachment.message_link))
? (await this.getQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain))?.eventToReplyTo
: undefined;

if (message.attachments?.some((attachment) => isQuoteAttachment(attachment) && Boolean(attachment.message_link))) {
const quoteMessage = await this.getQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain);
if (!quoteMessage) {
throw new Error('Failed to retrieve quote message');
}
return this.homeserverServices.message.sendReplyToInsideThreadMessage(
roomIdSchema.parse(matrixRoomId),
quoteMessage.rawMessage,
quoteMessage.formattedMessage,
userIdSchema.parse(matrixUserId),
eventIdSchema.parse(threadRootEventId),
eventIdSchema.parse(quoteMessage.eventToReplyTo),
);
const latestThreadMessage = !quoteMessageEventId
? (await Messages.findLatestFederationThreadMessageByTmid(message.tmid, message._id))?.federation?.eventId ||
eventIdSchema.parse(threadRootEventId)
: undefined;

if (!quoteMessageEventId && !latestThreadMessage) {
throw new Error('No event to reply to found');
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should be more specific about what type of event was expected (quote or thread) and include the message ID for debugging.

Suggested change
throw new Error('No event to reply to found');
throw new Error(
`No thread or quote event to reply to found for message ID: ${message._id}`
);

Copilot uses AI. Check for mistakes.
}

return this.homeserverServices.message.sendThreadMessage(
roomIdSchema.parse(matrixRoomId),
message.msg,
parsedMessage,
userIdSchema.parse(matrixUserId),
eventIdSchema.parse(threadRootEventId),
latestThreadEventId ? eventIdSchema.parse(latestThreadEventId) : undefined,
);
const eventToReplyToNormalized = eventIdSchema.parse(quoteMessageEventId ?? latestThreadMessage);

if (quoteMessageEventId) {
return { threadEventId: eventIdSchema.parse(threadRootEventId), replyToEventId: eventToReplyToNormalized };
}
return { threadEventId: eventIdSchema.parse(threadRootEventId), latestThreadEventId: eventToReplyToNormalized };
}

private async handleQuoteMessage(
message: IMessage,
matrixRoomId: string,
matrixUserId: string,
matrixDomain: string,
): Promise<{ eventId: string } | null> {
private async handleQuoteMessage(message: IMessage, matrixRoomId: string, matrixUserId: string, matrixDomain: string) {
if (!message.attachments?.some((attachment) => isQuoteAttachment(attachment) && Boolean(attachment.message_link))) {
return;
}
const quoteMessage = await this.getQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain);
if (!quoteMessage) {
throw new Error('Failed to retrieve quote message');
}
return this.homeserverServices.message.sendReplyToMessage(
roomIdSchema.parse(matrixRoomId),
quoteMessage.rawMessage,
quoteMessage.formattedMessage,
eventIdSchema.parse(quoteMessage.eventToReplyTo),
userIdSchema.parse(matrixUserId),
);
return {
replyToEventId: eventIdSchema.parse(quoteMessage.eventToReplyTo),
};
}

async sendMessage(message: IMessage, room: IRoomNativeFederated, user: IUser): Promise<void> {
Expand Down
8 changes: 4 additions & 4 deletions ee/packages/federation-matrix/src/events/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ async function handleMediaMessage(
room: IRoom,
matrixRoomId: string,
eventId: EventID,
tmid?: string,
thread?: { tmid: string; tshow: boolean },
): Promise<{
fromId: string;
rid: string;
msg: string;
federation_event_id: string;
tmid?: string;
thread?: { tmid: string; tshow: boolean };
attachments: [FileAttachmentProps];
}> {
const mimeType = fileInfo?.mimetype;
Expand Down Expand Up @@ -106,7 +106,7 @@ async function handleMediaMessage(
rid: room._id,
msg: '',
federation_event_id: eventId,
tmid,
thread,
attachments: [attachment],
};
}
Expand Down Expand Up @@ -239,7 +239,7 @@ export function message(emitter: Emitter<HomeserverEventSignatures>, serverName:
room,
data.room_id,
data.event_id,
thread?.tmid,
thread,
);
await Message.saveMessageFromFederation(result);
} else {
Expand Down
2 changes: 1 addition & 1 deletion packages/core-services/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
"dependencies": {
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/federation-sdk": "0.1.25",
"@rocket.chat/federation-sdk": "0.1.26",
"@rocket.chat/http-router": "workspace:^",
"@rocket.chat/icons": "^0.43.0",
"@rocket.chat/media-signaling": "workspace:^",
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7279,7 +7279,7 @@ __metadata:
"@rocket.chat/apps-engine": "workspace:^"
"@rocket.chat/core-typings": "workspace:^"
"@rocket.chat/eslint-config": "workspace:^"
"@rocket.chat/federation-sdk": "npm:0.1.25"
"@rocket.chat/federation-sdk": "npm:0.1.26"
"@rocket.chat/http-router": "workspace:^"
"@rocket.chat/icons": "npm:^0.43.0"
"@rocket.chat/jest-presets": "workspace:~"
Expand Down Expand Up @@ -7490,7 +7490,7 @@ __metadata:
"@rocket.chat/core-typings": "workspace:^"
"@rocket.chat/emitter": "npm:^0.31.25"
"@rocket.chat/eslint-config": "workspace:^"
"@rocket.chat/federation-sdk": "npm:0.1.25"
"@rocket.chat/federation-sdk": "npm:0.1.26"
"@rocket.chat/http-router": "workspace:^"
"@rocket.chat/license": "workspace:^"
"@rocket.chat/models": "workspace:^"
Expand All @@ -7515,9 +7515,9 @@ __metadata:
languageName: unknown
linkType: soft

"@rocket.chat/federation-sdk@npm:0.1.25":
version: 0.1.25
resolution: "@rocket.chat/federation-sdk@npm:0.1.25"
"@rocket.chat/federation-sdk@npm:0.1.26":
version: 0.1.26
resolution: "@rocket.chat/federation-sdk@npm:0.1.26"
dependencies:
"@datastructures-js/priority-queue": "npm:^6.3.3"
"@noble/ed25519": "npm:^3.0.0"
Expand All @@ -7530,7 +7530,7 @@ __metadata:
zod: "npm:^3.22.4"
peerDependencies:
typescript: ~5.9.2
checksum: 10/94446bf69983916584a8d202883a6bc9a5c97f8e3662c44361ba7d886e29a57c1385237cca2c4e4a62333813a28659ea94437b45cb1ecd311f6efaeb44f8f840
checksum: 10/db91fd3070c1630a2bf7805155cd4df060ac267fe252d574692dd680b260f66d8164c52f96253cd2567384e3ed2a11096c1869bb4ba1ba29c4fc591edd7e8186
languageName: node
linkType: hard

Expand Down
Loading