Files
platform/src/servers/api/desktop/websocket.ts
T
brunorezioandClaude Opus 5 044aacf4d5 remove the dead multi-user surface
Officer is single-user: the server owner is the only account, created once by
/auth/bootstrap. Everything that existed to serve additional users was
unreachable, so it is gone rather than left looking like it does something.

Accounts: drop the invite / resend-invite / delete / list-users routes and the
Users settings screen, the inert /auth/signup handler, and the account
verification chain it fed (verify, resend-verification, VerifyScreen, the
UserInvite + VerifyAdmin + VerifyRegistration templates). /auth/verify-token
survives for password resets only, and now requires a reset-password token
rather than accepting any signed JWT.

Roles: drop the users.role column and the four-value USER_ROLES enum. The
permissions table granted every role identical methods, and every
role === 'Super Admin' check was permanently true. The JWT no longer carries a
role claim.

Sandbox: remove sidecar/sandbox.ts and its five call sites. bwrap was selected
only for non-Super-Admin users, so it never ran. It was also not a usable agent
jail as written — --share-net, the project root (with .env) bound read-only,
and runuser dropping to the server's own uid. Rebuilding it for agent
containment would be a different construction, and git history keeps this one.

getHomeDir keeps its DATA_PATH meaning; the new getOwnerHomeDir resolves the
owner's real login home, which is what terminals, chats and task runs use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 23:30:20 +01:00

123 lines
3.1 KiB
TypeScript

import type { ServerWebSocket } from 'bun';
import type { Socket } from 'bun';
import * as sidecar from '@@/sidecar-registry';
type WSData = { userId: number; email: string; username: string; sessionId?: string };
type VncSession = {
tcpSocket: Socket<{ ws: ServerWebSocket<WSData> }> | null;
pendingMessages: Buffer[];
};
const sessions = new Map<ServerWebSocket<WSData>, VncSession>();
export const desktopWebsocket = {
async open(ws: ServerWebSocket<WSData>) {
const session: VncSession = { tcpSocket: null, pendingMessages: [] };
sessions.set(ws, session);
let port: number;
try {
const result = await sidecar.startVnc({
email: ws.data.email,
username: ws.data.username,
});
port = result.port;
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to start VNC session';
console.error('[desktop] VNC start failed:', message);
sessions.delete(ws);
ws.close(4004, 'VNC server unavailable');
return;
}
try {
const tcpSocket = await Bun.connect({
hostname: '127.0.0.1',
port,
socket: {
data(_socket, data) {
try {
ws.sendBinary(Buffer.from(data));
} catch {
// ws already closed
}
},
close() {
sessions.delete(ws);
try {
ws.close();
} catch {
/* ignore */
}
},
error(_socket, err) {
console.error('[desktop] TCP error:', err.message);
sessions.delete(ws);
try {
ws.close();
} catch {
/* ignore */
}
},
connectError(_socket, err) {
console.error('[desktop] TCP connect error:', err.message);
sessions.delete(ws);
try {
ws.close(4004, 'VNC server unavailable');
} catch {
/* ignore */
}
},
open(socket) {
for (const msg of session.pendingMessages) {
socket.write(msg);
}
session.pendingMessages = [];
},
},
data: { ws },
});
session.tcpSocket = tcpSocket;
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to connect to VNC server';
console.error('[desktop] VNC connect failed:', message);
sessions.delete(ws);
ws.close(4004, 'VNC server unavailable');
}
},
message(ws: ServerWebSocket<WSData>, raw: string | Buffer) {
const session = sessions.get(ws);
if (!session) return;
const data = typeof raw === 'string' ? Buffer.from(raw) : Buffer.from(raw);
if (!session.tcpSocket) {
session.pendingMessages.push(data);
return;
}
try {
session.tcpSocket.write(data);
} catch {
// ignore
}
},
close(ws: ServerWebSocket<WSData>) {
const session = sessions.get(ws);
if (session?.tcpSocket) {
try {
session.tcpSocket.end();
} catch {
// ignore
}
}
sessions.delete(ws);
},
drain() {},
};