From 73a4b69fe6d80bbd6c66f60aaee6195434cf79a6 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Thu, 26 Mar 2026 03:59:59 +0100 Subject: [PATCH] fix(federation): auto-peering on reconnect and DM deduplication across origins --- packages/web/src/stores/instanceStore.ts | 3 ++ packages/web/src/stores/spaceStore.ts | 37 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/web/src/stores/instanceStore.ts b/packages/web/src/stores/instanceStore.ts index ad3fa643..2f802181 100644 --- a/packages/web/src/stores/instanceStore.ts +++ b/packages/web/src/stores/instanceStore.ts @@ -651,6 +651,9 @@ export const useInstanceStore = create((set, get) => ({ syncProfileToRemote(connectedInstance).catch((err) => { console.warn(`[ProfileSync] Reconnect sync to ${origin} failed:`, err); }); + + // Initiate server-to-server peering for DM relay (non-fatal, idempotent) + api.federation.initiatePeering({ remoteOrigin: origin }).catch(() => {}); } catch (err) { if (isNetworkError(err)) { // Instance unreachable (NAT hairpinning, DNS, server down) — token may still be valid diff --git a/packages/web/src/stores/spaceStore.ts b/packages/web/src/stores/spaceStore.ts index c08165bb..6a2df4b0 100644 --- a/packages/web/src/stores/spaceStore.ts +++ b/packages/web/src/stores/spaceStore.ts @@ -668,9 +668,44 @@ export const useSpaceStore = create((set, get) => ({ }); const mergedDms = [...existingDmsFromOtherOrigins, ...incomingDms]; + // Deduplicate DMs: if the same 1-on-1 conversation exists from multiple origins, + // prefer the home-origin copy (empty string origin = home). This handles the case + // where the federation relay created a local copy of a remote DM. + const deduplicatedDms: typeof mergedDms = []; + const seenPairs = new Map(); // canonicalKey -> index in deduplicatedDms + + for (const dm of mergedDms) { + if (dm.members.length !== 2) { + // Group DMs: no dedup + deduplicatedDms.push(dm); + continue; + } + + // Build a canonical key from the two members' homeUserIds + const memberIds = dm.members.map(m => m.homeUserId || m.id).sort(); + const canonicalKey = memberIds.join(':'); + const dmOrigin = channelOriginMap.get(dm.id) ?? ''; + + const existingIdx = seenPairs.get(canonicalKey); + if (existingIdx !== undefined) { + // Duplicate found — keep the home-origin copy + const existingDm = deduplicatedDms[existingIdx]!; + const existingOrigin = channelOriginMap.get(existingDm.id) ?? ''; + + if (dmOrigin === '' && existingOrigin !== '') { + // New one is home, existing is remote — replace with home copy + deduplicatedDms[existingIdx] = dm; + } + // Otherwise keep existing (it's already home or first-seen) + } else { + seenPairs.set(canonicalKey, deduplicatedDms.length); + deduplicatedDms.push(dm); + } + } + const update: Partial = { spaces: mergedSpaces, - dmChannels: mergedDms, + dmChannels: deduplicatedDms, channelToSpaceMap, channelLastMessageIds, spacePermissions,