fix: Fix users unable to see or hear each other in voice channels due to
stale server sockets, passive non-initiators, and race conditions during peer connection setup. Fix users unable to see or hear each other in voice channels due to stale server sockets, passive non-initiators, and race conditions during peer connection setup. Server: - Close stale WebSocket connections sharing the same oderId in handleIdentify instead of letting them linger up to 45s - Make user_joined/user_left broadcasts identity-aware so duplicate sockets don't produce phantom join/leave events - Include serverIds in user_left payload for multi-room presence - Simplify findUserByOderId now that stale sockets are cleaned up Client - signaling: - Add fallback offer system with 1s timer for missed user_joined races - Add non-initiator takeover after 5s when the initiator fails to send an offer (NON_INITIATOR_GIVE_UP_MS) - Scope peerServerMap per signaling URL to prevent cross-server collisions - Add socket identity guards on all signaling event handlers - Replace canReusePeerConnection with hasActivePeerConnection and isPeerConnectionNegotiating with extended grace periods Client - peer connections: - Extract replaceUnusablePeer helper to deduplicate stale peer replacement in offer and ICE handlers - Add stale connectionstatechange guard to ignore events from replaced RTCPeerConnection instances - Use deterministic initiator election in peer recovery reconnects - Track createdAt on PeerData for staleness detection Client - presence: - Add multi-room presence tracking via presenceServerIds on User - Replace clearUsers + individual userJoined with syncServerPresence for atomic server roster updates - Make userLeft handle partial server removal instead of full eviction Documentation: - Add server-side connection hygiene, non-initiator takeover, and stale peer replacement sections to the realtime README
This commit is contained in:
@@ -95,6 +95,7 @@ export class SignalingTransportHandler<TMessage> {
|
||||
|
||||
sendRawMessage(message: Record<string, unknown>): void {
|
||||
const targetPeerId = typeof message['targetUserId'] === 'string' ? message['targetUserId'] : null;
|
||||
const messageType = typeof message['type'] === 'string' ? message['type'] : 'unknown';
|
||||
|
||||
if (targetPeerId) {
|
||||
const targetSignalUrl = this.dependencies.signalingCoordinator.getPeerSignalUrl(targetPeerId);
|
||||
@@ -102,6 +103,11 @@ export class SignalingTransportHandler<TMessage> {
|
||||
if (targetSignalUrl && this.sendRawMessageToSignalUrl(targetSignalUrl, message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dependencies.logger.warn('[signaling] Missing peer signal route for outbound raw message', {
|
||||
targetPeerId,
|
||||
type: messageType
|
||||
});
|
||||
}
|
||||
|
||||
const serverId = typeof message['serverId'] === 'string' ? message['serverId'] : null;
|
||||
@@ -118,12 +124,19 @@ export class SignalingTransportHandler<TMessage> {
|
||||
|
||||
if (connectedManagers.length === 0) {
|
||||
this.dependencies.logger.error('[signaling] No active signaling connection for outbound message', new Error('No signaling manager available'), {
|
||||
type: typeof message['type'] === 'string' ? message['type'] : 'unknown'
|
||||
type: messageType
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.dependencies.logger.warn('[signaling] Broadcasting raw message to all signaling managers due to unresolved route', {
|
||||
connectedSignalUrls: connectedManagers.map(({ signalUrl }) => signalUrl),
|
||||
serverId,
|
||||
targetPeerId,
|
||||
type: messageType
|
||||
});
|
||||
|
||||
for (const { manager } of connectedManagers) {
|
||||
manager.sendRawMessage(message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user