From ba7b975bc8f1c6d0f1146cdff8cdc1463320cf7b Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:26:48 +0200 Subject: [PATCH] feat: add federatedCallId and callOrigin to client call routing Enable federated DM calls to route accept/reject/end through the correct WebSocket connection using callOrigin, and include federatedCallId in all dm_call payloads for server-side FederatedCallEntry lookup. --- packages/shared/src/types.ts | 8 ++--- .../src/components/NotificationController.tsx | 2 +- .../web/src/components/layout/MainContent.tsx | 4 ++- .../layout/MobileVoiceFullScreen.tsx | 5 +-- .../components/layout/MobileVoiceMiniBar.tsx | 5 +-- .../components/voice/IncomingCallModal.tsx | 16 +++++++--- .../src/components/voice/VoiceControls.tsx | 5 +-- packages/web/src/hooks/useWebSocket.ts | 32 +++++++++++++------ packages/web/src/stores/voiceStore.ts | 22 +++++++++++-- packages/web/src/utils/voiceActions.ts | 5 +-- 10 files changed, 73 insertions(+), 31 deletions(-) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index ed33940c..c847f639 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -377,9 +377,9 @@ export type ClientEvent = | { type: 'channel_ack'; channelId: string; messageId: string } | { type: 'mark_unread'; channelId: string; messageId: string } | { type: 'dm_call_start'; dmChannelId: string } - | { type: 'dm_call_accept'; dmChannelId: string } - | { type: 'dm_call_reject'; dmChannelId: string } - | { type: 'dm_call_end'; dmChannelId: string } + | { type: 'dm_call_accept'; dmChannelId: string | null; federatedCallId?: string | null } + | { type: 'dm_call_reject'; dmChannelId: string | null; federatedCallId?: string | null } + | { type: 'dm_call_end'; dmChannelId: string | null; federatedCallId?: string | null } | { type: 'voice_status'; isMuted: boolean; isDeafened: boolean; isCameraOn: boolean; isScreenSharing: boolean } | { type: 'voice_space_mute'; userId: string; muted: boolean } | { type: 'voice_space_deafen'; userId: string; deafened: boolean } @@ -410,7 +410,7 @@ export type ServerEvent = | { type: 'friend_request_received'; request: FriendRequest } | { type: 'friend_request_accepted'; friend: Friend; requestId: string } | { type: 'dm_call_incoming'; dmChannelId: string | null; federatedCallId?: string; callerId: string; callerName: string; livekitUrl?: string; livekitToken?: string; callOrigin?: string } - | { type: 'dm_call_accepted'; dmChannelId: string } + | { type: 'dm_call_accepted'; dmChannelId: string | null; federatedCallId?: string } | { type: 'dm_call_rejected'; dmChannelId: string } | { type: 'dm_call_ended'; dmChannelId: string } | { type: 'voice_status_update'; userId: string; channelId: string; isMuted: boolean; isDeafened: boolean; isCameraOn: boolean; isScreenSharing: boolean } diff --git a/packages/web/src/components/NotificationController.tsx b/packages/web/src/components/NotificationController.tsx index b0ec1f0b..57b70535 100644 --- a/packages/web/src/components/NotificationController.tsx +++ b/packages/web/src/components/NotificationController.tsx @@ -86,7 +86,7 @@ export function NotificationController() { // DM call notification useEffect(() => { - let prevIncoming: { dmChannelId: string; callerId: string; callerName: string } | null = null; + let prevIncoming: { dmChannelId: string | null; callerId: string; callerName: string } | null = null; const unsubscribe = useVoiceStore.subscribe((state) => { if (state.incomingCall && !prevIncoming && !windowFocused.current) { diff --git a/packages/web/src/components/layout/MainContent.tsx b/packages/web/src/components/layout/MainContent.tsx index c3e3d1a7..81a2522e 100644 --- a/packages/web/src/components/layout/MainContent.tsx +++ b/packages/web/src/components/layout/MainContent.tsx @@ -102,7 +102,9 @@ export function MainContent() { const handleCancelCall = () => { if (!currentChannelId) return; useVoiceStore.getState().setOutgoingCall(null); - wsSend({ type: 'dm_call_end', dmChannelId: currentChannelId }, getChannelOrigin(currentChannelId)); + const { federatedCallId, callOrigin } = useVoiceStore.getState(); + const origin = callOrigin || getChannelOrigin(currentChannelId); + wsSend({ type: 'dm_call_end', dmChannelId: currentChannelId, federatedCallId }, origin); }; if (isInDmCall) { diff --git a/packages/web/src/components/layout/MobileVoiceFullScreen.tsx b/packages/web/src/components/layout/MobileVoiceFullScreen.tsx index 6d3658ce..72d4073d 100644 --- a/packages/web/src/components/layout/MobileVoiceFullScreen.tsx +++ b/packages/web/src/components/layout/MobileVoiceFullScreen.tsx @@ -98,9 +98,10 @@ export function MobileVoiceFullScreen() { }; const handleDisconnect = () => { - const { activeDmCall, disconnectFn } = useVoiceStore.getState(); + const { activeDmCall, disconnectFn, federatedCallId, callOrigin } = useVoiceStore.getState(); if (activeDmCall) { - wsSend({ type: 'dm_call_end', dmChannelId: activeDmCall.dmChannelId }, getChannelOrigin(activeDmCall.dmChannelId)); + const origin = callOrigin || getChannelOrigin(activeDmCall.dmChannelId); + wsSend({ type: 'dm_call_end', dmChannelId: activeDmCall.dmChannelId, federatedCallId }, origin); useVoiceStore.getState().setActiveDmCall(null); } else if (currentVoiceChannelId) { wsSend({ type: 'voice_leave' }, getChannelOrigin(currentVoiceChannelId)); diff --git a/packages/web/src/components/layout/MobileVoiceMiniBar.tsx b/packages/web/src/components/layout/MobileVoiceMiniBar.tsx index b5790c3d..229970a9 100644 --- a/packages/web/src/components/layout/MobileVoiceMiniBar.tsx +++ b/packages/web/src/components/layout/MobileVoiceMiniBar.tsx @@ -95,9 +95,10 @@ export function MobileVoiceMiniBar() {