From 02279d4e1533d955e0b89f7ac5e434471728a729 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Tue, 21 Apr 2026 17:50:12 +0700 Subject: [PATCH] fix(media-server): fall back to sole agent peer when peer_id missing Rails' /agent_answer forwards only the SDP answer to the media server; it doesn't persist or echo the peer_id returned from /agent-offer. The AgentAnswer handler required peer_id and 400'd on every Rails call, so Peer B never completed its handshake. With no agent remote track, the bridge delivered nothing to Meta, and Meta tore the call down after ~20s with error 138021 ("WhatsApp client terminated the call due to not receiving any media"). Add Session.SoleAgentPeerID and have the handler use it when peer_id is absent. Single-agent sessions now complete negotiation without extra Rails plumbing. Multi-agent sessions still require an explicit peer_id. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../media-server/internal/server/handlers.go | 11 +++++++++-- .../media-server/internal/session/session.go | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/enterprise/media-server/internal/server/handlers.go b/enterprise/media-server/internal/server/handlers.go index 3051dd4fa..5719a3fa0 100644 --- a/enterprise/media-server/internal/server/handlers.go +++ b/enterprise/media-server/internal/server/handlers.go @@ -297,9 +297,16 @@ func (h *Handlers) AgentAnswer(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, "sdp_answer is required") return } + // When peer_id is omitted (single-agent sessions), fall back to the only + // agent peer attached to the session. Rails doesn't currently surface + // peer_id through ActionCable, so browsers just send the SDP answer. if req.PeerID == "" { - writeError(w, http.StatusBadRequest, "peer_id is required") - return + if only, ok := sess.SoleAgentPeerID(); ok { + req.PeerID = only + } else { + writeError(w, http.StatusBadRequest, "peer_id is required (session has multiple agent peers)") + return + } } if err := sess.SetAgentAnswer(req.PeerID, req.SDPAnswer); err != nil { diff --git a/enterprise/media-server/internal/session/session.go b/enterprise/media-server/internal/session/session.go index 5b6222d66..7d819b2ca 100644 --- a/enterprise/media-server/internal/session/session.go +++ b/enterprise/media-server/internal/session/session.go @@ -204,6 +204,22 @@ func (s *Session) CreateAgentPeer(peerID string, role peer.PeerRole, iceServers return sdpOffer, nil } +// SoleAgentPeerID returns the peer_id when the session has exactly one agent +// peer. Used by the agent-answer handler as a fallback for clients that don't +// track peer ids. +func (s *Session) SoleAgentPeerID() (string, bool) { + s.mu.Lock() + defer s.mu.Unlock() + + if len(s.AgentPeers) != 1 { + return "", false + } + for id := range s.AgentPeers { + return id, true + } + return "", false +} + // SetAgentAnswer sets the agent browser's SDP answer on the specified agent // peer, completing the WebRTC handshake. func (s *Session) SetAgentAnswer(peerID, sdpAnswer string) error {