fix(federation): compute relay targets before member deletion in leave handler

getGroupDmTargetOrigins() was called after the leaving user's dm_members row
was deleted, causing their instance to be excluded from member_remove and
ownership_transfer relay fan-out if they were the only member from that peer.
Move the target origins and leaving user lookups to before the deletion.
This commit is contained in:
Jannis Braun
2026-03-26 20:19:55 +01:00
parent f072568b8a
commit 6a83b6c6a5
2 changed files with 16 additions and 12 deletions
+15 -12
View File
@@ -797,6 +797,14 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
// Check DM channel ownership before leaving
const dmChannel = db.select().from(schema.dmChannels).where(eq(schema.dmChannels.id, id)).get();
// Compute federation targets BEFORE member deletion so the leaving user's peer is included
let fedTargetOrigins: string[] | undefined;
let leavingUser: typeof schema.users.$inferSelect | undefined;
if (isFederationRelayEnabled() && dmChannel?.federatedId) {
fedTargetOrigins = getGroupDmTargetOrigins(id);
leavingUser = db.select().from(schema.users).where(eq(schema.users.id, request.userId)).get() ?? undefined;
}
// Delete dm_members row
db.delete(schema.dmMembers)
.where(and(
@@ -812,18 +820,14 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
)).run();
// Federation: relay member_remove (leave) to peers
const dmChannelForFed = db.select().from(schema.dmChannels).where(eq(schema.dmChannels.id, id)).get();
let leavingUser: typeof schema.users.$inferSelect | undefined;
let targetOrigins: string[] | undefined;
if (isFederationRelayEnabled() && dmChannelForFed?.federatedId) {
if (isFederationRelayEnabled() && dmChannel?.federatedId) {
const domainOrigin = getOurOrigin();
leavingUser = db.select().from(schema.users).where(eq(schema.users.id, request.userId)).get();
const memberRemovePayload: FederationRelayEvent = {
eventType: 'member_remove',
dmChannelId: id,
messageId: `member_remove:${request.userId}:${Date.now()}`,
federatedId: dmChannelForFed.federatedId,
federatedId: dmChannel.federatedId,
encryptionVersion: 0,
timestamp: Date.now(),
membership: {
@@ -839,7 +843,6 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
},
};
targetOrigins = getGroupDmTargetOrigins(id);
appendMutationLog(
memberRemovePayload.messageId,
id,
@@ -851,7 +854,7 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
id,
'member_remove',
JSON.stringify(memberRemovePayload),
targetOrigins,
fedTargetOrigins,
);
}
@@ -871,10 +874,10 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
.run();
// Federation: relay ownership transfer
if (isFederationRelayEnabled() && dmChannelForFed?.federatedId) {
if (isFederationRelayEnabled() && dmChannel?.federatedId) {
const domainOrigin = getOurOrigin();
const newOwnerUser = db.select().from(schema.users).where(eq(schema.users.id, nextOwner.userId)).get();
const prevOwnerUser = leavingUser; // Already queried above in step D
const prevOwnerUser = leavingUser; // Already queried above before member deletion
// Update federated owner columns
db.update(schema.dmChannels)
@@ -889,7 +892,7 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
eventType: 'ownership_transfer',
dmChannelId: id,
messageId: `ownership_transfer:${nextOwner.userId}:${Date.now()}`,
federatedId: dmChannelForFed.federatedId,
federatedId: dmChannel.federatedId,
encryptionVersion: 0,
timestamp: Date.now(),
ownership: {
@@ -915,7 +918,7 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
id,
'ownership_transfer',
JSON.stringify(transferPayload),
targetOrigins,
fedTargetOrigins,
);
}
}
@@ -286,6 +286,7 @@ export function KeybindsPanel() {
return (
<div className="space-y-5">
<h2 className="text-lg font-semibold text-txt-primary mb-6">Keybinds</h2>
{/* macOS Accessibility Warning */}
{isElectronMac() && accessibilityTrusted === false && (
<div className="rounded-lg bg-amber-500/10 border border-amber-500/20 p-3.5">