fix: normalize federated asset URLs in explore and social stores

Resolve remote instance avatars and space icons at the data boundary
so they render correctly instead of 404ing against the home instance.
This commit is contained in:
Jannis Braun
2026-03-08 22:40:02 +01:00
parent b598236f82
commit c3bf635fc9
3 changed files with 24 additions and 7 deletions
+2
View File
@@ -335,12 +335,14 @@ function handleEvent(origin: string, event: ServerEvent): void {
// ─── Social events (all origins — federation) ──────────────────────────
case 'friend_request_received': {
if (!isHome && event.request.user) normalizeUserAssets(event.request.user, origin);
const { addIncomingRequest } = useSocialStore.getState();
addIncomingRequest(event.request, origin);
break;
}
case 'friend_request_accepted': {
if (!isHome) normalizeUserAssets(event.friend, origin);
const { addFriendFromAccepted } = useSocialStore.getState();
addFriendFromAccepted(event.friend, event.requestId, origin);
break;
+4
View File
@@ -1,6 +1,7 @@
import { create } from 'zustand';
import type { ExploreSpace, JoinRequest, SpaceWithChannelsAndMembers } from '@backspace/shared';
import { api } from '../api/client';
import { resolveAssetUrl } from '../utils/assetUrls';
import { useInstanceStore } from './instanceStore';
import { useSpaceStore } from './spaceStore';
@@ -89,6 +90,9 @@ export const useExploreStore = create<ExploreState>((set, get) => ({
const key = `${space.id}:${origin}`;
if (seen.has(key)) continue;
seen.add(key);
if (origin && space.icon) {
space.icon = resolveAssetUrl(space.icon, origin) ?? space.icon;
}
allSpaces.push({ ...space, _instanceOrigin: origin, joined: space.joined ?? false });
}
}
+18 -7
View File
@@ -2,6 +2,7 @@ import { create } from 'zustand';
import type { Friend, FriendRequest, User } from '@backspace/shared';
import { api } from '../api/client';
import { useInstanceStore } from './instanceStore';
import { normalizeUserAssets } from '../utils/assetUrls';
// ─── Tagged types (origin tracking for federation) ───────────────────────────
@@ -66,6 +67,7 @@ export const useSocialStore = create<SocialState>((set, get) => ({
const key = `${friend.id}:${origin}`;
if (seen.has(key)) continue;
seen.add(key);
if (origin) normalizeUserAssets(friend, origin);
allFriends.push({ ...friend, _instanceOrigin: origin });
}
}
@@ -99,6 +101,7 @@ export const useSocialStore = create<SocialState>((set, get) => ({
const key = `${request.id}:${origin}`;
if (seen.has(key)) continue;
seen.add(key);
if (origin && request.user) normalizeUserAssets(request.user, origin);
allRequests.push({ ...request, _instanceOrigin: origin });
}
}
@@ -217,22 +220,30 @@ export const useSocialStore = create<SocialState>((set, get) => ({
const instances = useInstanceStore.getState().instances;
const connectedInstances = instances.filter(i => i.status === 'connected');
const results = await Promise.allSettled([
api.social.search(query),
...connectedInstances.map(inst => inst.api.social.search(query)),
]);
// Pair each promise with its origin for asset normalization
const searches: { promise: Promise<User[]>; origin: string }[] = [
{ promise: api.social.search(query), origin: '' },
...connectedInstances.map(inst => ({
promise: inst.api.social.search(query),
origin: inst.origin,
})),
];
const results = await Promise.allSettled(searches.map(s => s.promise));
const allUsers: User[] = [];
const seen = new Set<string>();
for (const result of results) {
if (result.status !== 'fulfilled') continue;
results.forEach((result, i) => {
if (result.status !== 'fulfilled') return;
const origin = searches[i]!.origin;
for (const user of result.value) {
if (seen.has(user.id)) continue;
seen.add(user.id);
if (origin) normalizeUserAssets(user, origin);
allUsers.push(user);
}
}
});
return allUsers;
} catch (err) {