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,