Skip to content
Merged
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
47 changes: 4 additions & 43 deletions ee/packages/federation-matrix/src/api/_matrix/invite.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { HomeserverServices, RoomService, StateService } from '@hs/federation-sdk';
import type { PersistentEventBase } from '@hs/room';
import type { PduMembershipEventContent, PersistentEventBase, RoomVersion } from '@hs/room';
import { Room } from '@rocket.chat/core-services';
import type { IUser, UserStatus } from '@rocket.chat/core-typings';
import { Router } from '@rocket.chat/http-router';
Expand Down Expand Up @@ -143,52 +143,13 @@ async function runWithBackoff(fn: () => Promise<void>, delaySec = 5) {
}
}

async function isDirectMessage(matrixRoom: unknown, inviteEvent: PersistentEventBase): Promise<boolean> {
try {
const room = matrixRoom as { getEvent?: (type: string) => { content?: { is_direct?: boolean } } };
const creationEvent = room.getEvent?.('m.room.create');
if (creationEvent?.content?.is_direct) {
return true;
}

const roomWithEvents = matrixRoom as { getEvents?: (type: string) => Array<{ content?: { membership?: string }; stateKey?: string }> };
const memberEvents = roomWithEvents.getEvents?.('m.room.member');
const joinedMembers =
memberEvents?.filter((event) => event.content?.membership === 'join' || event.content?.membership === 'invite') || [];

if (joinedMembers.length === 2) {
return true;
}

const uniqueUsers = new Set<string>();
if (memberEvents) {
for (const event of memberEvents) {
if (event.content?.membership === 'join' || event.content?.membership === 'invite') {
if (event.stateKey) {
uniqueUsers.add(event.stateKey);
}
}
}
}
uniqueUsers.add(inviteEvent.sender);
if (inviteEvent.stateKey) {
uniqueUsers.add(inviteEvent.stateKey);
}

return uniqueUsers.size <= 2;
} catch (error) {
console.log('Could not determine if room is DM:', error);
return false;
}
}

async function joinRoom({
inviteEvent,
user, // ours trying to join the room
room,
state,
}: {
inviteEvent: PersistentEventBase;
inviteEvent: PersistentEventBase<RoomVersion, 'm.room.member'>;
user: IUser;
room: RoomService;
state: StateService;
Expand All @@ -208,7 +169,7 @@ async function joinRoom({
}

// we only understand these two types of rooms, plus direct messages
const isDM = await isDirectMessage(matrixRoom, inviteEvent);
const isDM = inviteEvent.getContent<PduMembershipEventContent>().is_direct;
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

Don’t rely solely on is_direct; normalize and handle absence.

Some homeservers omit is_direct on invites or use isDirect. Treat it as optional and normalize; otherwise non‑DM rooms may be misclassified, hitting the “neither public, private, nor direct” error path.

Apply:

- const isDM = inviteEvent.getContent<PduMembershipEventContent>().is_direct;
+ const content = inviteEvent.getContent<PduMembershipEventContent & { is_direct?: boolean; isDirect?: boolean }>();
+ const isDM = (content.is_direct ?? content.isDirect) === true;

Optionally keep a lightweight heuristic fallback if the flag is absent (e.g., fall back to previous helper or room state) in a follow-up.

📝 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
const isDM = inviteEvent.getContent<PduMembershipEventContent>().is_direct;
const content = inviteEvent.getContent<PduMembershipEventContent & { is_direct?: boolean; isDirect?: boolean }>();
const isDM = (content.is_direct ?? content.isDirect) === true;
🤖 Prompt for AI Agents
In ee/packages/federation-matrix/src/api/_matrix/invite.ts around line 172, the
code currently reads the invite's is_direct directly and may misclassify rooms
when homeservers omit or rename that field; update the logic to treat the field
as optional and normalize both possible names (is_direct and isDirect), coerce
to a boolean (default to false when absent), and ensure downstream branching
uses this normalized value; optionally add a light heuristic fallback (e.g.,
call the existing helper or inspect room state) when no explicit flag is
present, but keep the immediate fix limited to normalization and safe defaulting
so invites aren't incorrectly classified.


if (!isDM && !matrixRoom.isPublic() && !matrixRoom.isInviteOnly()) {
throw new Error('room is neither public, private, nor direct message - rocketchat is unable to join for now');
Expand Down Expand Up @@ -392,7 +353,7 @@ export const getMatrixInviteRoutes = (services: HomeserverServices) => {
const inviteEvent = await invite.processInvite(event, roomId, eventId, roomVersion);

void startJoiningRoom({
inviteEvent,
inviteEvent: inviteEvent as PersistentEventBase<RoomVersion, 'm.room.member'>, // TODO: change the processInvite return type
user: ourUser,
room,
state,
Expand Down
Loading