fix: three root causes for federated DM call failures
1. Accept/reject/end from remote instance now resolves federatedId to local dmChannelId via DB lookup, so the host can find its VoiceRoom when the event arrives with only a federatedCallId. Previously silently failed with "No active call" error. 2. Batch all dm_call_incoming state updates into a single useVoiceStore.setState() call. Prevents SoundController from starting multiple ringtone instances (async playSound guard race when 4 separate set() calls each triggered the subscription). 3. Always overwrite callOrigin/federatedCallId (with null if absent) on dm_call_incoming. Prevents stale values from a previous federated call routing local accepts to the wrong instance.
This commit is contained in:
@@ -1442,7 +1442,7 @@ function handleDmCallStart(event: Record<string, unknown>, userId: string, usern
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDmCallAccept(event: Record<string, unknown>, userId: string, ws: WebSocket): void {
|
function handleDmCallAccept(event: Record<string, unknown>, userId: string, ws: WebSocket): void {
|
||||||
const dmChannelId = (event.dmChannelId as string) || null;
|
let dmChannelId = (event.dmChannelId as string) || null;
|
||||||
const federatedCallId = (event.federatedCallId as string) || null;
|
const federatedCallId = (event.federatedCallId as string) || null;
|
||||||
|
|
||||||
if (!dmChannelId && !federatedCallId) {
|
if (!dmChannelId && !federatedCallId) {
|
||||||
@@ -1450,6 +1450,18 @@ function handleDmCallAccept(event: Record<string, unknown>, userId: string, ws:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve federatedCallId → dmChannelId when only federatedCallId is provided.
|
||||||
|
// This happens when a remote instance accepts via callOrigin and the DM doesn't
|
||||||
|
// exist there (Path B), but the HOST has the VoiceRoom keyed by dmChannelId.
|
||||||
|
if (!dmChannelId && federatedCallId) {
|
||||||
|
const db = getDb();
|
||||||
|
const ch = db.select({ id: schema.dmChannels.id })
|
||||||
|
.from(schema.dmChannels)
|
||||||
|
.where(eq(schema.dmChannels.federatedId, federatedCallId))
|
||||||
|
.get();
|
||||||
|
if (ch) dmChannelId = ch.id;
|
||||||
|
}
|
||||||
|
|
||||||
// Path 1: Local room (we're the host) — only possible with dmChannelId
|
// Path 1: Local room (we're the host) — only possible with dmChannelId
|
||||||
if (dmChannelId) {
|
if (dmChannelId) {
|
||||||
if (!isDmMember(dmChannelId, userId)) {
|
if (!isDmMember(dmChannelId, userId)) {
|
||||||
@@ -1530,11 +1542,21 @@ function handleDmCallAccept(event: Record<string, unknown>, userId: string, ws:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDmCallReject(event: Record<string, unknown>, userId: string): void {
|
function handleDmCallReject(event: Record<string, unknown>, userId: string): void {
|
||||||
const dmChannelId = (event.dmChannelId as string) || null;
|
let dmChannelId = (event.dmChannelId as string) || null;
|
||||||
const federatedCallId = (event.federatedCallId as string) || null;
|
const federatedCallId = (event.federatedCallId as string) || null;
|
||||||
|
|
||||||
if (!dmChannelId && !federatedCallId) return;
|
if (!dmChannelId && !federatedCallId) return;
|
||||||
|
|
||||||
|
// Resolve federatedCallId → dmChannelId for host VoiceRoom lookup
|
||||||
|
if (!dmChannelId && federatedCallId) {
|
||||||
|
const db = getDb();
|
||||||
|
const ch = db.select({ id: schema.dmChannels.id })
|
||||||
|
.from(schema.dmChannels)
|
||||||
|
.where(eq(schema.dmChannels.federatedId, federatedCallId))
|
||||||
|
.get();
|
||||||
|
if (ch) dmChannelId = ch.id;
|
||||||
|
}
|
||||||
|
|
||||||
// Path 1: Local room (we're the host)
|
// Path 1: Local room (we're the host)
|
||||||
if (dmChannelId) {
|
if (dmChannelId) {
|
||||||
if (!isDmMember(dmChannelId, userId)) return;
|
if (!isDmMember(dmChannelId, userId)) return;
|
||||||
@@ -1587,11 +1609,21 @@ function handleDmCallReject(event: Record<string, unknown>, userId: string): voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDmCallEnd(event: Record<string, unknown>, userId: string): void {
|
function handleDmCallEnd(event: Record<string, unknown>, userId: string): void {
|
||||||
const dmChannelId = (event.dmChannelId as string) || null;
|
let dmChannelId = (event.dmChannelId as string) || null;
|
||||||
const federatedCallId = (event.federatedCallId as string) || null;
|
const federatedCallId = (event.federatedCallId as string) || null;
|
||||||
|
|
||||||
if (!dmChannelId && !federatedCallId) return;
|
if (!dmChannelId && !federatedCallId) return;
|
||||||
|
|
||||||
|
// Resolve federatedCallId → dmChannelId for host VoiceRoom lookup
|
||||||
|
if (!dmChannelId && federatedCallId) {
|
||||||
|
const db = getDb();
|
||||||
|
const ch = db.select({ id: schema.dmChannels.id })
|
||||||
|
.from(schema.dmChannels)
|
||||||
|
.where(eq(schema.dmChannels.federatedId, federatedCallId))
|
||||||
|
.get();
|
||||||
|
if (ch) dmChannelId = ch.id;
|
||||||
|
}
|
||||||
|
|
||||||
// Path 1: Local room (we're the host)
|
// Path 1: Local room (we're the host)
|
||||||
if (dmChannelId) {
|
if (dmChannelId) {
|
||||||
if (!isDmMember(dmChannelId, userId)) return;
|
if (!isDmMember(dmChannelId, userId)) return;
|
||||||
|
|||||||
@@ -779,22 +779,20 @@ function handleEvent(origin: string, event: ServerEvent): void {
|
|||||||
// ─── DM call events (all origins) ──────────────────────────────────────
|
// ─── DM call events (all origins) ──────────────────────────────────────
|
||||||
|
|
||||||
case 'dm_call_incoming': {
|
case 'dm_call_incoming': {
|
||||||
const { setIncomingCall, setFederatedCallData, setFederatedCallId, setCallOrigin } = useVoiceStore.getState();
|
// Batch ALL call state into a single set() to prevent:
|
||||||
setIncomingCall({
|
// 1. Ringtone multiplication (multiple subscription triggers from separate set() calls)
|
||||||
dmChannelId: event.dmChannelId ?? null,
|
// 2. Stale callOrigin/federatedCallId from previous calls (always overwritten)
|
||||||
callerId: event.callerId,
|
useVoiceStore.setState({
|
||||||
callerName: event.callerName,
|
incomingCall: {
|
||||||
|
dmChannelId: event.dmChannelId ?? null,
|
||||||
|
callerId: event.callerId,
|
||||||
|
callerName: event.callerName,
|
||||||
|
},
|
||||||
|
federatedCallToken: event.livekitToken ?? null,
|
||||||
|
federatedCallUrl: event.livekitUrl ?? null,
|
||||||
|
federatedCallId: event.federatedCallId ?? null,
|
||||||
|
callOrigin: event.callOrigin ?? null,
|
||||||
});
|
});
|
||||||
// Store federated call data if present (remote LiveKit URL + token)
|
|
||||||
if (event.livekitUrl && event.livekitToken) {
|
|
||||||
setFederatedCallData(event.livekitToken, event.livekitUrl);
|
|
||||||
}
|
|
||||||
if (event.federatedCallId) {
|
|
||||||
setFederatedCallId(event.federatedCallId);
|
|
||||||
}
|
|
||||||
if (event.callOrigin) {
|
|
||||||
setCallOrigin(event.callOrigin);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user