fix: exclude acting user from federated call end/reject broadcast

sendToFederatedCallUsers sent dm_call_ended/rejected back to the user
who initiated the action. They already disconnected in their click
handler — the redundant event triggered disconnectFn() again, causing
connect and disconnect sounds to play simultaneously.

Added excludeUserId parameter to sendToFederatedCallUsers, used in
handleDmCallEnd and handleDmCallReject Path 2.
This commit is contained in:
Jannis Braun
2026-04-08 14:49:29 +02:00
parent 9e7e068df7
commit 5b1c847e57
2 changed files with 9 additions and 4 deletions
+5 -2
View File
@@ -1589,11 +1589,12 @@ function handleDmCallReject(event: Record<string, unknown>, userId: string): voi
: undefined; : undefined;
if (fedCall) { if (fedCall) {
// Exclude the rejecting user — they already handled their own state
connectionManager.sendToFederatedCallUsers(fedCall.federatedId, { connectionManager.sendToFederatedCallUsers(fedCall.federatedId, {
type: 'dm_call_rejected', type: 'dm_call_rejected',
dmChannelId: fedCall.dmChannelId, dmChannelId: fedCall.dmChannelId,
federatedCallId: fedCall.federatedId, federatedCallId: fedCall.federatedId,
} as ServerEvent); } as ServerEvent, userId);
connectionManager.clearFederatedCall(fedCall.federatedId); connectionManager.clearFederatedCall(fedCall.federatedId);
const db = getDb(); const db = getDb();
@@ -1660,11 +1661,13 @@ function handleDmCallEnd(event: Record<string, unknown>, userId: string): void {
: undefined; : undefined;
if (fedCall) { if (fedCall) {
// Exclude the user who ended the call — they already disconnected in their click handler.
// Sending dm_call_ended back to them causes redundant disconnectFn() and double sounds.
connectionManager.sendToFederatedCallUsers(fedCall.federatedId, { connectionManager.sendToFederatedCallUsers(fedCall.federatedId, {
type: 'dm_call_ended', type: 'dm_call_ended',
dmChannelId: fedCall.dmChannelId, dmChannelId: fedCall.dmChannelId,
federatedCallId: fedCall.federatedId, federatedCallId: fedCall.federatedId,
} as ServerEvent); } as ServerEvent, userId);
connectionManager.clearFederatedCall(fedCall.federatedId); connectionManager.clearFederatedCall(fedCall.federatedId);
const db = getDb(); const db = getDb();
+3 -1
View File
@@ -758,13 +758,15 @@ class ConnectionManager {
* ALWAYS uses ringedUserIds, never sendToDmMembers — sendToDmMembers would * ALWAYS uses ringedUserIds, never sendToDmMembers — sendToDmMembers would
* also reach the caller's replicated stub, causing cross-instance event contamination * also reach the caller's replicated stub, causing cross-instance event contamination
* (the caller's multi-instance WS gets dm_call_accepted with the wrong dmChannelId). */ * (the caller's multi-instance WS gets dm_call_accepted with the wrong dmChannelId). */
sendToFederatedCallUsers(federatedId: string, event: ServerEvent): void { sendToFederatedCallUsers(federatedId: string, event: ServerEvent, excludeUserId?: string): void {
const call = this.federatedCalls.get(federatedId); const call = this.federatedCalls.get(federatedId);
if (!call) return; if (!call) return;
for (const uid of call.ringedUserIds) { for (const uid of call.ringedUserIds) {
if (uid !== excludeUserId) {
this.sendToUser(uid, event); this.sendToUser(uid, event);
} }
} }
}
/** Send to a room — routes to sendToSpace (space rooms) or sendToDmMembers (DM rooms). */ /** Send to a room — routes to sendToSpace (space rooms) or sendToDmMembers (DM rooms). */
sendToRoom(roomId: string, event: ServerEvent, excludeUserId?: string): void { sendToRoom(roomId: string, event: ServerEvent, excludeUserId?: string): void {