fix(federation): replace DNS hairpin self-POST with direct function call (FED-005)
Extract processRelayEvents() from the relay HTTP handler and call it directly in runInitialSyncForNewPeers(), eliminating the HTTP round-trip through public DNS that failed on networks without hairpin NAT.
This commit is contained in:
@@ -419,64 +419,7 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
|
||||
}
|
||||
|
||||
// 3. Process each event
|
||||
const accepted: string[] = [];
|
||||
const rejected: Array<{ messageId: string; reason: string }> = [];
|
||||
|
||||
for (const event of body.events) {
|
||||
try {
|
||||
switch (event.eventType) {
|
||||
case 'create':
|
||||
processCreateEvent(event, sourceInstance, peer.origin, db, accepted, rejected);
|
||||
break;
|
||||
case 'update':
|
||||
processUpdateEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'delete':
|
||||
processDeleteEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'reaction_add':
|
||||
processReactionAddEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'reaction_remove':
|
||||
processReactionRemoveEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'member_add':
|
||||
processMemberAddEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'member_remove':
|
||||
processMemberRemoveEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'ownership_transfer':
|
||||
processOwnershipTransferEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_request_create':
|
||||
processFriendRequestCreateEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_request_update':
|
||||
processFriendRequestUpdateEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_request_cancel':
|
||||
processFriendRequestCancelEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_add':
|
||||
processFriendAddEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_remove':
|
||||
processFriendRemoveEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'file_rejected':
|
||||
processFileRejectedEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
default:
|
||||
rejected.push({ messageId: event.messageId, reason: 'unknown_event_type' });
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : 'unknown_error';
|
||||
console.error(`[federation-relay] Error processing event ${event.messageId}:`, errMsg);
|
||||
rejected.push({ messageId: event.messageId, reason: 'processing_error' });
|
||||
}
|
||||
}
|
||||
const { accepted, rejected } = processRelayEvents(body.events, sourceInstance, peer.origin, db);
|
||||
|
||||
// 4. Update peer status
|
||||
db.update(schema.federationPeers)
|
||||
@@ -865,6 +808,80 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Relay Event Processing (shared by HTTP handler and initial sync) ────────
|
||||
|
||||
/**
|
||||
* Process an array of federation relay events. Used by the HTTP relay endpoint
|
||||
* and directly by the initial-sync worker (which skips the HTTP round-trip).
|
||||
*/
|
||||
export function processRelayEvents(
|
||||
events: FederationRelayEvent[],
|
||||
sourceInstance: string,
|
||||
peerOrigin: string,
|
||||
db: ReturnType<typeof getDb>,
|
||||
): { accepted: string[]; rejected: Array<{ messageId: string; reason: string }> } {
|
||||
const accepted: string[] = [];
|
||||
const rejected: Array<{ messageId: string; reason: string }> = [];
|
||||
|
||||
for (const event of events) {
|
||||
try {
|
||||
switch (event.eventType) {
|
||||
case 'create':
|
||||
processCreateEvent(event, sourceInstance, peerOrigin, db, accepted, rejected);
|
||||
break;
|
||||
case 'update':
|
||||
processUpdateEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'delete':
|
||||
processDeleteEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'reaction_add':
|
||||
processReactionAddEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'reaction_remove':
|
||||
processReactionRemoveEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'member_add':
|
||||
processMemberAddEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'member_remove':
|
||||
processMemberRemoveEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'ownership_transfer':
|
||||
processOwnershipTransferEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_request_create':
|
||||
processFriendRequestCreateEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_request_update':
|
||||
processFriendRequestUpdateEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_request_cancel':
|
||||
processFriendRequestCancelEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_add':
|
||||
processFriendAddEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'friend_remove':
|
||||
processFriendRemoveEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
case 'file_rejected':
|
||||
processFileRejectedEvent(event, sourceInstance, db, accepted, rejected);
|
||||
break;
|
||||
default:
|
||||
rejected.push({ messageId: event.messageId, reason: 'unknown_event_type' });
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : 'unknown_error';
|
||||
console.error(`[federation-relay] Error processing event ${event.messageId}:`, errMsg);
|
||||
rejected.push({ messageId: event.messageId, reason: 'processing_error' });
|
||||
}
|
||||
}
|
||||
|
||||
return { accepted, rejected };
|
||||
}
|
||||
|
||||
// ─── Relay Event Processors ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ import { generateSnowflake } from './snowflake.js';
|
||||
import { getDmMessageWithUser } from '../routes/dm.js';
|
||||
import { connectionManager } from '../ws/handler.js';
|
||||
import { generateThumbnail } from './thumbnail.js';
|
||||
import { processRelayEvents } from '../routes/federation.js';
|
||||
import type { FederationRelayRequest, FederationRelayResponse, FederationRelayEvent } from '@backspace/shared';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
@@ -781,21 +782,8 @@ async function runInitialSyncForNewPeers(): Promise<void> {
|
||||
|
||||
if (data.events.length === 0) break;
|
||||
|
||||
// Relay the events through our own relay endpoint logic
|
||||
// For simplicity, POST them to ourselves
|
||||
const relayBody = JSON.stringify({
|
||||
version: 1,
|
||||
sourceInstance: peer.origin,
|
||||
events: data.events,
|
||||
});
|
||||
const relayHeaders = buildFederationHeaders(relayBody, peer.hmacSecret, peer.origin);
|
||||
|
||||
await fetch(`${ourOrigin}/api/federation/relay`, {
|
||||
method: 'POST',
|
||||
headers: relayHeaders,
|
||||
body: relayBody,
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
});
|
||||
// Process events directly — no HTTP round-trip (FED-005)
|
||||
processRelayEvents(data.events, peer.origin, peer.origin, db);
|
||||
|
||||
totalEvents += data.events.length;
|
||||
sinceTimestamp = data.checkpoint;
|
||||
@@ -825,19 +813,8 @@ async function runInitialSyncForNewPeers(): Promise<void> {
|
||||
|
||||
if (friendData.events.length === 0) break;
|
||||
|
||||
const friendRelayBody = JSON.stringify({
|
||||
version: 1,
|
||||
sourceInstance: peer.origin,
|
||||
events: friendData.events,
|
||||
});
|
||||
const friendRelayHeaders = buildFederationHeaders(friendRelayBody, peer.hmacSecret, peer.origin);
|
||||
|
||||
await fetch(`${ourOrigin}/api/federation/relay`, {
|
||||
method: 'POST',
|
||||
headers: friendRelayHeaders,
|
||||
body: friendRelayBody,
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
});
|
||||
// Process events directly — no HTTP round-trip (FED-005)
|
||||
processRelayEvents(friendData.events, peer.origin, peer.origin, db);
|
||||
|
||||
totalEvents += friendData.events.length;
|
||||
friendSinceTimestamp = friendData.checkpoint;
|
||||
|
||||
Reference in New Issue
Block a user