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.16",
"@rocket.chat/federation-sdk": "0.1.17",
"@rocket.chat/http-router": "workspace:^",
"@rocket.chat/license": "workspace:^",
"@rocket.chat/models": "workspace:^",
Expand Down
27 changes: 26 additions & 1 deletion ee/packages/federation-matrix/src/FederationMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ import {
} from '@rocket.chat/core-typings';
import type { MessageQuoteAttachment, IMessage, IRoom, IUser, IRoomNativeFederated } from '@rocket.chat/core-typings';
import { eventIdSchema, getAllServices, roomIdSchema, userIdSchema } from '@rocket.chat/federation-sdk';
import type { EventID, UserID, HomeserverServices, FileMessageType, PresenceState } from '@rocket.chat/federation-sdk';
import type {
EventID,
UserID,
HomeserverServices,
FileMessageType,
PresenceState,
PersistentEventBase,
RoomVersion,
} from '@rocket.chat/federation-sdk';
import { Logger } from '@rocket.chat/logger';
import { Users, Subscriptions, Messages, Rooms, Settings } from '@rocket.chat/models';
import emojione from 'emojione';
Expand Down Expand Up @@ -971,4 +979,21 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS

return results;
}

async emitJoin(membershipEvent: PersistentEventBase<RoomVersion, 'm.room.member'>) {
if (!this.homeserverServices) {
this.logger.warn('Homeserver services not available, skipping user role room scoped');
return;
}

this.homeserverServices.emitter.emit('homeserver.matrix.membership', {
event_id: membershipEvent.eventId,
event: membershipEvent.event,
room_id: membershipEvent.roomId,
state_key: membershipEvent.stateKey,
content: { membership: 'join' },
sender: membershipEvent.sender,
origin_server_ts: Date.now(),
});
}
Comment on lines +983 to +998
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix warning message and preserve original event data.

The method has several issues:

  1. Line 985: The warning message is copy-pasted from another method and says "skipping user role room scoped" instead of something relevant like "skipping join event emission"
  2. Line 994: The content hardcodes membership: 'join', but this should preserve the original membership state from the event parameter
  3. Line 996: Using Date.now() for origin_server_ts loses the original event timestamp

