diff --git a/packages/web/src/api/client.ts b/packages/web/src/api/client.ts index 4f158e9e..0e5bedf5 100644 --- a/packages/web/src/api/client.ts +++ b/packages/web/src/api/client.ts @@ -23,6 +23,7 @@ import type { UpdateMemberRequest, LiveKitTokenResponse, CreateDmRequest, + AddDmMemberRequest, CreateGroupDmRequest, CreateDmMessageRequest, Friend, @@ -158,7 +159,7 @@ export class BackspaceApiClient { sendMessage: (id: string, data: CreateDmMessageRequest) => Promise; updateMessage: (id: string, data: UpdateMessageRequest) => Promise; deleteMessage: (id: string) => Promise<{ success: boolean }>; - addMember: (dmChannelId: string, data: { userId: string }) => Promise; + addMember: (dmChannelId: string, data: AddDmMemberRequest) => Promise; leave: (dmChannelId: string) => Promise<{ success: boolean }>; }; @@ -519,7 +520,7 @@ export class BackspaceApiClient { request('PATCH', `/dm/messages/${id}`, data), deleteMessage: (id: string) => request<{ success: boolean }>('DELETE', `/dm/messages/${id}`), - addMember: (dmChannelId: string, data: { userId: string }) => + addMember: (dmChannelId: string, data: AddDmMemberRequest) => request('POST', `/dm/${dmChannelId}/members`, data), leave: (dmChannelId: string) => request<{ success: boolean }>('DELETE', `/dm/${dmChannelId}/members`), diff --git a/packages/web/src/components/chat/FriendsPage.tsx b/packages/web/src/components/chat/FriendsPage.tsx index aafb8b77..2a38f627 100644 --- a/packages/web/src/components/chat/FriendsPage.tsx +++ b/packages/web/src/components/chat/FriendsPage.tsx @@ -53,7 +53,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) { const pendingIncoming = requests.filter(r => r.status === 'pending' && r.user?.id === r.fromId); const pendingOutgoing = requests.filter(r => r.status === 'pending' && r.user?.id === r.toId); - const handleOpenDm = async (friendId: string, instanceOrigin: string, homeUserId?: string) => { + const handleOpenDm = async (friendId: string, homeUserId?: string, homeInstance?: string | null) => { try { // Check if a DM already exists with this user (on any instance) const existing = useSpaceStore.getState().findExistingDmForUser({ id: friendId, homeUserId: homeUserId ?? undefined }); @@ -62,13 +62,12 @@ export function FriendsPage({ mobile }: FriendsPageProps) { navigate(`/channels/@me/${existing.dm.id}`); return; } - let client = api; - if (instanceOrigin) { - const instance = useInstanceStore.getState().instances.find(i => i.origin === instanceOrigin); - if (instance?.api) client = instance.api; - } - const dmChannel = await client.dm.create({ userId: friendId }); - addDmChannel(dmChannel, instanceOrigin); + const dmChannel = await api.dm.create({ + userId: homeInstance ? undefined : friendId, + homeUserId: homeUserId ?? undefined, + homeInstance: homeInstance ?? undefined, + }); + addDmChannel(dmChannel); navigate(`/channels/@me/${dmChannel.id}`); } catch (err) { console.error('Failed to open DM:', err); @@ -98,7 +97,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) { ) : ( onlineFriends.map(friend => ( - removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend._instanceOrigin, friend.homeUserId ?? undefined)} /> + removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend.homeUserId ?? undefined, friend.homeInstance)} /> )) )} @@ -116,7 +115,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) { ) : ( friends.map(friend => ( - removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend._instanceOrigin, friend.homeUserId ?? undefined)} /> + removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend.homeUserId ?? undefined, friend.homeInstance)} /> )) )} @@ -198,7 +197,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) { if (mobile) { pushMobileScreen('user-profile', { userId: friend.id }); } else { - handleOpenDm(friend.id, friend._instanceOrigin ?? '', friend.homeUserId ?? undefined); + handleOpenDm(friend.id, friend.homeUserId ?? undefined, friend.homeInstance); } }} className={rowClass} @@ -357,7 +356,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) { function AddFriendTab({ onOpenDm, }: { - onOpenDm: (userId: string, origin: string, homeUserId?: string) => void; + onOpenDm: (userId: string, homeUserId?: string, homeInstance?: string | null) => void; }) { const searchUsers = useSocialStore((s) => s.searchUsers); const sendFriendRequest = useSocialStore((s) => s.sendFriendRequest); @@ -610,7 +609,7 @@ function UserDiscoverCard({ onRelationshipChange, }: { user: TaggedDiscoverUser; - onOpenDm: (userId: string, origin: string, homeUserId?: string) => void; + onOpenDm: (userId: string, homeUserId?: string, homeInstance?: string | null) => void; onRelationshipChange: (userId: string, origin: string, relationship: TaggedDiscoverUser['relationship'], requestId?: string) => void; }) { const sendFriendRequest = useSocialStore((s) => s.sendFriendRequest); @@ -729,7 +728,7 @@ function UserDiscoverCard({ }; const handleMessage = () => { - onOpenDm(user.id, user._instanceOrigin, user.homeUserId ?? undefined); + onOpenDm(user.id, user.homeUserId ?? undefined, user.homeInstance); }; return ( diff --git a/packages/web/src/components/layout/DmSearchBar.tsx b/packages/web/src/components/layout/DmSearchBar.tsx index 56b6c4f0..0929be37 100644 --- a/packages/web/src/components/layout/DmSearchBar.tsx +++ b/packages/web/src/components/layout/DmSearchBar.tsx @@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import type { User, DmChannel } from '@backspace/shared'; import { Avatar } from '../ui/Avatar'; -import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore'; +import { useSpaceStore } from '../../stores/spaceStore'; import { useAuthStore } from '../../stores/authStore'; import { useUIStore } from '../../stores/uiStore'; import { api } from '../../api/client'; @@ -182,10 +182,12 @@ export function DmSearchBar() { navigate(`/channels/@me/${existing.dm.id}`); return; } - const origin = resolveUserOrigin(item.user); - const dmApi = getApiForOrigin(origin); - const channel = await dmApi.dm.create({ userId: item.user.id }); - addDmChannel(channel, origin); + const channel = await api.dm.create({ + userId: item.user.homeInstance ? undefined : item.user.id, + homeUserId: item.user.homeUserId ?? undefined, + homeInstance: item.user.homeInstance ?? undefined, + }); + addDmChannel(channel); close(); useUIStore.getState().setShowDms(true); navigate(`/channels/@me/${channel.id}`); diff --git a/packages/web/src/components/modals/AddDmMemberModal.tsx b/packages/web/src/components/modals/AddDmMemberModal.tsx index 9fd760b6..4b8b420e 100644 --- a/packages/web/src/components/modals/AddDmMemberModal.tsx +++ b/packages/web/src/components/modals/AddDmMemberModal.tsx @@ -114,7 +114,11 @@ export function AddDmMemberModal() { } else { // Existing group DM → add each friend sequentially for (const friend of selectedFriends) { - await api.dm.addMember(dmChannelId, { userId: friend.id }); + await api.dm.addMember(dmChannelId, { + userId: friend.homeInstance ? undefined : friend.id, + homeUserId: friend.homeUserId ?? undefined, + homeInstance: friend.homeInstance ?? undefined, + }); } closeModal(); } diff --git a/packages/web/src/components/modals/NewDmModal.tsx b/packages/web/src/components/modals/NewDmModal.tsx index f93daaea..15de6367 100644 --- a/packages/web/src/components/modals/NewDmModal.tsx +++ b/packages/web/src/components/modals/NewDmModal.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Modal } from '../ui/Modal'; import { Avatar } from '../ui/Avatar'; import { useUIStore } from '../../stores/uiStore'; -import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore'; +import { useSpaceStore } from '../../stores/spaceStore'; import { api } from '../../api/client'; import type { User } from '@backspace/shared'; import { parseFederatedUsername } from '../../utils/identity'; @@ -67,10 +67,12 @@ export function NewDmModal() { navigate(`/channels/@me/${existing.dm.id}`); return; } - const origin = resolveUserOrigin(user); - const dmApi = getApiForOrigin(origin); - const channel = await dmApi.dm.create({ userId: user.id }); - addDmChannel(channel, origin); + const channel = await api.dm.create({ + userId: user.homeInstance ? undefined : user.id, + homeUserId: user.homeUserId ?? undefined, + homeInstance: user.homeInstance ?? undefined, + }); + addDmChannel(channel); closeModal(); useUIStore.getState().setShowDms(true); navigate(`/channels/@me/${channel.id}`); diff --git a/packages/web/src/components/modals/UserProfileModal.tsx b/packages/web/src/components/modals/UserProfileModal.tsx index 8438033b..039fabf7 100644 --- a/packages/web/src/components/modals/UserProfileModal.tsx +++ b/packages/web/src/components/modals/UserProfileModal.tsx @@ -6,6 +6,7 @@ import { Avatar } from '../ui/Avatar'; import { Username } from '../ui/Username'; import { useUIStore } from '../../stores/uiStore'; import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore'; +import { api } from '../../api/client'; import { useSocialStore, type TaggedFriend, type TaggedFriendRequest, InstanceNotConnectedError, InstanceDisconnectedError } from '../../stores/socialStore'; import { ConnectInstanceModal } from './ConnectInstanceModal'; import { useAuthStore } from '../../stores/authStore'; @@ -170,9 +171,12 @@ export function UserProfileModal() { navigate(`/channels/@me/${existing.dm.id}`); return; } - const dmApi = getApiForOrigin(userOrigin); - const channel = await dmApi.dm.create({ userId: user.id }); - addDmChannel(channel, userOrigin); + const channel = await api.dm.create({ + userId: user.homeInstance ? undefined : user.id, + homeUserId: user.homeUserId ?? undefined, + homeInstance: user.homeInstance ?? undefined, + }); + addDmChannel(channel); useUIStore.getState().setShowDms(true); closeModal(); navigate(`/channels/@me/${channel.id}`); diff --git a/packages/web/src/components/ui/UserProfilePopout.tsx b/packages/web/src/components/ui/UserProfilePopout.tsx index 05c2859c..64ffd61a 100644 --- a/packages/web/src/components/ui/UserProfilePopout.tsx +++ b/packages/web/src/components/ui/UserProfilePopout.tsx @@ -5,6 +5,7 @@ import type { User } from '@backspace/shared'; import { Avatar } from '../ui/Avatar'; import { Username } from '../ui/Username'; import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore'; +import { api } from '../../api/client'; import { useUIStore } from '../../stores/uiStore'; import { getAvatarGradient, adjustColor, mutedGradient } from '../../utils/gradients'; import { parseFederatedUsername } from '../../utils/identity'; @@ -50,9 +51,12 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout navigate(`/channels/@me/${existing.dm.id}`); return; } - const dmApi = getApiForOrigin(origin); - const channel = await dmApi.dm.create({ userId: user.id }); - addDmChannel(channel, origin); + const channel = await api.dm.create({ + userId: user.homeInstance ? undefined : user.id, + homeUserId: user.homeUserId ?? undefined, + homeInstance: user.homeInstance ?? undefined, + }); + addDmChannel(channel); useUIStore.getState().setShowDms(true); onClose(); navigate(`/channels/@me/${channel.id}`);