Feature · 02

Live console

Logs stream from the container's stdio over a single WebSocket per server, multiplexed across every connected client. Commands typed in the panel land directly in the JVM.

01

Handshake

Connect to /ws/console with the session JWT in the Socket.IO auth payload (a query-string token also works). The gateway validates it in handleConnection and disconnects on failure before any room is joined.

Terminal
// Token comes from the panel session; fetch it from GET /api/auth/token,
// then hand it to Socket.IO in the auth payload.
const ws = io("https://cexi.my.id", {
  path: "/ws/console",
  transports: ["websocket", "polling"],
  auth: { token },
});

ws.on("connect", () => ws.emit("join", { serverId }));
02

Replay buffer

The first event after join is history: the last 200 lines of the container's combined stdout/stderr, fetched via container.logs. This bridges the gap before the live attach stream takes over.

Terminal
ws.on("history", ({ lines }) => term.write(lines));
ws.on("log",     ({ data })  => term.write(data));
03

Multiplexing

M.A.F Cloud holds at most one Docker attach stream per running container and emits its output to a Socket.io room (server:<id>). Joining as a second client increments a refcount; leaving decrements it; the stream is torn down when the count reaches zero. This avoids the multi-attach buffering quirks the Docker daemon has when several clients hold their own stream concurrently. If that stream drops while the container is still running, the agent re-attaches on its own so the room keeps receiving output.

04

Commands

A command event is bounded at 1 KB and stripped of trailing newlines server-side, then a single \n is appended before the bytes are written to the attach stream's stdin half. Anything you would type at the server console works:say hello, op steve, stop.

Terminal
ws.emit("command", { serverId, text: "say hello" });
05

Resilience & auto-reconnect

The console heals itself on both ends. The browser client prefers a raw WebSocket but falls back to HTTP long-polling behind proxies that refuse the upgrade, and it reconnects with backoff — re-joining the room and replaying the tail on every reconnect.

When an agent reconnects to the API after an update, crash, or network blip, the panel re-attaches every open console for that node, so live output resumes without a page reload.

06

Proxies & idle timeouts

Cloudflare's free plan caps proxied WebSocket idle time at roughly 100 seconds. For a hosting panel where a session stays open while you read logs, that manifests as the console dropping every couple of minutes; keep the API hostname on DNS-only mode (grey cloud) so traffic hits the origin directly and the origin timeout governs.

Platforms that sleep idle instances — such as Render's free tier after about 15 minutes — will drop the agent and console while asleep. Both reconnect automatically the next time the API is hit and wakes.