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.
This commit is contained in:
Jannis Braun
2026-04-08 03:26:48 +02:00
parent 0c57f9491f
commit ba7b975bc8
10 changed files with 73 additions and 31 deletions
@@ -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) {
@@ -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) {
@@ -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));
@@ -95,9 +95,10 @@ export function MobileVoiceMiniBar() {
<button
onClick={(e) => {
e.stopPropagation();
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));
@@ -15,7 +15,9 @@ export function IncomingCallModal() {
if (incomingCall) {
timerRef.current = setTimeout(() => {
// Auto-reject after timeout
wsSend({ type: 'dm_call_reject', dmChannelId: incomingCall.dmChannelId }, getChannelOrigin(incomingCall.dmChannelId));
const { callOrigin, federatedCallId } = useVoiceStore.getState();
const origin = callOrigin || (incomingCall.dmChannelId ? getChannelOrigin(incomingCall.dmChannelId) : undefined);
wsSend({ type: 'dm_call_reject', dmChannelId: incomingCall.dmChannelId, federatedCallId }, origin);
setIncomingCall(null);
}, 30000);
}
@@ -40,16 +42,20 @@ export function IncomingCallModal() {
const handleAccept = () => {
if (timerRef.current) clearTimeout(timerRef.current);
const dmChannelId = incomingCall.dmChannelId;
const origin = getChannelOrigin(dmChannelId);
wsSend({ type: 'dm_call_accept', dmChannelId }, origin);
const { callOrigin, federatedCallId } = useVoiceStore.getState();
const origin = callOrigin || (dmChannelId ? getChannelOrigin(dmChannelId) : undefined);
wsSend({ type: 'dm_call_accept', dmChannelId, federatedCallId }, origin);
// Connect directly within gesture context (required for iOS audio permission)
const connectFn = useVoiceStore.getState().connectFn;
if (connectFn) connectFn(dmChannelId, true);
if (connectFn) connectFn(dmChannelId || federatedCallId!, true);
};
const handleDecline = () => {
if (timerRef.current) clearTimeout(timerRef.current);
wsSend({ type: 'dm_call_reject', dmChannelId: incomingCall.dmChannelId }, getChannelOrigin(incomingCall.dmChannelId));
const { callOrigin, federatedCallId } = useVoiceStore.getState();
const dmChannelId = incomingCall.dmChannelId;
const origin = callOrigin || (dmChannelId ? getChannelOrigin(dmChannelId) : undefined);
wsSend({ type: 'dm_call_reject', dmChannelId, federatedCallId }, origin);
setIncomingCall(null);
};
@@ -75,9 +75,10 @@ export function VoiceControls() {
};
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 {
wsSend({ type: 'voice_leave' }, voiceOrigin);