feat(web): WS handlers for friend_request_sent + friend_request_relay_failed

Multi-tab sync: friend_request_sent appends the new outbound request to
socialStore (deduped by id+origin), no toast.

Async rollback: friend_request_relay_failed removes the row by id and
surfaces a warning toast with the target handle and reason. Wires the
client side of the rollback hook from T10.
This commit is contained in:
Jannis Braun
2026-04-25 22:29:45 +02:00
parent b28bf6646d
commit 9d3f75b33c
2 changed files with 33 additions and 0 deletions
+21
View File
@@ -862,6 +862,27 @@ function handleEvent(origin: string, event: ServerEvent): void {
break; break;
} }
case 'friend_request_sent': {
// Multi-tab sync: another tab/device of the same user just created an outbound request.
if (!isHome && event.request.user) normalizeUserAssets(event.request.user, origin);
const { addOutboundRequest } = useSocialStore.getState();
addOutboundRequest(event.request, origin);
break;
}
case 'friend_request_relay_failed': {
// Async rollback notification: the federated friend_request_create was permanently rejected.
// Drop the optimistic row and surface a warning toast.
const { removeRequestById } = useSocialStore.getState();
removeRequestById(event.requestId, origin);
const { addToast } = useUIStore.getState();
addToast(
`Friend request to ${event.targetHandle} could not be delivered: ${event.message}`,
'warning',
);
break;
}
case 'friend_request_accepted': { case 'friend_request_accepted': {
if (!isHome) normalizeUserAssets(event.friend, origin); if (!isHome) normalizeUserAssets(event.friend, origin);
const { addFriendFromAccepted } = useSocialStore.getState(); const { addFriendFromAccepted } = useSocialStore.getState();
+12
View File
@@ -57,6 +57,7 @@ interface SocialState {
removeFriend: (id: string) => Promise<void>; removeFriend: (id: string) => Promise<void>;
searchUsers: (query: string) => Promise<TaggedUser[]>; searchUsers: (query: string) => Promise<TaggedUser[]>;
addIncomingRequest: (request: FriendRequest, origin: string) => void; addIncomingRequest: (request: FriendRequest, origin: string) => void;
addOutboundRequest: (request: FriendRequest, origin: string) => void;
addFriendFromAccepted: (friend: Friend, requestId: string, origin: string) => void; addFriendFromAccepted: (friend: Friend, requestId: string, origin: string) => void;
updateFriendPresence: (userId: string, status: string) => void; updateFriendPresence: (userId: string, status: string) => void;
updateFriendProfile: (user: User) => void; updateFriendProfile: (user: User) => void;
@@ -343,6 +344,17 @@ export const useSocialStore = create<SocialState>((set, get) => ({
}); });
}, },
// Called from WS handler for multi-tab sync when this user creates an outbound request
addOutboundRequest: (request: FriendRequest, origin: string) => {
set((state) => {
const canonicalId = request.user?.homeUserId ?? request.user?.id;
if (canonicalId && state.requests.some(r => (r.user?.homeUserId ?? r.user?.id) === canonicalId)) {
return state;
}
return { requests: [...state.requests, { ...request, _instanceOrigin: origin }] };
});
},
// Called from WS handler when someone accepts your friend request // Called from WS handler when someone accepts your friend request
addFriendFromAccepted: (friend: Friend, requestId: string, origin: string) => { addFriendFromAccepted: (friend: Friend, requestId: string, origin: string) => {
set((state) => { set((state) => {