From c3bf635fc91b2b3a944dc68e99542ee0ac03a3c9 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sun, 8 Mar 2026 22:40:02 +0100 Subject: [PATCH] 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. --- packages/web/src/hooks/useWebSocket.ts | 2 ++ packages/web/src/stores/exploreStore.ts | 4 ++++ packages/web/src/stores/socialStore.ts | 25 ++++++++++++++++++------- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/web/src/hooks/useWebSocket.ts b/packages/web/src/hooks/useWebSocket.ts index 8fd9ae21..6c8ee7ff 100644 --- a/packages/web/src/hooks/useWebSocket.ts +++ b/packages/web/src/hooks/useWebSocket.ts @@ -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; diff --git a/packages/web/src/stores/exploreStore.ts b/packages/web/src/stores/exploreStore.ts index 526c5ab7..92b8bd45 100644 --- a/packages/web/src/stores/exploreStore.ts +++ b/packages/web/src/stores/exploreStore.ts @@ -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((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 }); } } diff --git a/packages/web/src/stores/socialStore.ts b/packages/web/src/stores/socialStore.ts index 6344f4eb..a523cc42 100644 --- a/packages/web/src/stores/socialStore.ts +++ b/packages/web/src/stores/socialStore.ts @@ -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((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((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((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; 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(); - 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) {