11import 'reflect-metadata' ;
22
3+ import type { PresenceState } from '@hs/core' ;
34import { ConfigService , createFederationContainer , getAllServices } from '@hs/federation-sdk' ;
45import type { HomeserverEventSignatures , HomeserverServices , FederationContainerOptions } from '@hs/federation-sdk' ;
56import { type IFederationMatrixService , Room , ServiceClass , Settings } from '@rocket.chat/core-services' ;
6- import type { IMessage , IRoom , IUser } from '@rocket.chat/core-typings' ;
7+ import { isDeletedMessage , isMessageFromMatrixFederation , UserStatus , type IMessage , type IRoom , type IUser } from '@rocket.chat/core-typings' ;
78import { Emitter } from '@rocket.chat/emitter' ;
89import { Router } from '@rocket.chat/http-router' ;
910import { Logger } from '@rocket.chat/logger' ;
@@ -63,6 +64,58 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
6364 createFederationContainer ( containerOptions , config ) ;
6465 instance . homeserverServices = getAllServices ( ) ;
6566 instance . buildMatrixHTTPRoutes ( ) ;
67+ instance . onEvent ( 'user.typing' , async ( { isTyping, roomId, user : { username } } ) : Promise < void > => {
68+ if ( ! roomId || ! username ) {
69+ return ;
70+ }
71+ const externalRoomId = await MatrixBridgedRoom . getExternalRoomId ( roomId ) ;
72+ if ( ! externalRoomId ) {
73+ return ;
74+ }
75+ const localUser = await Users . findOneByUsername ( username , { projection : { _id : 1 } } ) ;
76+ if ( ! localUser ) {
77+ return ;
78+ }
79+ const externalUserId = await MatrixBridgedUser . getExternalUserIdByLocalUserId ( localUser . _id ) ;
80+ if ( ! externalUserId ) {
81+ return ;
82+ }
83+ void instance . homeserverServices . edu . sendTypingNotification ( externalRoomId , externalUserId , isTyping ) ;
84+ } ) ;
85+ instance . onEvent (
86+ 'presence.status' ,
87+ async ( { user } : { user : Pick < IUser , '_id' | 'username' | 'status' | 'statusText' | 'name' | 'roles' > } ) : Promise < void > => {
88+ if ( ! user . username || ! user . status ) {
89+ return ;
90+ }
91+ const localUser = await Users . findOneByUsername ( user . username , { projection : { _id : 1 } } ) ;
92+ if ( ! localUser ) {
93+ return ;
94+ }
95+ const externalUserId = await MatrixBridgedUser . getExternalUserIdByLocalUserId ( localUser . _id ) ;
96+ if ( ! externalUserId ) {
97+ return ;
98+ }
99+
100+ const roomsUserIsMemberOf = await Subscriptions . findUserFederatedRoomIds ( localUser . _id ) . toArray ( ) ;
101+ const statusMap : Record < UserStatus , PresenceState > = {
102+ [ UserStatus . ONLINE ] : 'online' ,
103+ [ UserStatus . OFFLINE ] : 'offline' ,
104+ [ UserStatus . AWAY ] : 'unavailable' ,
105+ [ UserStatus . BUSY ] : 'unavailable' ,
106+ [ UserStatus . DISABLED ] : 'offline' ,
107+ } ;
108+ void instance . homeserverServices . edu . sendPresenceUpdateToRooms (
109+ [
110+ {
111+ user_id : externalUserId ,
112+ presence : statusMap [ user . status ] || 'offline' ,
113+ } ,
114+ ] ,
115+ roomsUserIsMemberOf . map ( ( { externalRoomId } ) => externalRoomId ) ,
116+ ) ;
117+ } ,
118+ ) ;
66119
67120 return instance ;
68121 }
@@ -184,7 +237,41 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
184237
185238 const actualMatrixUserId = existingMatrixUserId || matrixUserId ;
186239
187- const result = await this . homeserverServices . message . sendMessage ( matrixRoomId , message . msg , actualMatrixUserId ) ;
240+ let result ;
241+
242+ if ( ! message . tmid ) {
243+ result = await this . homeserverServices . message . sendMessage ( matrixRoomId , message . msg , actualMatrixUserId ) ;
244+ } else {
245+ const threadRootMessage = await Messages . findOneById ( message . tmid ) ;
246+ const threadRootEventId = threadRootMessage ?. federation ?. eventId ;
247+
248+ if ( threadRootEventId ) {
249+ const latestThreadMessage = await Messages . findOne (
250+ {
251+ 'tmid' : message . tmid ,
252+ 'federation.eventId' : { $exists : true } ,
253+ '_id' : { $ne : message . _id } , // Exclude the current message
254+ } ,
255+ { sort : { ts : - 1 } } ,
256+ ) ;
257+ const latestThreadEventId = latestThreadMessage ?. federation ?. eventId ;
258+
259+ result = await this . homeserverServices . message . sendThreadMessage (
260+ matrixRoomId ,
261+ message . msg ,
262+ actualMatrixUserId ,
263+ threadRootEventId ,
264+ latestThreadEventId ,
265+ ) ;
266+ } else {
267+ this . logger . warn ( 'Thread root event ID not found, sending as regular message' ) ;
268+ result = await this . homeserverServices . message . sendMessage ( matrixRoomId , message . msg , actualMatrixUserId ) ;
269+ }
270+ }
271+
272+ if ( ! result ) {
273+ throw new Error ( 'Failed to send message to Matrix - no result returned' ) ;
274+ }
188275
189276 await Messages . setFederationEventIdById ( message . _id , result . eventId ) ;
190277
@@ -195,6 +282,39 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
195282 }
196283 }
197284
285+ async deleteMessage ( message : IMessage ) : Promise < void > {
286+ try {
287+ if ( ! isMessageFromMatrixFederation ( message ) || isDeletedMessage ( message ) ) {
288+ return ;
289+ }
290+ const matrixRoomId = await MatrixBridgedRoom . getExternalRoomId ( message . rid ) ;
291+ if ( ! matrixRoomId ) {
292+ throw new Error ( `No Matrix room mapping found for room ${ message . rid } ` ) ;
293+ }
294+ const matrixDomain = await this . getMatrixDomain ( ) ;
295+ const matrixUserId = `@${ message . u . username } :${ matrixDomain } ` ;
296+ const existingMatrixUserId = await MatrixBridgedUser . getExternalUserIdByLocalUserId ( message . u . _id ) ;
297+ if ( ! existingMatrixUserId ) {
298+ await MatrixBridgedUser . createOrUpdateByLocalId ( message . u . _id , matrixUserId , true , matrixDomain ) ;
299+ }
300+
301+ if ( ! this . homeserverServices ) {
302+ this . logger . warn ( 'Homeserver services not available, skipping message redaction' ) ;
303+ return ;
304+ }
305+ const matrixEventId = message . federation ?. eventId ;
306+ if ( ! matrixEventId ) {
307+ throw new Error ( `No Matrix event ID mapping found for message ${ message . _id } ` ) ;
308+ }
309+ const eventId = await this . homeserverServices . message . redactMessage ( matrixRoomId , matrixEventId , matrixUserId ) ;
310+
311+ this . logger . debug ( 'Message Redaction sent to Matrix successfully:' , eventId ) ;
312+ } catch ( error ) {
313+ this . logger . error ( 'Failed to send redaction to Matrix:' , error ) ;
314+ throw error ;
315+ }
316+ }
317+
198318 async inviteUsersToRoom ( room : IRoom , usersUserName : string [ ] , inviter : IUser ) : Promise < void > {
199319 try {
200320 const matrixRoomId = await MatrixBridgedRoom . getExternalRoomId ( room . _id ) ;
0 commit comments