Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
34 changes: 14 additions & 20 deletions apps/meteor/ee/server/hooks/federation/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { api, FederationMatrix } from '@rocket.chat/core-services';
import { isEditedMessage, type IMessage, type IRoom, type IUser } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import { MatrixBridgedRoom, Rooms } from '@rocket.chat/models';

import notifications from '../../../../app/notifications/server/lib/Notifications';
import { settings } from '../../../../app/settings/server';
import { callbacks } from '../../../../lib/callbacks';
import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback';
import { afterRemoveFromRoomCallback } from '../../../../lib/callbacks/afterRemoveFromRoomCallback';
Expand All @@ -17,8 +19,6 @@ import { FederationActions } from '../../../../server/services/room/hooks/Before
callbacks.add('federation.afterCreateFederatedRoom', async (room, { owner, originalMemberList: members, options }) => {
if (FederationActions.shouldPerformFederationAction(room)) {
const federatedRoomId = options?.federatedRoomId;
// TODO: move this to the hooks folder
setupTypingEventListenerForRoom(room._id);

if (!federatedRoomId) {
// if room if exists, we don't want to create it again
Expand Down Expand Up @@ -228,22 +228,16 @@ callbacks.add(
'federation-matrix-after-create-direct-room',
);

// TODO: THIS IS NOT READY FOR PRODUCTION! IMPOSSIBLE TO ADD ONE LISTENER PER ROOM!
const setupTypingEventListenerForRoom = (roomId: string): void => {
notifications.streamRoom.on(`${roomId}/user-activity`, (username, activity) => {
if (Array.isArray(activity) && (!activity.length || activity.includes('user-typing'))) {
void api.broadcast('user.typing', {
user: { username },
isTyping: activity.includes('user-typing'),
roomId,
});
}
});
};

export const setupInternalEDUEventListeners = async () => {
const federatedRooms = await Rooms.findFederatedRooms({ projection: { _id: 1 } }).toArray();
for (const room of federatedRooms) {
setupTypingEventListenerForRoom(room._id);
notifications.streamLocal.on(`user-activity`, ({ rid, username, activities }) => {
if (!License.hasModule('federation') || !settings.get('Federation_Service_Enabled')) {
return;
}
};

if (activities.includes('user-typing')) {
void api.broadcast('user.typing', {
user: { username },
isTyping: activities.includes('user-typing'),
roomId: rid,
});
}
});
6 changes: 0 additions & 6 deletions apps/meteor/ee/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ import './local-services/ldap/service';
import './methods/getReadReceipts';
import './patches';
import './hooks/federation';
import { License } from '@rocket.chat/license';

export * from './apps/startup';
export { registerEEBroker } from './startup';

await License.onLicense('federation', async () => {
const { setupInternalEDUEventListeners } = await import('./hooks/federation');
await setupInternalEDUEventListeners();
});
53 changes: 23 additions & 30 deletions apps/meteor/server/modules/listeners/listeners.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,39 @@ export class ListenersModule {
notifications.notifyRoom(rid, 'videoconf', callId);
});

service.onEvent('presence.status', ({ user }) => this.handlePresence({ user }, notifications));
service.onEvent('presence.status', ({ user }) => {
const { _id, username, name, status, statusText, roles } = user;
if (!status || !username) {
return;
}

notifications.notifyUserInThisInstance(_id, 'userData', {
type: 'updated',
id: _id,
diff: {
status,
...(statusText && { statusText }),
},
unset: {},
});

notifications.notifyLoggedInThisInstance('user-status', [_id, username, STATUS_MAP[status], statusText, name, roles]);

if (_id) {
notifications.sendPresence(_id, username, STATUS_MAP[status], statusText);
}
});

service.onEvent('user.updateCustomStatus', (userStatus) => {
notifications.notifyLoggedInThisInstance('updateCustomUserStatus', {
userStatusData: userStatus,
});
});

service.onEvent('federation-matrix.user.typing', ({ isTyping, roomId, username }) => {
service.onEvent('user-activity', ({ isTyping, roomId, username }) => {
notifications.notifyRoom(roomId, 'user-activity', username, isTyping ? ['user-typing'] : []);
});

service.onEvent('federation-matrix.user.presence.status', ({ user }) => this.handlePresence({ user }, notifications));

service.onEvent('watch.messages', async ({ message }) => {
if (!message.rid) {
return;
Expand Down Expand Up @@ -491,30 +510,4 @@ export class ListenersModule {
notifications.streamRoomMessage.emit(roomId, acknowledgeMessage);
});
}

private handlePresence(
{ user }: { user: Pick<IUser, '_id' | 'username' | 'status' | 'statusText' | 'name' | 'roles'> },
notifications: NotificationsModule,
): void {
const { _id, username, name, status, statusText, roles } = user;
if (!status || !username) {
return;
}

notifications.notifyUserInThisInstance(_id, 'userData', {
type: 'updated',
id: _id,
diff: {
status,
...(statusText && { statusText }),
},
unset: {},
});

notifications.notifyLoggedInThisInstance('user-status', [_id, username, STATUS_MAP[status], statusText, name, roles]);

if (_id) {
notifications.sendPresence(_id, username, STATUS_MAP[status], statusText);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,11 @@ export class NotificationsModule {
eventName: E extends ExtractNotifyUserEventName<'notify-room', P> ? E : never,
...args: E extends ExtractNotifyUserEventName<'notify-room', P> ? StreamerCallbackArgs<'notify-room', `${P}/${E}`> : never
): void {
this.streamLocal.emit(eventName, {
rid: room,
eventName,
args,
});
return this.streamRoom.emit(`${room}/${eventName}`, ...args);
}

Expand Down
5 changes: 3 additions & 2 deletions ee/packages/federation-matrix/src/events/edu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
}

const user = await Users.findOneById(matrixUser.uid, { projection: { _id: 1, username: 1 } });
if (!user || !user.username) {

Check warning on line 26 in ee/packages/federation-matrix/src/events/edu.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Prefer using an optional chain expression instead, as it's more concise and easier to read
logger.debug(`User not found for uid: ${matrixUser.uid}`);
return;
}

void api.broadcast('federation-matrix.user.typing', {
void api.broadcast('user-activity', {
username: user.username,
isTyping: data.typing,
roomId: matrixRoom,
Expand Down Expand Up @@ -71,8 +71,9 @@
);

const { _id, username, statusText, roles, name } = user;
void api.broadcast('federation-matrix.user.presence.status', {
void api.broadcast('presence.status', {
user: { status, _id, username, statusText, roles, name },
previousStatus: undefined,
});
logger.debug(`Updated presence for user ${matrixUser.uid} to ${status} from Matrix federation`);
} catch (error) {
Expand Down
6 changes: 1 addition & 5 deletions packages/core-services/src/events/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,7 @@ export type EventSignatures = {
}): void;
'user.updateCustomStatus'(userStatus: Omit<ICustomUserStatus, '_updatedAt'>): void;
'user.typing'(data: { user: Partial<IUser>; isTyping: boolean; roomId: string }): void;
'federation-matrix.user.typing'(data: { username: string; isTyping: boolean; roomId: string }): void;
'federation-matrix.user.presence.status'(data: {
user: Pick<IUser, '_id' | 'username' | 'status' | 'statusText' | 'name' | 'roles'>;
previousStatus?: UserStatus;
}): void;
'user-activity'(data: { username: string; isTyping: boolean; roomId: string }): void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use stable identifiers: prefer userId over username in the event payload

Usernames can change and are not globally unique across federated spaces. Emit a stable ID and keep username optional for UI.

Apply this diff:

-	'user-activity'(data: { username: string; isTyping: boolean; roomId: string }): void;
+	'user-activity'(data: { userId: IUser['_id']; username?: IUser['username']; isTyping: boolean; roomId: IRoom['_id'] }): 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
'user-activity'(data: { username: string; isTyping: boolean; roomId: string }): void;
'user-activity'(data: { userId: IUser['_id']; username?: IUser['username']; isTyping: boolean; roomId: IRoom['_id'] }): void;
🤖 Prompt for AI Agents
In packages/core-services/src/events/Events.ts around line 153, the
'user-activity' event currently requires username but should use a stable userId
instead; update the event signature to accept data: { userId: string; username?:
string; isTyping: boolean; roomId: string } (userId required, username
optional), and then update all emitters and listeners across the codebase to
pass and consume userId rather than username while keeping username as an
optional UI-only field for backward compatibility.

'user.video-conference'(data: {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
userId: IUser['_id'];
action: string;
Expand Down
11 changes: 11 additions & 0 deletions packages/ddp-client/src/types/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface StreamerEvents {

'notify-room': [
{ key: `${string}/user-activity`; args: [username: string, activities: string[]] },
/* @deprecated over UserActivity */
{ key: `${string}/typing`; args: [username: string, typing: boolean] },
{
key: `${string}/deleteMessageBulk`;
Expand Down Expand Up @@ -570,6 +571,16 @@ export interface StreamerEvents {
key: 'broadcast';
args: any[];
},
{
key: `user-activity`;
args: [
{
rid: string;
username: string;
activities: string[];
},
];
},
];
}

Expand Down
Loading