Apply this diff to fix these issues:

 	async emitJoin(membershipEvent: PersistentEventBase<RoomVersion, 'm.room.member'>) {
 		if (!this.homeserverServices) {
-			this.logger.warn('Homeserver services not available, skipping user role room scoped');
+			this.logger.warn('Homeserver services not available, skipping join event emission');
 			return;
 		}
 
 		this.homeserverServices.emitter.emit('homeserver.matrix.membership', {
 			event_id: membershipEvent.eventId,
 			event: membershipEvent.event,
 			room_id: membershipEvent.roomId,
 			state_key: membershipEvent.stateKey,
-			content: { membership: 'join' },
+			content: membershipEvent.event.content,
 			sender: membershipEvent.sender,
-			origin_server_ts: Date.now(),
+			origin_server_ts: membershipEvent.event.origin_server_ts,
 		});
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async emitJoin(membershipEvent: PersistentEventBase<RoomVersion, 'm.room.member'>) {
if (!this.homeserverServices) {
this.logger.warn('Homeserver services not available, skipping user role room scoped');
return;
}
this.homeserverServices.emitter.emit('homeserver.matrix.membership', {
event_id: membershipEvent.eventId,
event: membershipEvent.event,
room_id: membershipEvent.roomId,
state_key: membershipEvent.stateKey,
content: { membership: 'join' },
sender: membershipEvent.sender,
origin_server_ts: Date.now(),
});
}
async emitJoin(membershipEvent: PersistentEventBase<RoomVersion, 'm.room.member'>) {
if (!this.homeserverServices) {
this.logger.warn('Homeserver services not available, skipping join event emission');
return;
}
this.homeserverServices.emitter.emit('homeserver.matrix.membership', {
event_id: membershipEvent.eventId,
event: membershipEvent.event,
room_id: membershipEvent.roomId,
state_key: membershipEvent.stateKey,
content: membershipEvent.event.content,
sender: membershipEvent.sender,
origin_server_ts: membershipEvent.event.origin_server_ts,
});
}
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/FederationMatrix.ts around lines 983 to
998, fix the incorrect warning text and preserve original event data when
emitting joins: change the logger.warn text to something like "Homeserver
services not available, skipping join event emission"; set the emitted
content.membership to the membership value from the incoming event (e.g.
membershipEvent.event.content.membership) instead of hardcoding 'join'; and set
origin_server_ts to the original event timestamp (e.g.
membershipEvent.event.origin_server_ts or equivalent field on the event) rather
than using Date.now() so the original event time is preserved.

}
9 changes: 7 additions & 2 deletions ee/packages/federation-matrix/src/api/_matrix/invite.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Room } from '@rocket.chat/core-services';
import { FederationMatrix, Room } from '@rocket.chat/core-services';
import { isUserNativeFederated, type IUser } from '@rocket.chat/core-typings';
import { eventIdSchema, roomIdSchema } from '@rocket.chat/federation-sdk';
import type {
Expand Down Expand Up @@ -172,7 +172,7 @@ async function joinRoom({
await room.joinUser(inviteEvent.roomId, inviteEvent.event.state_key);

// now we create the room we saved post joining
const matrixRoom = await state.getFullRoomState2(inviteEvent.roomId);
const matrixRoom = await state.getLatestRoomState2(inviteEvent.roomId);
if (!matrixRoom) {
throw new Error('room not found not processing invite');
}
Expand Down Expand Up @@ -265,6 +265,11 @@ async function joinRoom({
}

await Room.addUserToRoom(internalRoomId, { _id: user._id }, { _id: senderUserId, username: inviteEvent.sender });

for await (const event of matrixRoom.getMemberJoinEvents()) {
console.log('emitting another join', event.toStrippedJson());
await FederationMatrix.emitJoin(event);
}
Comment on lines +269 to +272
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace console.log with proper logger and add error handling.

The loop has several issues:

  1. Line 270 uses console.log instead of a proper logger
  2. No error handling for FederationMatrix.emitJoin - if it fails, remaining events won't be processed
  3. The log message "emitting another join" is informal

Apply this diff to use proper logging and add error handling:

 	await Room.addUserToRoom(internalRoomId, { _id: user._id }, { _id: senderUserId, username: inviteEvent.sender });
 
 	for await (const event of matrixRoom.getMemberJoinEvents()) {
-		console.log('emitting another join', event.toStrippedJson());
-		await FederationMatrix.emitJoin(event);
+		try {
+			await FederationMatrix.emitJoin(event);
+		} catch (error) {
+			console.error('Failed to emit join event', { eventId: event.eventId, error });
+		}
 	}
 }

Note: If a proper logger instance is available in this scope, replace console.error with the logger.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for await (const event of matrixRoom.getMemberJoinEvents()) {
console.log('emitting another join', event.toStrippedJson());
await FederationMatrix.emitJoin(event);
}
await Room.addUserToRoom(internalRoomId, { _id: user._id }, { _id: senderUserId, username: inviteEvent.sender });
for await (const event of matrixRoom.getMemberJoinEvents()) {
- console.log('emitting another join', event.toStrippedJson());
try {
await FederationMatrix.emitJoin(event);
} catch (error) {
console.error('Failed to emit join event', { eventId: event.eventId, error });
}
}
}
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/_matrix/invite.ts around lines 269 to
272, replace the informal console.log with the module logger (or console.error
only if no logger in scope) and wrap the await FederationMatrix.emitJoin(event)
call in a try/catch so a failure for one event does not stop processing the
rest; on success use a clear info/debug log (include context like room id,
member and event id/stripped JSON) and on error log an error-level message
including the caught error and the same context, then continue to the next
event.

}

async function startJoiningRoom(...opts: Parameters<typeof joinRoom>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export interface IFederationMatrixService {
inviteUsersToRoom(room: IRoomFederated, usersUserName: string[], inviter: IUser): Promise<void>;
notifyUserTyping(rid: string, user: string, isTyping: boolean): Promise<void>;
verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }>;
emitJoin(membershipEvent: any): Promise<void>;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace any with strongly-typed parameter.

The interface uses any for the membershipEvent parameter, but the implementation in FederationMatrix.ts (lines 983-998) uses the specific type PersistentEventBase<RoomVersion, 'm.room.member'>. This type mismatch defeats TypeScript's type safety.

Apply this diff to align the interface with the implementation:

+import type { PersistentEventBase, RoomVersion } from '@rocket.chat/federation-sdk';
+
 export interface IFederationMatrixService {
 	createRoom(room: IRoomFederated, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }>;
 	ensureFederatedUsersExistLocally(members: string[]): Promise<void>;
 	createDirectMessageRoom(room: IRoomFederated, members: IUser[], creatorId: IUser['_id']): Promise<void>;
 	sendMessage(message: IMessage, room: IRoomFederated, user: IUser): Promise<void>;
 	deleteMessage(matrixRoomId: string, 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>;
 	leaveRoom(rid: IRoomFederated['_id'], user: IUser): Promise<void>;
 	kickUser(room: IRoomNativeFederated, removedUser: IUser, userWhoRemoved: IUser): Promise<void>;
 	updateMessage(room: IRoomNativeFederated, message: IMessage): Promise<void>;
 	updateRoomName(rid: string, displayName: string, user: IUser): Promise<void>;
 	updateRoomTopic(room: IRoomNativeFederated, topic: string, user: IUser): Promise<void>;
 	addUserRoleRoomScoped(
 		room: IRoomNativeFederated,
 		senderId: string,
 		userId: string,
 		role: 'moderator' | 'owner' | 'leader' | 'user',
 	): Promise<void>;
 	inviteUsersToRoom(room: IRoomFederated, usersUserName: string[], inviter: IUser): Promise<void>;
 	notifyUserTyping(rid: string, user: string, isTyping: boolean): Promise<void>;
 	verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }>;
-	emitJoin(membershipEvent: any): Promise<void>;
+	emitJoin(membershipEvent: PersistentEventBase<RoomVersion, 'm.room.member'>): Promise<void>;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
emitJoin(membershipEvent: any): Promise<void>;
import type { PersistentEventBase, RoomVersion } from '@rocket.chat/federation-sdk';
export interface IFederationMatrixService {
createRoom(room: IRoomFederated, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }>;
ensureFederatedUsersExistLocally(members: string[]): Promise<void>;
createDirectMessageRoom(room: IRoomFederated, members: IUser[], creatorId: IUser['_id']): Promise<void>;
sendMessage(message: IMessage, room: IRoomFederated, user: IUser): Promise<void>;
deleteMessage(matrixRoomId: string, 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>;
leaveRoom(rid: IRoomFederated['_id'], user: IUser): Promise<void>;
kickUser(room: IRoomNativeFederated, removedUser: IUser, userWhoRemoved: IUser): Promise<void>;
updateMessage(room: IRoomNativeFederated, message: IMessage): Promise<void>;
updateRoomName(rid: string, displayName: string, user: IUser): Promise<void>;
updateRoomTopic(room: IRoomNativeFederated, topic: string, user: IUser): Promise<void>;
addUserRoleRoomScoped(
room: IRoomNativeFederated,
senderId: string,
userId: string,
role: 'moderator' | 'owner' | 'leader' | 'user',
): Promise<void>;
inviteUsersToRoom(room: IRoomFederated, usersUserName: string[], inviter: IUser): Promise<void>;
notifyUserTyping(rid: string, user: string, isTyping: boolean): Promise<void>;
verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }>;
emitJoin(membershipEvent: PersistentEventBase<RoomVersion, 'm.room.member'>): Promise<void>;
}
🤖 Prompt for AI Agents
In packages/core-services/src/types/IFederationMatrixService.ts around line 26,
the emitJoin(membershipEvent: any) signature uses any but the implementation
expects PersistentEventBase<RoomVersion, 'm.room.member'>; change the parameter
type to PersistentEventBase<RoomVersion, 'm.room.member'> and add the necessary
imports (PersistentEventBase and RoomVersion) from their source module so the
interface matches FederationMatrix.ts implementation and preserves TypeScript
type safety.

}
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7534,7 +7534,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.16"
"@rocket.chat/federation-sdk": "npm:0.1.17"
"@rocket.chat/http-router": "workspace:^"
"@rocket.chat/license": "workspace:^"
"@rocket.chat/models": "workspace:^"
Expand All @@ -7560,9 +7560,9 @@ __metadata:
languageName: unknown
linkType: soft

"@rocket.chat/federation-sdk@npm:0.1.16":
version: 0.1.16
resolution: "@rocket.chat/federation-sdk@npm:0.1.16"
"@rocket.chat/federation-sdk@npm:0.1.17":
version: 0.1.17
resolution: "@rocket.chat/federation-sdk@npm:0.1.17"
dependencies:
"@datastructures-js/priority-queue": "npm:^6.3.3"
"@noble/ed25519": "npm:^3.0.0"
Expand All @@ -7575,7 +7575,7 @@ __metadata:
zod: "npm:^3.22.4"
peerDependencies:
typescript: ~5.9.2
checksum: 10/88edcce215b91956237c896ce0b5d00fea5aba9c8ebe314cb63f26c8677cde8e9c9c4ad72a0e7ce6ea5be40c2b7d1bbdfae295289acfe7cf242e23a7f0583f63
checksum: 10/4b83071913a84c275c2b49c55e9993a12812d3ba9945cba9d6c3f2b3bcba4ae5a868de6379853f9c49e93a6dc22f5bb5023fa50245e94b1e0a2a0a003558ef0a
languageName: node
linkType: hard

Expand Down
Loading