Skip to content

Remote Namespace

eric edited this page Jul 18, 2026 · 3 revisions

Remote Namespace

The /remote socket.io namespace provides one-time-ticket binary streaming sessions — built for remote-control use cases: an agent streams binary frames (e.g. screen captures) to viewers, and viewers send input events back to the agent.

Flow

  1. An authenticated client on the main namespace requests a ticket:

    client → server: REMOTE_TICKET_REQUEST { session_id?, machine_id?, role: 'agent'|'viewer' }
    server → client: REMOTE_TICKET { ok, session_id, ticket, role, expires_in }
    

    With auth enabled, the socket must authenticate first; the ticket inherits the socket's realm. Tickets are single-purpose, random, and expire after 60 s by default (remote: { ticket_ttl_ms }).

  2. The remote party connects to /remote and presents the ticket:

    socket → server: auth { ticket, session_id }
    server → socket: auth_ok    { session_id, role }
                   | auth_error { message }       // invalid/expired/mismatched
    

    The ticket must match the session and the role it was issued for.

  3. Relay (0.14.3: rule-table driven, extensible without server changes):

    Event Direction Payload
    frame agent → all viewers binary (Buffer/ArrayBuffer) frame data
    input.mouse / input.keyboard viewer → agent input event object
    input.sas viewer → agent secure-attention (Ctrl-Alt-Del) request
    remote.setmonitor viewer → agent multi-monitor switch request
    rtc.offer agent → viewers WebRTC SDP offer (agent is the offerer)
    rtc.answer / rtc.ready viewer → agent SDP answer / "viewer subscribed, send the offer"
    rtc.ice both → opposite role trickle ICE candidates
    any other event sender → opposite role relayed verbatim (catch-all)

    One agent per session, any number of viewers; everything is scoped to the session's two rooms. The rtc.* events are the WebRTC signaling bus: the session can negotiate a peer-to-peer DataChannel and move frames/input off the relay, falling back to it when P2P fails.

    The catch-all relays events with no rule to the opposite role's room, so new event names work with zero server changes. Reserved names (auth, auth_ok, auth_error, socket.io internals) are never relayed — a party cannot forge server control events.

Relay configuration

The rule table is operator-extensible via the hot-reloadable remote settings block (applies within ~1 s, no restart):

"remote": {
  "relay": {
    "clipboard.set": { "from": "viewer", "to": "agent" },
    "input.sas":     { "enabled": false }
  },
  "relay_unlisted": "opposite"
}
  • Rule fields: from (agent / viewer / any — sender role guard), to (agent / viewers / opposite), enabled: false to drop.
  • relay_unlisted: "opposite" (default) or "off" for a strict allow-list.
  • Settings are deep-merged: re-enabling a disabled event needs an explicit "enabled": true.
  • Acknowledgement callbacks are stripped before re-emit; multi-argument payloads relay intact.

Session inspection

Server-side:

server.remote.getSession(sessionId);   // { session_id, agent_connected, viewer_count }
server.remote.listSessions();
server.remote.issueTicket({ session_id, realm, machine_id, role });

Security notes

  • Tickets are the only way into /remote — there is no password fallback.
  • Issuing a ticket requires an (authenticated, when auth is on) main- namespace connection, so realm/role policy gates who can start sessions.
  • Expired or reused-for-the-wrong-session tickets are rejected with auth_error.

Clone this wiki locally