feat(web): route DM creation to home instance with federated identity
All DM creation and add-member call sites now use the home api client and pass homeUserId/homeInstance instead of routing to the remote instance. Also updates addMember in the API client to accept AddDmMemberRequest.
This commit is contained in:
@@ -23,6 +23,7 @@ import type {
|
|||||||
UpdateMemberRequest,
|
UpdateMemberRequest,
|
||||||
LiveKitTokenResponse,
|
LiveKitTokenResponse,
|
||||||
CreateDmRequest,
|
CreateDmRequest,
|
||||||
|
AddDmMemberRequest,
|
||||||
CreateGroupDmRequest,
|
CreateGroupDmRequest,
|
||||||
CreateDmMessageRequest,
|
CreateDmMessageRequest,
|
||||||
Friend,
|
Friend,
|
||||||
@@ -158,7 +159,7 @@ export class BackspaceApiClient {
|
|||||||
sendMessage: (id: string, data: CreateDmMessageRequest) => Promise<DmMessageWithUser>;
|
sendMessage: (id: string, data: CreateDmMessageRequest) => Promise<DmMessageWithUser>;
|
||||||
updateMessage: (id: string, data: UpdateMessageRequest) => Promise<DmMessageWithUser>;
|
updateMessage: (id: string, data: UpdateMessageRequest) => Promise<DmMessageWithUser>;
|
||||||
deleteMessage: (id: string) => Promise<{ success: boolean }>;
|
deleteMessage: (id: string) => Promise<{ success: boolean }>;
|
||||||
addMember: (dmChannelId: string, data: { userId: string }) => Promise<DmChannel>;
|
addMember: (dmChannelId: string, data: AddDmMemberRequest) => Promise<DmChannel>;
|
||||||
leave: (dmChannelId: string) => Promise<{ success: boolean }>;
|
leave: (dmChannelId: string) => Promise<{ success: boolean }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -519,7 +520,7 @@ export class BackspaceApiClient {
|
|||||||
request<DmMessageWithUser>('PATCH', `/dm/messages/${id}`, data),
|
request<DmMessageWithUser>('PATCH', `/dm/messages/${id}`, data),
|
||||||
deleteMessage: (id: string) =>
|
deleteMessage: (id: string) =>
|
||||||
request<{ success: boolean }>('DELETE', `/dm/messages/${id}`),
|
request<{ success: boolean }>('DELETE', `/dm/messages/${id}`),
|
||||||
addMember: (dmChannelId: string, data: { userId: string }) =>
|
addMember: (dmChannelId: string, data: AddDmMemberRequest) =>
|
||||||
request<DmChannel>('POST', `/dm/${dmChannelId}/members`, data),
|
request<DmChannel>('POST', `/dm/${dmChannelId}/members`, data),
|
||||||
leave: (dmChannelId: string) =>
|
leave: (dmChannelId: string) =>
|
||||||
request<{ success: boolean }>('DELETE', `/dm/${dmChannelId}/members`),
|
request<{ success: boolean }>('DELETE', `/dm/${dmChannelId}/members`),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
const pendingIncoming = requests.filter(r => r.status === 'pending' && r.user?.id === r.fromId);
|
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 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 {
|
try {
|
||||||
// Check if a DM already exists with this user (on any instance)
|
// Check if a DM already exists with this user (on any instance)
|
||||||
const existing = useSpaceStore.getState().findExistingDmForUser({ id: friendId, homeUserId: homeUserId ?? undefined });
|
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}`);
|
navigate(`/channels/@me/${existing.dm.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let client = api;
|
const dmChannel = await api.dm.create({
|
||||||
if (instanceOrigin) {
|
userId: homeInstance ? undefined : friendId,
|
||||||
const instance = useInstanceStore.getState().instances.find(i => i.origin === instanceOrigin);
|
homeUserId: homeUserId ?? undefined,
|
||||||
if (instance?.api) client = instance.api;
|
homeInstance: homeInstance ?? undefined,
|
||||||
}
|
});
|
||||||
const dmChannel = await client.dm.create({ userId: friendId });
|
addDmChannel(dmChannel);
|
||||||
addDmChannel(dmChannel, instanceOrigin);
|
|
||||||
navigate(`/channels/@me/${dmChannel.id}`);
|
navigate(`/channels/@me/${dmChannel.id}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to open DM:', err);
|
console.error('Failed to open DM:', err);
|
||||||
@@ -98,7 +97,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
onlineFriends.map(friend => (
|
onlineFriends.map(friend => (
|
||||||
<FriendItem key={`${friend.id}:${friend._instanceOrigin}`} friend={friend} onRemove={() => removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend._instanceOrigin, friend.homeUserId ?? undefined)} />
|
<FriendItem key={`${friend.id}:${friend._instanceOrigin}`} friend={friend} onRemove={() => removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend.homeUserId ?? undefined, friend.homeInstance)} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +115,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
friends.map(friend => (
|
friends.map(friend => (
|
||||||
<FriendItem key={`${friend.id}:${friend._instanceOrigin}`} friend={friend} onRemove={() => removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend._instanceOrigin, friend.homeUserId ?? undefined)} />
|
<FriendItem key={`${friend.id}:${friend._instanceOrigin}`} friend={friend} onRemove={() => removeFriend(friend.id)} onDm={() => handleOpenDm(friend.id, friend.homeUserId ?? undefined, friend.homeInstance)} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -198,7 +197,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
if (mobile) {
|
if (mobile) {
|
||||||
pushMobileScreen('user-profile', { userId: friend.id });
|
pushMobileScreen('user-profile', { userId: friend.id });
|
||||||
} else {
|
} else {
|
||||||
handleOpenDm(friend.id, friend._instanceOrigin ?? '', friend.homeUserId ?? undefined);
|
handleOpenDm(friend.id, friend.homeUserId ?? undefined, friend.homeInstance);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={rowClass}
|
className={rowClass}
|
||||||
@@ -357,7 +356,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) {
|
|||||||
function AddFriendTab({
|
function AddFriendTab({
|
||||||
onOpenDm,
|
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 searchUsers = useSocialStore((s) => s.searchUsers);
|
||||||
const sendFriendRequest = useSocialStore((s) => s.sendFriendRequest);
|
const sendFriendRequest = useSocialStore((s) => s.sendFriendRequest);
|
||||||
@@ -610,7 +609,7 @@ function UserDiscoverCard({
|
|||||||
onRelationshipChange,
|
onRelationshipChange,
|
||||||
}: {
|
}: {
|
||||||
user: TaggedDiscoverUser;
|
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;
|
onRelationshipChange: (userId: string, origin: string, relationship: TaggedDiscoverUser['relationship'], requestId?: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const sendFriendRequest = useSocialStore((s) => s.sendFriendRequest);
|
const sendFriendRequest = useSocialStore((s) => s.sendFriendRequest);
|
||||||
@@ -729,7 +728,7 @@ function UserDiscoverCard({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMessage = () => {
|
const handleMessage = () => {
|
||||||
onOpenDm(user.id, user._instanceOrigin, user.homeUserId ?? undefined);
|
onOpenDm(user.id, user.homeUserId ?? undefined, user.homeInstance);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import type { User, DmChannel } from '@backspace/shared';
|
import type { User, DmChannel } from '@backspace/shared';
|
||||||
import { Avatar } from '../ui/Avatar';
|
import { Avatar } from '../ui/Avatar';
|
||||||
import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore';
|
import { useSpaceStore } from '../../stores/spaceStore';
|
||||||
import { useAuthStore } from '../../stores/authStore';
|
import { useAuthStore } from '../../stores/authStore';
|
||||||
import { useUIStore } from '../../stores/uiStore';
|
import { useUIStore } from '../../stores/uiStore';
|
||||||
import { api } from '../../api/client';
|
import { api } from '../../api/client';
|
||||||
@@ -182,10 +182,12 @@ export function DmSearchBar() {
|
|||||||
navigate(`/channels/@me/${existing.dm.id}`);
|
navigate(`/channels/@me/${existing.dm.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const origin = resolveUserOrigin(item.user);
|
const channel = await api.dm.create({
|
||||||
const dmApi = getApiForOrigin(origin);
|
userId: item.user.homeInstance ? undefined : item.user.id,
|
||||||
const channel = await dmApi.dm.create({ userId: item.user.id });
|
homeUserId: item.user.homeUserId ?? undefined,
|
||||||
addDmChannel(channel, origin);
|
homeInstance: item.user.homeInstance ?? undefined,
|
||||||
|
});
|
||||||
|
addDmChannel(channel);
|
||||||
close();
|
close();
|
||||||
useUIStore.getState().setShowDms(true);
|
useUIStore.getState().setShowDms(true);
|
||||||
navigate(`/channels/@me/${channel.id}`);
|
navigate(`/channels/@me/${channel.id}`);
|
||||||
|
|||||||
@@ -114,7 +114,11 @@ export function AddDmMemberModal() {
|
|||||||
} else {
|
} else {
|
||||||
// Existing group DM → add each friend sequentially
|
// Existing group DM → add each friend sequentially
|
||||||
for (const friend of selectedFriends) {
|
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();
|
closeModal();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Modal } from '../ui/Modal';
|
import { Modal } from '../ui/Modal';
|
||||||
import { Avatar } from '../ui/Avatar';
|
import { Avatar } from '../ui/Avatar';
|
||||||
import { useUIStore } from '../../stores/uiStore';
|
import { useUIStore } from '../../stores/uiStore';
|
||||||
import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore';
|
import { useSpaceStore } from '../../stores/spaceStore';
|
||||||
import { api } from '../../api/client';
|
import { api } from '../../api/client';
|
||||||
import type { User } from '@backspace/shared';
|
import type { User } from '@backspace/shared';
|
||||||
import { parseFederatedUsername } from '../../utils/identity';
|
import { parseFederatedUsername } from '../../utils/identity';
|
||||||
@@ -67,10 +67,12 @@ export function NewDmModal() {
|
|||||||
navigate(`/channels/@me/${existing.dm.id}`);
|
navigate(`/channels/@me/${existing.dm.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const origin = resolveUserOrigin(user);
|
const channel = await api.dm.create({
|
||||||
const dmApi = getApiForOrigin(origin);
|
userId: user.homeInstance ? undefined : user.id,
|
||||||
const channel = await dmApi.dm.create({ userId: user.id });
|
homeUserId: user.homeUserId ?? undefined,
|
||||||
addDmChannel(channel, origin);
|
homeInstance: user.homeInstance ?? undefined,
|
||||||
|
});
|
||||||
|
addDmChannel(channel);
|
||||||
closeModal();
|
closeModal();
|
||||||
useUIStore.getState().setShowDms(true);
|
useUIStore.getState().setShowDms(true);
|
||||||
navigate(`/channels/@me/${channel.id}`);
|
navigate(`/channels/@me/${channel.id}`);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Avatar } from '../ui/Avatar';
|
|||||||
import { Username } from '../ui/Username';
|
import { Username } from '../ui/Username';
|
||||||
import { useUIStore } from '../../stores/uiStore';
|
import { useUIStore } from '../../stores/uiStore';
|
||||||
import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore';
|
import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore';
|
||||||
|
import { api } from '../../api/client';
|
||||||
import { useSocialStore, type TaggedFriend, type TaggedFriendRequest, InstanceNotConnectedError, InstanceDisconnectedError } from '../../stores/socialStore';
|
import { useSocialStore, type TaggedFriend, type TaggedFriendRequest, InstanceNotConnectedError, InstanceDisconnectedError } from '../../stores/socialStore';
|
||||||
import { ConnectInstanceModal } from './ConnectInstanceModal';
|
import { ConnectInstanceModal } from './ConnectInstanceModal';
|
||||||
import { useAuthStore } from '../../stores/authStore';
|
import { useAuthStore } from '../../stores/authStore';
|
||||||
@@ -170,9 +171,12 @@ export function UserProfileModal() {
|
|||||||
navigate(`/channels/@me/${existing.dm.id}`);
|
navigate(`/channels/@me/${existing.dm.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dmApi = getApiForOrigin(userOrigin);
|
const channel = await api.dm.create({
|
||||||
const channel = await dmApi.dm.create({ userId: user.id });
|
userId: user.homeInstance ? undefined : user.id,
|
||||||
addDmChannel(channel, userOrigin);
|
homeUserId: user.homeUserId ?? undefined,
|
||||||
|
homeInstance: user.homeInstance ?? undefined,
|
||||||
|
});
|
||||||
|
addDmChannel(channel);
|
||||||
useUIStore.getState().setShowDms(true);
|
useUIStore.getState().setShowDms(true);
|
||||||
closeModal();
|
closeModal();
|
||||||
navigate(`/channels/@me/${channel.id}`);
|
navigate(`/channels/@me/${channel.id}`);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { User } from '@backspace/shared';
|
|||||||
import { Avatar } from '../ui/Avatar';
|
import { Avatar } from '../ui/Avatar';
|
||||||
import { Username } from '../ui/Username';
|
import { Username } from '../ui/Username';
|
||||||
import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore';
|
import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore';
|
||||||
|
import { api } from '../../api/client';
|
||||||
import { useUIStore } from '../../stores/uiStore';
|
import { useUIStore } from '../../stores/uiStore';
|
||||||
import { getAvatarGradient, adjustColor, mutedGradient } from '../../utils/gradients';
|
import { getAvatarGradient, adjustColor, mutedGradient } from '../../utils/gradients';
|
||||||
import { parseFederatedUsername } from '../../utils/identity';
|
import { parseFederatedUsername } from '../../utils/identity';
|
||||||
@@ -50,9 +51,12 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
|
|||||||
navigate(`/channels/@me/${existing.dm.id}`);
|
navigate(`/channels/@me/${existing.dm.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dmApi = getApiForOrigin(origin);
|
const channel = await api.dm.create({
|
||||||
const channel = await dmApi.dm.create({ userId: user.id });
|
userId: user.homeInstance ? undefined : user.id,
|
||||||
addDmChannel(channel, origin);
|
homeUserId: user.homeUserId ?? undefined,
|
||||||
|
homeInstance: user.homeInstance ?? undefined,
|
||||||
|
});
|
||||||
|
addDmChannel(channel);
|
||||||
useUIStore.getState().setShowDms(true);
|
useUIStore.getState().setShowDms(true);
|
||||||
onClose();
|
onClose();
|
||||||
navigate(`/channels/@me/${channel.id}`);
|
navigate(`/channels/@me/${channel.id}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user