feat(web): user-facing pending peering subscriptions and outcome notifications

Two new inline sections in the user-facing federation/connections settings
panel: 'Recent peering outcomes' (terminal-state notifications with
Retry-for-approved + Dismiss) and 'Pending peering approvals' (active
subscriber rows the user is waiting on, with Cancel). New WS handlers for
peering_subscription_changed and peering_notification_received refresh the
lists in real-time and surface a transient toast for online users. Retry
deep-link for friend_add prefills the friend-add input with the original
target handle (other reasons get Dismiss only — the gate doesn't wire
those paths yet).
This commit is contained in:
Jannis Braun
2026-04-26 22:42:53 +02:00
parent eddf2254cc
commit cebbd5c859
4 changed files with 479 additions and 4 deletions
+28
View File
@@ -14,6 +14,7 @@ import { getActiveRoom } from './useLiveKit';
import { useUIStore } from '../stores/uiStore';
import { useActivityStore } from '../stores/activityStore';
import { useDiscoverStore } from '../stores/discoverStore';
import { useFederationStore } from '../stores/federationStore';
// ─── Rejected peer origins (for unreachable member indicators) ───────────────
const rejectedPeerOrigins = new Set<string>();
@@ -781,6 +782,33 @@ function handleEvent(origin: string, event: ServerEvent): void {
break;
}
case 'peering_subscription_changed': {
// The user's pending peering-subscription set changed (admin approved/
// denied/expired the parent request, or the user cancelled a row from
// another tab). Refetch — the server is the source of truth.
void useFederationStore.getState().refetchPeeringSubscriptions();
break;
}
case 'peering_notification_received': {
// Terminal-state outcome arrived for one of the user's outbound peering
// requests. Refetch the notifications list and surface a transient
// toast — the inline list in the Connections panel is the persistent
// surface; the toast is opportunistic for online users.
void useFederationStore.getState().refetchPeeringNotifications();
const message =
event.kind === 'approved'
? 'Your peering request was approved'
: event.kind === 'denied'
? 'Your peering request was denied'
: 'Your peering request expired';
// uiStore exposes 'info' | 'warning' | 'success' — use 'success' for
// approved, 'warning' for denied/expired (no error severity exists).
const severity: 'success' | 'warning' = event.kind === 'approved' ? 'success' : 'warning';
useUIStore.getState().addToast(message, severity, 4500);
break;
}
case 'dm_message_deleted':
if (!isHome && !activePeerOrigins.has(origin)) break;
removeMessage(event.messageId, event.dmChannelId);