-
Notifications
You must be signed in to change notification settings - Fork 13k
feat: DMs #36762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: DMs #36762
Changes from all commits
f8ab99a
552a233
b45e1e9
b29acfd
4ebe2ab
3888415
6afeb60
b52dac2
742070d
c66f67f
c825f20
c29e660
015ea0c
df2dafc
0a386a8
403cb8c
86f6ac5
078fa55
cc24ef3
64ec2bb
334ab57
51e1c99
3f3c22f
5bd1969
2e54712
bdae1e6
d764574
6301344
7f3d80d
7e88919
da37cef
c305b7b
67b1b84
4ae076d
56148aa
9bb8c6f
73b032f
6566910
bb0bfce
882bc0e
7f58c78
1576fa3
38f20db
6ebee24
074964e
d5dd20d
a2ad5e9
4aeb36f
68ac152
a98df67
1b5907a
5639c74
fee110d
3a6f225
40e0c91
fde4a47
8925c71
21dac58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -230,11 +230,164 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS | |
|
|
||
| this.logger.debug('Room creation completed successfully', room._id); | ||
| } catch (error) { | ||
| console.log(error); | ||
| this.logger.error('Failed to create room:', error); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| async ensureFederatedUsersExistLocally(members: (IUser | string)[]): Promise<void> { | ||
| try { | ||
| this.logger.debug('Ensuring federated users exist locally before DM creation', { memberCount: members.length }); | ||
|
|
||
| for await (const member of members) { | ||
| let username: string; | ||
|
|
||
| if (typeof member === 'string') { | ||
| username = member; | ||
| } else { | ||
| username = member.username as string; | ||
| } | ||
|
|
||
| if (!username.includes(':') && !username.includes('@')) { | ||
| continue; | ||
| } | ||
|
|
||
| const externalUserId = username.includes(':') ? `@${username}` : `@${username}:${this.serverName}`; | ||
|
|
||
| const existingUser = await Users.findOneByUsername(username); | ||
| if (existingUser) { | ||
| const existingBridge = await MatrixBridgedUser.getExternalUserIdByLocalUserId(existingUser._id); | ||
| if (!existingBridge) { | ||
| const remoteDomain = externalUserId.split(':')[1] || this.serverName; | ||
| await MatrixBridgedUser.createOrUpdateByLocalId(existingUser._id, externalUserId, true, remoteDomain); | ||
| } | ||
| continue; | ||
| } | ||
|
|
||
| this.logger.debug('Creating federated user locally', { externalUserId, username }); | ||
|
|
||
| const remoteDomain = externalUserId.split(':')[1] || this.serverName; | ||
| const localName = username.split(':')[0]?.replace('@', '') || username; | ||
|
|
||
| const newUser = { | ||
| username, | ||
| name: localName, | ||
| type: 'user' as const, | ||
| status: UserStatus.OFFLINE, | ||
| active: true, | ||
| roles: ['user'], | ||
| requirePasswordChange: false, | ||
| federated: true, | ||
| createdAt: new Date(), | ||
| _updatedAt: new Date(), | ||
| }; | ||
|
|
||
| const { insertedId } = await Users.insertOne(newUser); | ||
| await MatrixBridgedUser.createOrUpdateByLocalId(insertedId, externalUserId, true, remoteDomain); | ||
|
|
||
| this.logger.debug('Successfully created federated user locally', { userId: insertedId, externalUserId }); | ||
| } | ||
| } catch (error) { | ||
| this.logger.error('Failed to ensure federated users exist locally:', error); | ||
| } | ||
| } | ||
|
|
||
| async createDirectMessageRoom(room: IRoom, members: (IUser | string)[], creatorId: IUser['_id']): Promise<void> { | ||
| try { | ||
| this.logger.debug('Creating direct message room in Matrix', { roomId: room._id, memberCount: members.length }); | ||
|
|
||
| if (!this.homeserverServices) { | ||
| this.logger.warn('Homeserver services not available, skipping DM room creation'); | ||
| return; | ||
| } | ||
|
|
||
| const creator = await Users.findOneById(creatorId); | ||
| if (!creator) { | ||
| throw new Error('Creator not found in members list'); | ||
| } | ||
|
|
||
| const matrixUserId = `@${creator.username}:${this.serverName}`; | ||
| const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(creator._id); | ||
| if (!existingMatrixUserId) { | ||
| await MatrixBridgedUser.createOrUpdateByLocalId(creator._id, matrixUserId, true, this.serverName); | ||
| } | ||
|
|
||
| const actualMatrixUserId = existingMatrixUserId || matrixUserId; | ||
|
|
||
| let matrixRoomResult: { room_id: string; event_id?: string }; | ||
| if (members.length === 2) { | ||
| const otherMember = members.find((member) => { | ||
| if (typeof member === 'string') { | ||
| return true; // Remote user | ||
| } | ||
| return member._id !== creatorId; | ||
| }); | ||
| if (!otherMember) { | ||
| throw new Error('Other member not found for 1-on-1 DM'); | ||
| } | ||
| let otherMemberMatrixId: string; | ||
| if (typeof otherMember === 'string') { | ||
| otherMemberMatrixId = otherMember.startsWith('@') ? otherMember : `@${otherMember}`; | ||
| } else if (otherMember.username?.includes(':')) { | ||
| otherMemberMatrixId = otherMember.username.startsWith('@') ? otherMember.username : `@${otherMember.username}`; | ||
| } else { | ||
| otherMemberMatrixId = `@${otherMember.username}:${this.serverName}`; | ||
| } | ||
| const roomId = await this.homeserverServices.room.createDirectMessageRoom(actualMatrixUserId, otherMemberMatrixId); | ||
| matrixRoomResult = { room_id: roomId }; | ||
| } else { | ||
| // For group DMs (more than 2 members), create a private room | ||
| const roomName = room.name || room.fname || `Group chat with ${members.length} members`; | ||
| matrixRoomResult = await this.homeserverServices.room.createRoom(actualMatrixUserId, roomName, 'invite'); | ||
| } | ||
|
|
||
| const mapping = await MatrixBridgedRoom.getLocalRoomId(matrixRoomResult.room_id); | ||
| if (!mapping) { | ||
| await MatrixBridgedRoom.createOrUpdateByLocalRoomId(room._id, matrixRoomResult.room_id, this.serverName); | ||
| } | ||
|
|
||
| for await (const member of members) { | ||
| if (typeof member !== 'string' && member._id === creatorId) continue; | ||
|
|
||
| try { | ||
| let memberMatrixUserId: string; | ||
| let memberId: string | undefined; | ||
|
|
||
| if (typeof member === 'string') { | ||
| memberMatrixUserId = member.startsWith('@') ? member : `@${member}`; | ||
| memberId = undefined; | ||
| } else if (member.username?.includes(':')) { | ||
| memberMatrixUserId = member.username.startsWith('@') ? member.username : `@${member.username}`; | ||
| memberId = member._id; | ||
| } else { | ||
| memberMatrixUserId = `@${member.username}:${this.serverName}`; | ||
| memberId = member._id; | ||
| } | ||
|
|
||
| if (memberId) { | ||
| const existingMemberMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(memberId); | ||
|
|
||
| if (!existingMemberMatrixUserId) { | ||
| await MatrixBridgedUser.createOrUpdateByLocalId(memberId, memberMatrixUserId, true, this.serverName); | ||
| } | ||
| } | ||
|
|
||
| if (members.length > 2) { | ||
| await this.homeserverServices.invite.inviteUserToRoom(memberMatrixUserId, matrixRoomResult.room_id, actualMatrixUserId); | ||
| } | ||
| } catch (error) { | ||
| this.logger.error('Error creating or updating bridged user for DM:', error); | ||
| } | ||
| } | ||
| await Rooms.setAsFederated(room._id); | ||
| this.logger.debug('Direct message room creation completed successfully', room._id); | ||
| } catch (error) { | ||
| this.logger.error('Failed to create direct message room:', error); | ||
| throw error; | ||
| } | ||
| } | ||
|
Comment on lines
+296
to
+389
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify Matrix user ID format handling in DM creation. The method constructs Matrix user IDs differently for local vs. federated users, but doesn't validate the format consistently. Let's verify the Matrix ID format handling across the codebase: 🏁 Script executed: #!/bin/bash
# Check how Matrix user IDs are constructed elsewhere
rg -n "startsWith\('@'\)" ee/packages/federation-matrix/src/ -A 2 -B 2
# Check for any existing validation of Matrix ID format
rg -n "@.*:.*" ee/packages/federation-matrix/src/ --type ts | grep -E "(validate|check|verify)"
# Look for other instances of Matrix ID construction
ast-grep --pattern 'username.startsWith("@") ? username : `@${username}`'Length of output: 1638 Normalize and validate Matrix user IDs before use
|
||
|
|
||
| async sendMessage(message: IMessage, room: IRoom, user: IUser): Promise<void> { | ||
| try { | ||
| const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(room._id); | ||
|
|
@@ -260,7 +413,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS | |
| const parsedMessage = await toExternalMessageFormat({ | ||
| message: message.msg, | ||
| externalRoomId: matrixRoomId, | ||
| homeServerDomain: await this.serverName, | ||
| homeServerDomain: this.serverName, | ||
| }); | ||
| if (!message.tmid) { | ||
| if (message.attachments?.some((attachment) => isQuoteAttachment(attachment) && Boolean(attachment.message_link))) { | ||
|
|
@@ -370,7 +523,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS | |
| return; | ||
| } | ||
| const messageToReplyTo = await Messages.findOneById(messageToReplyToId); | ||
| if (!messageToReplyTo || !messageToReplyTo.federation?.eventId) { | ||
| if (!messageToReplyTo?.federation?.eventId) { | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -679,7 +832,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS | |
| const parsedMessage = await toExternalMessageFormat({ | ||
| message: newContent, | ||
| externalRoomId: matrixRoomId, | ||
| homeServerDomain: await this.serverName, | ||
| homeServerDomain: this.serverName, | ||
| }); | ||
| const eventId = await this.homeserverServices.message.updateMessage( | ||
| matrixRoomId, | ||
|
|
@@ -771,7 +924,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS | |
| } else if (role === 'moderator') { | ||
| powerLevel = 50; | ||
| } | ||
|
|
||
| await this.homeserverServices.room.setPowerLevelForUser(matrixRoomId, senderMatrixUserId, matrixUserId, powerLevel); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add input validation for federated users provisioning.
The method doesn't validate the input array or handle edge cases like empty members list.
Add validation at the beginning of the method:
async ensureFederatedUsersExistLocally(members: (IUser | string)[]): Promise<void> { try { + if (!members || members.length === 0) { + this.logger.debug('No members to provision'); + return; + } + this.logger.debug('Ensuring federated users exist locally before DM creation', { memberCount: members.length });📝 Committable suggestion
🤖 Prompt for AI Agents