8.0 KiB
8.0 KiB
sessionId
| sessionId |
|---|
| session-260912-132945-1get |
Requirements
Overview & Goals
Separate WebRTC voice signaling from the main real-time chat gateway by introducing a dedicated Voice WebSocket endpoint (/ws/voice). The voice signaling protocol (SDP offer/answer exchange, ICE candidates, room joins/leaves) is isolated from chat events, mirroring the architecture of production communication platforms (e.g. Discord's separate Gateway and Voice Gateway).
Scope
In Scope
- Implement a dedicated WebSocket route
GET /ws/voice(nested alongside/ws/gatewayunderws_routes). - Authenticate incoming voice WebSocket connections using the existing
CurrentUserextractor (supporting Authorization headers, cookies, and?token=query parameters). - Define and implement a lightweight, dedicated JSON signaling protocol for voice sessions (
offer,answer,ice-candidate,leave,error). - Bind the voice connection lifecycle directly to the WebSocket lifecycle: connecting/joining initiates the session; closing the socket cleanly tears down the
PeerConnectionand removes the participant from theVoiceRoom. - Remove voice signaling handlers (
namespace: "Voice") from the general chat gateway (src/routes/gateway/mod.rs), restoring clean separation of concerns. - Maintain full compatibility with
VoiceServiceandrustrtcSFU track fan-out.
Out of Scope
- Changing the underlying
rustrtcSFU forwarding engine or UDP multiplexing port (udp_port). - Implementing TURN/STUN relay configurations.
- Video/screen-sharing UI controls (handled in future iterations).
User Stories
- As a client joining a voice channel, I want to open a dedicated WebSocket connection for voice so that media signaling traffic (large SDP payloads, ICE candidates) does not delay or congest my chat message stream.
- As a client leaving a voice channel, I want closing my voice WebSocket connection to immediately and cleanly release all server WebRTC resources without affecting my general gateway connection.
- As a backend developer, I want voice signaling isolated in its own module/handler so the main gateway router remains focused strictly on application domain events.
Functional Requirements
- Dedicated Route:
GET /ws/voiceupgrades to a WebSocket connection authenticated viaCurrentUser. - Signaling Exchange:
- Client sends
offerwith{ "channel_id": Uuid, "sdp": String }. The server validates permissions (JOIN_VOICE), creates/updates therustrtc::PeerConnection, and returns ananswerevent with the server SDP. - Client and server exchange
ice-candidateevents with{ "channel_id": Uuid, "candidate": String }. - Client sends
leaveor closes the socket; server removes peer fromVoiceRoomand unsubscribes tracks.
- Client sends
- Error Handling: Server returns
{ "action": "error", "message": String }on permission denial, invalid payload, or WebRTC negotiation failure. - Gateway Decoupling:
/ws/gatewayno longer processes"Voice"namespace events.
Non-Functional Requirements
- Performance: High throughput and low latency signaling with asynchronous message handling via Tokio tasks.
- Reliability: Deterministic cleanup on socket disconnection preventing orphaned WebRTC sessions or memory leaks.
Technical Design
Current Implementation
src/routes/mod.rs: Mountsws_routescontaining/ws/gateway.src/routes/gateway/mod.rs:GatewayClient::on_messageinspects JSON events; ifnamespace == "Voice", it dispatchesoffer,ice-candidate, andleavetoAppState.voice.src/voice/service.rs: ManagesRtcConfiguration,VoiceRoominstances, SDP answers, ICE candidates, and permissions.src/http/middleware.rs&src/http/context.rs:auth_middlewareextracts JWTs from Bearer headers, cookies, or?token=query parameters and populatesCurrentUser.
Key Decisions
- Dedicated WebSocket Route (
/ws/voice): Voice signaling is completely extracted into its own endpoint and handler module (src/routes/voice/orsrc/voice/ws.rs), eliminating protocol coupling insrc/routes/gateway/. - Connection-bound Voice Lifecycle: The dedicated WebSocket lifetime directly reflects voice room occupancy. When the WebSocket connection drops,
VoiceService::leaveis triggered automatically. - Simplified Signaling Protocol: Elimination of the generic
GatewayEventenvelope wrapper ({ namespace, action, content }) in favor of direct, typed voice signaling messages ({ action, ... }). - Standardized Authentication: Reuse of
CurrentUserextractor ensures uniform JWT validation across REST, Gateway, and Voice WebSocket endpoints.
Proposed Changes
- New Route & Handler:
- Create
src/routes/voice/withmod.rs,handlers.rs, androutes.rs(or integrate undersrc/voice/and expose viasrc/routes/mod.rs). - Add
GET /voiceroute tows_routesinsrc/routes/mod.rs(resulting in/ws/voice). - Implement
voice_ws_handleracceptingWebSocketUpgrade,State(AppState), andCurrentUser(user).
- Create
- Voice WebSocket Protocol:
- Define incoming message types:
Offer { channel_id, sdp },IceCandidate { channel_id, candidate },Leave { channel_id }. - Define outgoing message types:
Answer { channel_id, sdp },IceCandidate { channel_id, candidate },Error { message }.
- Define incoming message types:
- Gateway Cleanup:
- Remove
namespace == "Voice"handling fromGatewayClient::on_message. - Remove voice-specific imports from
src/routes/gateway/mod.rs.
- Remove
- VoiceService Integration:
- Update
VoiceService::handle_offerto take the dedicated voice WebSocket sender channel for emitting server ICE candidates.
- Update
Data Models / Contracts
// Voice WebSocket Client -> Server messages
#[derive(Debug, Deserialize)]
#[serde(tag = "action", rename_all = "kebab-case")]
pub enum VoiceClientMessage {
Offer {
channel_id: Uuid,
sdp: String,
},
IceCandidate {
channel_id: Uuid,
candidate: String,
},
Leave {
channel_id: Uuid,
},
}
// Voice WebSocket Server -> Client messages
#[derive(Debug, Serialize)]
#[serde(tag = "action", rename_all = "kebab-case")]
pub enum VoiceServerMessage {
Answer {
channel_id: Uuid,
sdp: String,
},
IceCandidate {
channel_id: Uuid,
candidate: String,
},
Error {
message: String,
},
}
Components
VoiceWsHandler(src/routes/voice/handlers.rs): Handles WebSocket upgrades, authentication, event loop, message serialization/deserialization, and lifecycle teardown.VoiceRoutes(src/routes/voice/routes.rs): Defines Axum router for/voice.VoiceService(src/voice/service.rs): Coordinatesrustrtc::PeerConnectioninstances, SFU track forwarding, and room management.GatewayManager(src/routes/gateway/mod.rs): Retains responsibility solely for domain event broadcasting (chat, server, channel updates).
File Structure
src/routes/mod.rs: Registervoice::routes::router()under/ws.src/routes/voice/mod.rs: Public module definition.src/routes/voice/routes.rs: Voice WebSocket route definition.src/routes/voice/handlers.rs: Voice WebSocket connection handler and message loop.src/routes/voice/messages.rs: Strongly typed signaling message definitions.src/routes/gateway/mod.rs: Cleaned up to remove voice signaling logic.
Architecture Diagram
graph TD
Client[Client App]
Client -- Chat events / presence --> GW["/ws/gateway (GatewayManager)"]
Client -- Voice signaling (SDP/ICE) --> VWS["/ws/voice (VoiceWsHandler)"]
VWS -- Manage connections & rooms --> VS[VoiceService]
VS -- SFU media fan-out --> RR[rustrtc / VoiceRoom]
Client == SRTP media over single UDP port ==> RR
Risks
- Socket Disconnect Race Conditions: Ensure that client disconnection promptly removes the user from
VoiceRoomwithout deadlockingRwLockguards. - Message Deserialization Errors: Malformed payloads must result in an
Errorresponse over the WebSocket rather than terminating the connection loop prematurely.