Files
platform/src/servers/api/minesweeper/router.ts
T
pastilhasandClaude Fable 5 af918251d3 /minesweeper — a wine session streamed to the browser over RFB
a websocket byte relay onto x11vnc's tcp socket, the same shape as the
terminal's relay onto the pty sidecar. this is the piece plugins/desktop
could not have: a plugin cannot claim a websocket provider.

the rfb client is ours rather than vendored novnc — raw and copyrect,
one pixel format we dictate so a rectangle memcpys into ImageData. we
control the server, so the other eight encodings are surface we would
carry and never run.

execution, so owner-only: wine runs as the owner, which makes the game's
menus file dialogs over the owner's home.

two bugs worth keeping. the token key is BEARER_TOKEN, not token, so the
upgrade 401'd before any rfb byte moved. and the /status health check
opened a tcp connection to prove liveness — libvncserver blocks its
accept loop peeking for a websocket handshake, so a polled health check
filled the backlog and locked out the real viewer. a liveness check must
not be indistinguishable from a client; it reads /proc/net/tcp now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 21:39:35 +00:00

29 lines
1.0 KiB
TypeScript

import { createRouter } from '../../create-router';
import { sessionStatus, startSession, stopSession } from './session';
/**
* Session control. The pixels do not come through here — they come through the websocket relay; this
* is only "is it up, bring it up, take it down", plus the window size the panel needs to size its
* canvas.
*/
export const minesweeperRouter = createRouter();
minesweeperRouter.get('/status', async (c) => c.json(await sessionStatus()));
minesweeperRouter.post('/start', async (c) => {
const status = await sessionStatus();
if (!status.installed) {
return c.json({ error: 'Minesweeper is not installed on this server (wine-minesweeper is missing)' }, 501);
}
if (status.running) return c.json(status);
const result = await startSession();
if (!result.ok) return c.json({ error: `Could not start the session: ${result.out.trim()}` }, 500);
return c.json(await sessionStatus());
});
minesweeperRouter.post('/stop', async (c) => {
await stopSession();
return c.json(await sessionStatus());
});