fix: send dm_channel_created to local users when bootstrapping federated group DM

When processMemberAddEvent bootstrapped a new group DM channel for the
first time on a receiving instance, local users were never notified via
WebSocket — only dm_member_added was broadcast, which requires the client
to already know about the channel. Now, after bootstrap, dm_channel_created
is sent directly to each local connected member, and the redundant
dm_member_added broadcast is skipped for the bootstrap path.
This commit is contained in:
Jannis Braun
2026-03-27 05:35:29 +01:00
parent 72aa318928
commit 8e589f41fd
+33 -1
View File
@@ -1,6 +1,6 @@
import type { FastifyInstance } from 'fastify'; import type { FastifyInstance } from 'fastify';
import { randomBytes } from 'node:crypto'; import { randomBytes } from 'node:crypto';
import { eq, and, or, isNull } from 'drizzle-orm'; import { eq, and, or, isNull, inArray } from 'drizzle-orm';
import { authenticate, requireAdmin } from '../utils/auth.js'; import { authenticate, requireAdmin } from '../utils/auth.js';
import { generateHmacSecret, getOurOrigin, parseFederationHeaders, verifySignature } from '../utils/federationAuth.js'; import { generateHmacSecret, getOurOrigin, parseFederationHeaders, verifySignature } from '../utils/federationAuth.js';
import { generateSnowflake } from '../utils/snowflake.js'; import { generateSnowflake } from '../utils/snowflake.js';
@@ -1637,6 +1637,8 @@ function processMemberAddEvent(
.where(eq(schema.dmChannels.federatedId, event.federatedId)) .where(eq(schema.dmChannels.federatedId, event.federatedId))
.get(); .get();
let bootstrapped = false;
// Bootstrap: channel doesn't exist yet — create from group metadata // Bootstrap: channel doesn't exist yet — create from group metadata
if (!channel && event.group) { if (!channel && event.group) {
const channelId = generateSnowflake(); const channelId = generateSnowflake();
@@ -1682,6 +1684,34 @@ function processMemberAddEvent(
.where(eq(schema.dmChannels.id, channelId)).get(); .where(eq(schema.dmChannels.id, channelId)).get();
console.log(`[federation] Bootstrapped group DM channel ${channelId} (federated_id: ${event.federatedId})`); console.log(`[federation] Bootstrapped group DM channel ${channelId} (federated_id: ${event.federatedId})`);
bootstrapped = true;
// Build full DmChannel response for dm_channel_created
const memberRows = db.select()
.from(schema.dmMembers)
.where(eq(schema.dmMembers.dmChannelId, channelId))
.all();
const memberUserIds = memberRows.map(m => m.userId);
const memberUsers = memberUserIds.length > 0
? db.select().from(schema.users).where(inArray(schema.users.id, memberUserIds)).all()
: [];
const bootstrapResult = {
id: channelId,
ownerId,
createdAt: now,
members: memberUsers.map(u => sanitizeUser(u)),
lastMessage: null,
};
// Send dm_channel_created to all local WebSocket-connected members
for (const mu of memberUsers) {
connectionManager.sendToUser(mu.id, {
type: 'dm_channel_created',
dmChannel: bootstrapResult,
});
}
} }
if (!channel) { if (!channel) {
@@ -1735,12 +1765,14 @@ function processMemberAddEvent(
}).run(); }).run();
} }
if (!bootstrapped) {
// Broadcast to local WebSocket clients // Broadcast to local WebSocket clients
connectionManager.sendToDmMembers(channel.id, { connectionManager.sendToDmMembers(channel.id, {
type: 'dm_member_added', type: 'dm_member_added',
dmChannelId: channel.id, dmChannelId: channel.id,
user: sanitizeUser(localUser), user: sanitizeUser(localUser),
}); });
}
accepted.push(event.messageId); accepted.push(event.messageId);
} }