diff --git a/packages/server/src/routes/federation.ts b/packages/server/src/routes/federation.ts index e77fa108..4c1f6435 100644 --- a/packages/server/src/routes/federation.ts +++ b/packages/server/src/routes/federation.ts @@ -775,6 +775,10 @@ async function handleOutboundDeny( type: 'peering_notification_received' as const, kind: 'denied', }); + // Subscriber row is about to cascade-delete; refresh the user's pending list. + connectionManager.sendToUser(sub.userId, { + type: 'peering_subscription_changed' as const, + }); } // Cascade-delete clears subscribers via onDelete: 'cascade'. diff --git a/packages/server/src/utils/federationPeerActivation.ts b/packages/server/src/utils/federationPeerActivation.ts index 04dd71eb..afe32f78 100644 --- a/packages/server/src/utils/federationPeerActivation.ts +++ b/packages/server/src/utils/federationPeerActivation.ts @@ -246,6 +246,11 @@ async function fanoutOutboundSubscribers(peerId: string): Promise { type: 'peering_notification_received' as const, kind: 'approved', }); + // The subscriber row is about to cascade-delete; tell the user's UI to + // refetch its pending list so the now-stale row disappears. + connectionManager.sendToUser(sub.userId, { + type: 'peering_subscription_changed' as const, + }); } // Cascade-deletes subscriber rows via onDelete: 'cascade'. diff --git a/packages/server/src/utils/storageJanitor.ts b/packages/server/src/utils/storageJanitor.ts index 164bafe1..df636113 100644 --- a/packages/server/src/utils/storageJanitor.ts +++ b/packages/server/src/utils/storageJanitor.ts @@ -460,6 +460,7 @@ export async function cleanupExpiredApprovalRequests(): Promise { if (expiredRequests.length === 0) return 0; const { getOurOrigin, buildFederationHeaders } = await import('./federationAuth.js'); + const { connectionManager } = await import('../ws/handler.js'); const ourOrigin = getOurOrigin(); const now = Date.now(); let deletedCount = 0; @@ -486,6 +487,16 @@ export async function cleanupExpiredApprovalRequests(): Promise { readAt: null, }) .run(); + // Subscriber row is about to cascade-delete; if the user is online, + // refresh their pending list AND notification list. Offline users see + // both on next page load via the GET endpoints (persistence guarantee). + connectionManager.sendToUser(sub.userId, { + type: 'peering_notification_received' as const, + kind: 'expired', + }); + connectionManager.sendToUser(sub.userId, { + type: 'peering_subscription_changed' as const, + }); } db.delete(schema.peerApprovalRequests) .where(eq(schema.peerApprovalRequests.id, req.id))