feat(server): fan dm_call_end out on host ring timeout

This commit is contained in:
Jannis Braun
2026-04-23 23:19:42 +02:00
parent 6cd7728f3e
commit f6252b8ce1
6 changed files with 84 additions and 19 deletions
+16
View File
@@ -99,6 +99,9 @@ class ConnectionManager {
private pendingOfflineTimeouts: Map<string, NodeJS.Timeout> = new Map();
// roomId → Timeout for ringing DM rooms (60s auto-cleanup)
private ringingTimeouts: Map<string, NodeJS.Timeout> = new Map();
// Callback registered by events.ts to fan dm_call_end out to peers on ring timeout.
// Null during startup — ring timeouts that fire before registration simply no-op (there are no peers to notify before boot completes).
private ringTimeoutFanoutHook: ((dmChannelId: string, callerId: string) => Promise<void>) | null = null;
/** Federated calls where this instance is NOT the host. Keyed by federatedId. */
private federatedCalls: Map<string, FederatedCallEntry> = new Map();
private federatedCallTimeouts: Map<string, NodeJS.Timeout> = new Map();
@@ -401,6 +404,11 @@ class ConnectionManager {
return true;
}
/** Register a fan-out callback invoked when a ringing DM room hits its 60s timeout. */
setRingTimeoutFanoutHook(fn: (dmChannelId: string, callerId: string) => Promise<void>): void {
this.ringTimeoutFanoutHook = fn;
}
/** Create a DM room in ringing state with 60s auto-cleanup. */
createDmRoom(dmChannelId: string, callerId: string): boolean {
const created = this.createRoom(dmChannelId, 'dm', {
@@ -415,11 +423,19 @@ class ConnectionManager {
this.ringingTimeouts.delete(dmChannelId);
const room = this.voiceRooms.get(dmChannelId);
if (room && room.roomType === 'dm' && (room.metadata as DmRoomMeta).state === 'ringing') {
const ringedCallerId = (room.metadata as DmRoomMeta).callerId;
this.destroyRoom(dmChannelId);
this.sendToDmMembers(dmChannelId, {
type: 'dm_call_ended',
dmChannelId,
});
// Fan dm_call_end out to remote peers so stranded Path-A/B ringees exit the ring.
// Without this, an accept-relay failure → Alice's 60s auto-clean leaves Bob's FederatedCallEntry lingering with no terminal event.
if (this.ringTimeoutFanoutHook) {
this.ringTimeoutFanoutHook(dmChannelId, ringedCallerId).catch(err =>
console.error('[ws] ring-timeout fan-out error:', err),
);
}
}
}, 60_000);
this.ringingTimeouts.set(dmChannelId, timeout);