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. Since 0.15.0 it is also the reference implementation of the Custom Namespaces plugin contract.

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