signaling_server
WebRTC signaling: peer registry, session/room relay, and secure-mode gates.
Implements the WebSocket signaling plane the WebRTC streaming mode rides on. One in-process "server" peer owns the media graph; browser "client" peers (controllers and viewers, one set per display) pair with it through SESSION messages, or exchange ROOM messages for multi-peer rooms. Newest-connection-wins eviction mirrors websockets mode so a page refresh supersedes its own stale socket, with a takeover-storm breaker so two live auto-reconnecting pages cannot trade the session forever.
Wire protocol (text frames, space-separated): a peer opens with
HELLO \<server|client> [\<json-metadata>] (client_type, client_slot,
client_strict_viewer, client_token, server_token, display_id,
display_position) and is answered HELLO. SESSION \<peer-id|server> pairs
the caller with the callee: the caller gets SESSION_OK \<callee-id>, the callee
SESSION_START \<caller-id> \<client_type> \<display_id> \<display_position> [\<client_token>], and a disconnect sends the partner SESSION_END \<peer-id> \<client_type>. In a session every message is addressed \<peer-id> \<message>
and relayed as \<sender-id> \<message>, only between session partners. ROOM \<room-id> joins or creates a room (ROOM_OK \<member-ids>, members get
ROOM_PEER_JOINED / ROOM_PEER_LEFT \<peer-id>), where ROOM_PEER_MSG \<peer-id> \<message> is relayed as ROOM_PEER_MSG \<sender-id> \<message>.
Errors are ERROR \<text>.
Concurrency model: all registry mutation happens under a single asyncio.Lock, and every socket send/close triggered while holding it is deferred and flushed after release, so one slow peer socket can never serialize other handshakes.
In secure mode (a master token is configured) the auth middleware forwards
WebSocket upgrades without Basic auth, so the token checks here are the only
gate: client peers must present a provisioned session token and server peers
the master token itself. Token checks are constant-time lookups in the live
control-plane table (/api/tokens), read per handshake.
attributelogger= logging.getLogger('signaling')