fix(web): friend-add error toasts surface raw codes — read err.message, not err.body

The T19/T20 catch blocks looked for an `.body` property on thrown errors
to extract the structured error code. The shared API client (api/client.ts:298)
actually throws `new Error(body.error)` — the code lives in `err.message`,
and there's no `.body` attached.

Live E2E (T22 scenario 2) caught this: typing alice@orbit against an
awaiting_approval peer surfaced the raw code 'peer_pending_approval' as
the toast text instead of the human-readable mapServerErrorToMessage
output. Same defect would have hit every server-error toast on both
FriendsPage (AddFriend + UserDiscoverCard) and UserProfileModal.

Catch blocks now use err.message as both the code and the fallback text;
the inline comment points at the API client throw site so the contract
is documented at the consumer.
This commit is contained in:
Jannis Braun
2026-04-25 23:17:19 +02:00
parent 4a939b743f
commit fe969a7d96
2 changed files with 14 additions and 28 deletions
@@ -476,16 +476,12 @@ function AddFriendTab({
addToast('Friend request sent!', 'success');
setQuery('');
} catch (err) {
const errorBody = (err as { body?: unknown })?.body;
let code: string | undefined;
let message: string | undefined;
if (errorBody && typeof errorBody === 'object') {
code = (errorBody as { error?: string }).error;
message = (errorBody as { message?: string }).message;
}
// Fall back to the Error message if the server didn't send a structured body.
const fallback = message ?? (err instanceof Error ? err.message : undefined);
addToast(mapServerErrorToMessage(code, fallback, query.trim()), 'warning');
// The shared API client throws `new Error(body.error)` for non-2xx
// responses (api/client.ts:298), so `err.message` carries the server's
// error code (e.g. 'peer_pending_approval'). It also doubles as fallback
// text if the code is unrecognized by mapServerErrorToMessage.
const code = err instanceof Error ? err.message : undefined;
addToast(mapServerErrorToMessage(code, code, query.trim()), 'warning');
} finally {
setDirectAddLoading(false);
}
@@ -643,15 +639,9 @@ function UserDiscoverCard({
const requestId = await sendFriendRequest(username);
onRelationshipChange(user.id, user._instanceOrigin, 'outbound_pending', requestId);
} catch (err) {
const errorBody = (err as { body?: unknown })?.body;
let code: string | undefined;
let message: string | undefined;
if (errorBody && typeof errorBody === 'object') {
code = (errorBody as { error?: string }).error;
message = (errorBody as { message?: string }).message;
}
const fallback = message ?? (err instanceof Error ? err.message : 'Failed to send request');
setError(mapServerErrorToMessage(code, fallback, username));
// See handleDirectAdd above — err.message is the server error code.
const code = err instanceof Error ? err.message : undefined;
setError(mapServerErrorToMessage(code, code ?? 'Failed to send request', username));
} finally {
setActionLoading(false);
}
@@ -186,15 +186,11 @@ export function UserProfileModal() {
try {
await sendFriendRequest(user.username);
} catch (err) {
const errorBody = (err as { body?: unknown })?.body;
let code: string | undefined;
let message: string | undefined;
if (errorBody && typeof errorBody === 'object') {
code = (errorBody as { error?: string }).error;
message = (errorBody as { message?: string }).message;
}
const fallback = message ?? (err instanceof Error ? err.message : undefined);
addToast(mapServerErrorToMessage(code, fallback, user.username), 'warning');
// The shared API client throws `new Error(body.error)` for non-2xx
// responses (api/client.ts:298), so err.message carries the server's
// error code (e.g. 'peer_pending_approval').
const code = err instanceof Error ? err.message : undefined;
addToast(mapServerErrorToMessage(code, code, user.username), 'warning');
} finally {
setFriendActionLoading(false);
}