- Add src/utils/sensitive.ts: redactUrl/redactValue/redactForLog - Apply redaction to MCP config printing (mcp.tsx) - Apply redaction to hard-fail logging (log.ts) - Apply redaction to chrome native host debug log - Apply redaction to CLI error output (exit.ts) - RCS: default bind to 127.0.0.1 instead of 0.0.0.0 - RCS: CORS restricted to localhost + baseUrl whitelist - pipeTransport: default bind to 127.0.0.1 via PIPE_HOST env
116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import { Hono } from "hono";
|
|
import { cors } from "hono/cors";
|
|
import { logger } from "hono/logger";
|
|
import { serveStatic } from "hono/bun";
|
|
import { config } from "./config";
|
|
import { closeAllConnections } from "./transport/ws-handler";
|
|
import { startDisconnectMonitor } from "./services/disconnect-monitor";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
// Routes
|
|
import v1Environments from "./routes/v1/environments";
|
|
import v1EnvironmentsWork from "./routes/v1/environments.work";
|
|
import v1Sessions from "./routes/v1/sessions";
|
|
import v1SessionIngress, { websocket } from "./routes/v1/session-ingress";
|
|
import v2CodeSessions from "./routes/v2/code-sessions";
|
|
import v2Worker from "./routes/v2/worker";
|
|
import v2WorkerEventsStream from "./routes/v2/worker-events-stream";
|
|
import v2WorkerEvents from "./routes/v2/worker-events";
|
|
import webAuth from "./routes/web/auth";
|
|
import webSessions from "./routes/web/sessions";
|
|
import webControl from "./routes/web/control";
|
|
import webEnvironments from "./routes/web/environments";
|
|
|
|
console.log("[RCS] In-memory store ready (no SQLite)");
|
|
|
|
const app = new Hono();
|
|
const port = config.port;
|
|
const host = config.host;
|
|
|
|
const allowedWebOrigins = [
|
|
`http://localhost:${port}`,
|
|
`http://127.0.0.1:${port}`,
|
|
config.baseUrl,
|
|
].filter(Boolean);
|
|
|
|
// Middleware
|
|
app.use("*", logger());
|
|
app.use(
|
|
"/web/*",
|
|
cors({
|
|
origin: (origin) =>
|
|
allowedWebOrigins.includes(origin) ? origin : undefined,
|
|
credentials: true,
|
|
}),
|
|
);
|
|
|
|
// Health check
|
|
app.get("/health", (c) => c.json({ status: "ok", version: config.version }));
|
|
|
|
// Static files — serve web/ directory under /code path
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const webDir = resolve(__dirname, "../web");
|
|
|
|
const stripCodePrefix = (p: string) => p.replace(/^\/code/, "");
|
|
|
|
// Serve all static files under /code/* from web/ directory
|
|
app.use("/code/*", serveStatic({ root: webDir, rewriteRequestPath: stripCodePrefix }));
|
|
// /code, /code/, and /code/:sessionId — SPA fallback
|
|
app.get("/code", serveStatic({ root: webDir, path: "index.html" }));
|
|
app.get("/code/", serveStatic({ root: webDir, path: "index.html" }));
|
|
app.get("/code/:sessionId", serveStatic({ root: webDir, path: "index.html" }));
|
|
|
|
// v1 Environment routes
|
|
app.route("/v1/environments", v1Environments);
|
|
app.route("/v1/environments", v1EnvironmentsWork);
|
|
|
|
// v1 Session routes
|
|
app.route("/v1/sessions", v1Sessions);
|
|
|
|
// Session Ingress (WebSocket) — mounted at both /v1 and /v2 so the bridge
|
|
// client's buildSdkUrl works with or without an Envoy proxy rewriting /v1→/v2.
|
|
app.route("/v1/session_ingress", v1SessionIngress);
|
|
app.route("/v2/session_ingress", v1SessionIngress);
|
|
|
|
// v2 Code Sessions routes
|
|
app.route("/v1/code/sessions", v2CodeSessions);
|
|
app.route("/v1/code/sessions", v2Worker);
|
|
app.route("/v1/code/sessions", v2WorkerEventsStream);
|
|
app.route("/v1/code/sessions", v2WorkerEvents);
|
|
|
|
// Web control panel routes
|
|
app.route("/web", webAuth);
|
|
app.route("/web", webSessions);
|
|
app.route("/web", webControl);
|
|
app.route("/web", webEnvironments);
|
|
|
|
console.log(`[RCS] Remote Control Server starting on ${host}:${port}`);
|
|
console.log("[RCS] API key configuration loaded");
|
|
console.log(`[RCS] Base URL: ${config.baseUrl || `http://localhost:${port}`}`);
|
|
console.log(`[RCS] Disconnect timeout: ${config.disconnectTimeout}s`);
|
|
|
|
// Start disconnect monitor
|
|
startDisconnectMonitor();
|
|
|
|
export default {
|
|
port,
|
|
hostname: host,
|
|
fetch: app.fetch,
|
|
websocket: {
|
|
...websocket,
|
|
idleTimeout: 255, // WS idle timeout (seconds) — must be inside websocket object
|
|
},
|
|
idleTimeout: 255, // HTTP server idle timeout (seconds) — needed for long-polling endpoints
|
|
};
|
|
|
|
// Graceful shutdown
|
|
async function gracefulShutdown(signal: string) {
|
|
console.log(`\n[RCS] Received ${signal}, shutting down...`);
|
|
closeAllConnections();
|
|
process.exit(0);
|
|
}
|
|
|
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|