feat(federation): rollbackFriendRequestCreate + side-effect registration

This commit is contained in:
Jannis Braun
2026-04-25 21:51:04 +02:00
parent 9e3417485c
commit 08872f1e08
3 changed files with 164 additions and 0 deletions
@@ -12,6 +12,11 @@
* Errors thrown by callbacks are logged but not re-thrown — rollback failure
* must not prevent the outbox entry from being deleted.
*/
import { getDb } from '../db/index.js';
import * as schema from '../db/schema.js';
import { eq } from 'drizzle-orm';
import { connectionManager } from '../ws/handler.js';
type PermanentFailureCallback = (messageId: string, reason: string) => void;
const callbacks = new Map<string, PermanentFailureCallback>();
@@ -37,3 +42,54 @@ export function invokePermanentFailureCallback(eventType: string, messageId: str
export function _resetCallbacks(): void {
callbacks.clear();
}
/**
* Rollback handler for 'friend_request_create' outbox events.
*
* Deletes the pending friend request row whose relayMessageId matches the
* failed outbox message, then notifies the sender via WebSocket so the client
* can surface an appropriate error toast.
*/
export function rollbackFriendRequestCreate(messageId: string, receiverReason: string): void {
const db = getDb();
const row = db
.select()
.from(schema.friendRequests)
.where(eq(schema.friendRequests.relayMessageId, messageId))
.get();
if (!row) return; // Idempotent — no row to roll back.
// Look up recipient handle for the toast text BEFORE deleting.
const recipient = db
.select({ username: schema.users.username })
.from(schema.users)
.where(eq(schema.users.id, row.toId))
.get();
const targetHandle = recipient?.username ?? 'unknown';
db.delete(schema.friendRequests).where(eq(schema.friendRequests.id, row.id)).run();
// Reason mapping: receiver-side reason → client-facing reason.
// 'recipient_not_found' → 'user_not_found' (the looked-up identity vanished)
// anything else → 'peer_rejected' (catch-all)
const reason: 'user_not_found' | 'peer_rejected' =
receiverReason === 'recipient_not_found' ? 'user_not_found' : 'peer_rejected';
const message =
reason === 'user_not_found'
? `User ${targetHandle} no longer exists on the remote instance.`
: `Friend request to ${targetHandle} was rejected by the remote instance.`;
connectionManager.sendToUser(row.fromId, {
type: 'friend_request_relay_failed',
requestId: row.id,
reason,
message,
targetHandle,
});
}
// Register the callback at module-load time so the worker invokes it.
registerPermanentFailureCallback('friend_request_create', rollbackFriendRequestCreate);