refactor(federation): rename canonicalDmPairId to computeFederatedId

This commit is contained in:
Jannis Braun
2026-03-26 20:08:37 +01:00
parent 6f901f4a7a
commit 0742c37ca1
2 changed files with 34 additions and 27 deletions
+20 -21
View File
@@ -9,7 +9,7 @@ import { config } from '../config.js';
import { connectionManager } from '../ws/handler.js'; import { connectionManager } from '../ws/handler.js';
import { sanitizeUser } from '../utils/sanitize.js'; import { sanitizeUser } from '../utils/sanitize.js';
import { deleteAttachmentFiles } from '../utils/fileCleanup.js'; import { deleteAttachmentFiles } from '../utils/fileCleanup.js';
import { canonicalDmPairId, getDmParticipants } from '../utils/federationOutbox.js'; import { computeFederatedId, getDmParticipants } from '../utils/federationOutbox.js';
import { getDmMessageWithUser } from './dm.js'; import { getDmMessageWithUser } from './dm.js';
import type { FederationRelayRequest, FederationRelayResponse, FederationRelayEvent, FederationRelayAttachment, FederationSyncRequest, FederationSyncResponse, DmMessageWithUser } from '@backspace/shared'; import type { FederationRelayRequest, FederationRelayResponse, FederationRelayEvent, FederationRelayAttachment, FederationSyncRequest, FederationSyncResponse, DmMessageWithUser } from '@backspace/shared';
@@ -526,11 +526,12 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
limit = Math.max(1, Math.min(500, Math.floor(limit))); limit = Math.max(1, Math.min(500, Math.floor(limit)));
// 3. Determine which DM channels to sync. // 3. Determine which DM channels to sync.
// Use canonical_pair_id: any channel with a pair ID is a federated 1-on-1 DM // Use federated_id: any channel with a federated ID is a federated DM
// that should be synced. The peer's relay endpoint will create the channel // that should be synced. The peer's relay endpoint will create the channel
// if it doesn't exist, or match by canonical_pair_id if it does. // if it doesn't exist, or match by federated_id if it does.
const sharedChannelRows = rawDb.prepare(` const sharedChannelRows = rawDb.prepare(`
SELECT id as dm_channel_id FROM dm_channels WHERE canonical_pair_id IS NOT NULL SELECT id as dm_channel_id FROM dm_channels
WHERE federated_id IS NOT NULL AND deleted_at IS NULL
`).all() as Array<{ dm_channel_id: string }>; `).all() as Array<{ dm_channel_id: string }>;
const sharedChannelIds = sharedChannelRows.map(r => r.dm_channel_id); const sharedChannelIds = sharedChannelRows.map(r => r.dm_channel_id);
@@ -818,25 +819,24 @@ function resolveLocalUser(
} }
/** /**
* Find or create a local DM channel for a federated 1-on-1 pair. * Find or create a local DM channel for a federated DM.
* Uses canonical_pair_id for deterministic cross-instance lookup. * Uses federated_id for deterministic cross-instance lookup.
*/ */
function findOrCreateDmChannel( function findOrCreateDmChannel(
canonicalPairId: string, federatedId: string,
localUserIdA: string, localUserIds: string[],
localUserIdB: string,
db: ReturnType<typeof getDb>, db: ReturnType<typeof getDb>,
): string { ): string {
// Try to find existing channel by canonical pair ID // Try to find existing channel by federated ID
const existing = db const existing = db
.select() .select()
.from(schema.dmChannels) .from(schema.dmChannels)
.where(eq(schema.dmChannels.canonicalPairId, canonicalPairId)) .where(eq(schema.dmChannels.federatedId, federatedId))
.get(); .get();
if (existing) { if (existing) {
// Ensure both users are members (they might have been removed) // Ensure all users are members (they might have been removed)
for (const userId of [localUserIdA, localUserIdB]) { for (const userId of localUserIds) {
const member = db const member = db
.select() .select()
.from(schema.dmMembers) .from(schema.dmMembers)
@@ -861,19 +861,19 @@ function findOrCreateDmChannel(
return existing.id; return existing.id;
} }
// Create new DM channel with canonical pair ID // Create new DM channel with federated ID
const channelId = generateSnowflake(); const channelId = generateSnowflake();
const now = Date.now(); const now = Date.now();
db.insert(schema.dmChannels) db.insert(schema.dmChannels)
.values({ .values({
id: channelId, id: channelId,
canonicalPairId, federatedId,
createdAt: now, createdAt: now,
}) })
.run(); .run();
for (const userId of [localUserIdA, localUserIdB]) { for (const userId of localUserIds) {
db.insert(schema.dmMembers) db.insert(schema.dmMembers)
.values({ .values({
dmChannelId: channelId, dmChannelId: channelId,
@@ -993,15 +993,14 @@ function processCreateEvent(
} }
const authorUser = authorEntry.localUser; const authorUser = authorEntry.localUser;
// Compute canonical pair ID from participants' home user IDs and find/create channel // Compute federated ID from participants' home user IDs and find/create channel
const pairId = canonicalDmPairId( const federatedId = computeFederatedId(
resolvedParticipants[0]!.homeUserId, resolvedParticipants[0]!.homeUserId,
resolvedParticipants[1]!.homeUserId, resolvedParticipants[1]!.homeUserId,
); );
const localDmChannelId = findOrCreateDmChannel( const localDmChannelId = findOrCreateDmChannel(
pairId, federatedId,
resolvedParticipants[0]!.localUser.id, [resolvedParticipants[0]!.localUser.id, resolvedParticipants[1]!.localUser.id],
resolvedParticipants[1]!.localUser.id,
db, db,
); );
+12 -4
View File
@@ -201,13 +201,21 @@ export function queueOutboxEvent(
} }
/** /**
* Compute a deterministic canonical DM pair ID from two home user IDs. * Compute a federated ID for a DM channel.
* The pair is sorted lexicographically before hashing to ensure the same *
* result regardless of argument order. Returns first 32 hex chars of SHA-256. * For 1-on-1 DMs: deterministic SHA-256 hash of 2 sorted home user IDs (backward compatible).
* For group DMs: call with no arguments to generate a new UUID.
*/ */
export function canonicalDmPairId(homeUserIdA: string, homeUserIdB: string): string { export function computeFederatedId(homeUserIdA: string, homeUserIdB: string): string;
export function computeFederatedId(): string;
export function computeFederatedId(homeUserIdA?: string, homeUserIdB?: string): string {
if (homeUserIdA && homeUserIdB) {
// 1-on-1: deterministic pair hash (backward compatible with canonicalDmPairId)
const sorted = [homeUserIdA, homeUserIdB].sort(); const sorted = [homeUserIdA, homeUserIdB].sort();
return crypto.createHash('sha256').update(sorted.join(':')).digest('hex').slice(0, 32); return crypto.createHash('sha256').update(sorted.join(':')).digest('hex').slice(0, 32);
}
// Group: origin-assigned UUID
return crypto.randomUUID();
} }
/** /**