-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Description
Description:
The chat.getMentionedMessages and chat.getStarredMessages REST API endpoints
currently require a roomId parameter. This makes it impossible for the Activity
Hub to fetch mentions and starred messages globally across all of a user's subscribed
rooms in a single request.
Affected file 1 — apps/meteor/app/api/server/lib/messages.ts
Both findMentionedMessages and findStarredMessages have roomId typed as
string (required), with no global fallback path. If roomId is not provided,
the function immediately fails at the room access check:
// findMentionedMessages
export async function findMentionedMessages({
uid,
roomId,
pagination: { offset, count, sort },
}: {
uid: string;
roomId: string; // required — no optional global path exists
pagination: { offset: number; count: number; sort: FindOptions<IMessage>['sort'] };
}) {
const room = await Rooms.findOneById(roomId);
if (!room || !(await canAccessRoomAsync(room, { _id: uid }))) {
throw new Error('error-not-allowed');
}
// only ever calls the room-scoped model method
const { cursor, totalCount } = Messages.findPaginatedVisibleByMentionAndRoomId(user.username, roomId, {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
});
}// findStarredMessages
export async function findStarredMessages({
uid,
roomId,
pagination: { offset, count, sort },
}: {
uid: string;
roomId: string; // required — no optional global path exists
pagination: { offset: number; count: number; sort: FindOptions<IMessage>['sort'] };
}) {
const room = await Rooms.findOneById(roomId);
if (!room || !(await canAccessRoomAsync(room, { _id: uid }))) {
throw new Error('error-not-allowed');
}
// only ever calls the room-scoped model method
const { cursor, totalCount } = Messages.findStarredByUserAtRoom(uid, roomId, {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
});
}Affected file 2 — packages/models/src/models/Messages.ts
The model only has room-scoped methods. There are no global counterparts that
can query across multiple rooms for a given user:
// only room-scoped method exists for mentions
findPaginatedVisibleByMentionAndRoomId(
username: IUser['username'],
rid: IRoom['_id'],
options?: FindOptions<IMessage>,
): FindPaginated<FindCursor<IMessage>> {
const query: Filter<IMessage> = {
'_hidden': { $ne: true },
'mentions.username': username,
rid, // always scoped to a single room
};
return this.findPaginated(query, options);
}
// only room-scoped method exists for starred
findStarredByUserAtRoom(
userId: IUser['_id'],
roomId: IRoom['_id'],
options?: FindOptions<IMessage>,
): FindPaginated<FindCursor<IMessage>> {
const query: Filter<IMessage> = {
'_hidden': { $ne: true },
'starred._id': userId,
'rid': roomId, // always scoped to a single room
};
return this.findPaginated(query, options);
}Affected file 3 — packages/models/src/models/BaseRaw.ts
BaseRaw has no aggregatePaginated method. Only findPaginated exists, which
uses a simple find() + countDocuments() and cannot support cross-room
aggregation pipelines needed for a global query:
findPaginated(query: Filter<T> = {}, options?: any): FindPaginated<FindCursor<WithId<T>>> {
const optionsDef = this.doNotMixInclusionAndExclusionFields(options);
const cursor = optionsDef ? this.col.find(query, optionsDef) : this.col.find(query);
const totalCount = this.col.countDocuments(query);
return {
cursor,
totalCount,
};
}
// no aggregatePaginated method existsA note on user privacy:
While implementing the global fetch, we want to make sure the Activity Hub only
shows messages from rooms that are part of a user's normal chat experience.
Rocket.Chat has different room types, each stored as a t field on the
subscription document:
t |
Room type | Should appear in Activity Hub |
|---|---|---|
c |
Public channel | Yes |
p |
Private group | Yes |
d |
Direct message | Yes |
l |
Livechat | No |
v |
VoIP | No |
Livechat rooms (l) are customer support conversations that belong to agents
and managers — a regular user should never see those messages appearing in their
personal Activity Hub feed. VoIP rooms (v) are call records and not chat
messages, so they have no place in a mentions or starred messages view either.
The global subscription fetch should filter to only include the standard room
types (c, p, d) so that every user's Activity Hub stays within the
boundaries of what they are actually meant to see. This keeps the privacy model
consistent with the existing per-room checks already in place for the
room-scoped path.
Steps to reproduce:
- Log in as any user who is subscribed to multiple rooms
- Call
GET api/v1/chat.getMentionedMessageswithout aroomIdparameter - Call
GET api/v1/chat.getStarredMessageswithout aroomIdparameter
Expected behavior:
Both endpoints should support an optional roomId. When roomId is omitted,
the API should return all mentioned/starred messages across every room the user
is subscribed to, paginated, sorted by timestamp descending. This is required
for the Activity Hub to display a unified cross-room feed in a single request.
Actual behavior:
Both endpoints require roomId. There is no global path in the service layer,
no global query methods in the model layer, and no base aggregation support in
BaseRaw. As a result, a client wanting to power the Activity Hub must fire one
separate API request per subscribed room and merge results client-side — which
for a user subscribed to 50 rooms means 50 HTTP round trips per Activity Hub load
instead of one.
Server Setup Information:
- Version of Rocket.Chat Server: latest
- License Type: Community / Enterprise
- Number of Users: any deployment where users are subscribed to many rooms
- Operating System: any
- Deployment Method: any
- Number of Running Instances: any
- DB Replicaset Oplog: any
- NodeJS Version: any
- MongoDB Version: any
Client Setup Information
- Desktop App or Browser Version: any
- Operating System: any
Additional context
This is a prerequisite for the Activity Hub GSoC feature. The Activity Hub is
intended to be a single view showing mentions, starred messages, and discussions
across all of a user's rooms. Without a global API path, every page load of the
Activity Hub requires N sequential or parallel HTTP calls (one per subscribed room),
which creates unnecessary backend stress and poor UX latency that scales linearly
with the number of rooms a user has joined.
The three files that need to change to support this are:
apps/meteor/app/api/server/lib/messages.ts— makeroomIdoptional, add
global branch usingSubscriptions.findByUserIdpackages/models/src/models/Messages.ts— add global query methods
findPaginatedVisibleByMentionandfindStarredByUserpackages/models/src/models/BaseRaw.ts— addaggregatePaginatedto support
paginated aggregation pipelines at the base model level
Relevant logs:
No runtime error is thrown. The current behaviour silently fails for clients
that omit roomId — the request is rejected at the room access check with
error-not-allowed because roomId is undefined, giving no indication that
a global path is simply not implemented.
The Rocket.Chat monorepo requires a full Docker + MongoDB replica set to run
locally. The failure is verifiable directly from the source — roomId is typed
as string (required) in messages.ts, so any request without it is rejected
at the canAccessRoomAsync check before any database query runs.