From f7ba3916788907ef4cf716532bba93614344fdf8 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:26:23 +0100 Subject: [PATCH] feat(federation): handle group DM messages via federatedId lookup in relay processor In processCreateEvent, branch on event.federatedId: group DM messages now look up the pre-bootstrapped local channel by federatedId instead of computing a pair hash from two participants. In queueDmRelay, fetch the channel's federatedId and ownerId and include federatedId in the outgoing relay payload for group DMs so receiving instances can route correctly. --- packages/server/src/routes/federation.ts | 43 ++++++++++++++----- packages/server/src/utils/federationOutbox.ts | 9 ++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/server/src/routes/federation.ts b/packages/server/src/routes/federation.ts index f27ff216..62e7c567 100644 --- a/packages/server/src/routes/federation.ts +++ b/packages/server/src/routes/federation.ts @@ -1002,16 +1002,39 @@ function processCreateEvent( } const authorUser = authorEntry.localUser; - // Compute federated ID from participants' home user IDs and find/create channel - const federatedId = computeFederatedId( - resolvedParticipants[0]!.homeUserId, - resolvedParticipants[1]!.homeUserId, - ); - const localDmChannelId = findOrCreateDmChannel( - federatedId, - [resolvedParticipants[0]!.localUser.id, resolvedParticipants[1]!.localUser.id], - db, - ); + // Resolve local DM channel: group DMs carry a federatedId and the channel + // must already exist (bootstrapped by a prior member_add event); 1-on-1 DMs + // are computed from the pair of home user IDs and created on demand. + let localDmChannelId: string; + + if (event.federatedId) { + // Group DM: look up by federated_id (channel must already exist from member_add bootstrap) + const channel = db + .select() + .from(schema.dmChannels) + .where(and( + eq(schema.dmChannels.federatedId, event.federatedId), + isNull(schema.dmChannels.deletedAt), + )) + .get(); + + if (!channel) { + rejected.push({ messageId: event.messageId, reason: 'channel_not_found' }); + return; + } + localDmChannelId = channel.id; + } else { + // 1-on-1 DM: compute federated_id from pair and find/create channel + const federatedId = computeFederatedId( + resolvedParticipants[0]!.homeUserId, + resolvedParticipants[1]!.homeUserId, + ); + localDmChannelId = findOrCreateDmChannel( + federatedId, + [resolvedParticipants[0]!.localUser.id, resolvedParticipants[1]!.localUser.id], + db, + ); + } // Insert the message const localMessageId = generateSnowflake(); diff --git a/packages/server/src/utils/federationOutbox.ts b/packages/server/src/utils/federationOutbox.ts index de1c7d6e..92e1db10 100644 --- a/packages/server/src/utils/federationOutbox.ts +++ b/packages/server/src/utils/federationOutbox.ts @@ -318,8 +318,17 @@ export function queueDmRelay( const targetOrigins = getGroupDmTargetOrigins(dmChannelId); + // Fetch channel to check if it's a group DM with a federatedId + const db = getDb(); + const channel = db + .select({ federatedId: schema.dmChannels.federatedId, ownerId: schema.dmChannels.ownerId }) + .from(schema.dmChannels) + .where(eq(schema.dmChannels.id, dmChannelId)) + .get(); + appendMutationLog(message.id, dmChannelId, eventType); queueOutboxEvent(message.id, dmChannelId, eventType, JSON.stringify({ + ...(channel?.federatedId && channel.ownerId ? { federatedId: channel.federatedId } : {}), message: { ...buildRelayPayload(message, message.user), attachments: attachments.length > 0 ? attachments : undefined,