- Add device/session/step DB persistence in server agent loop - Add goal preprocessor for compound goals (e.g., "open YouTube and search X") - Add step-level logging to agent loop - Fix dashboard WebSocket auth (direct DB token lookup instead of auth.api) - Fix web layout to use locals.session.token instead of cookie - Add dashboard-ws.svelte.ts WebSocket store with auto-reconnect - Rewrite devices page with direct DB queries and real-time updates - Add device detail page with live step display and session history - Add Android companion app resources, themes, and screen capture consent Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
2.8 KiB
TypeScript
99 lines
2.8 KiB
TypeScript
import { Hono } from "hono";
|
|
import { cors } from "hono/cors";
|
|
import { auth } from "./auth.js";
|
|
import { env } from "./env.js";
|
|
import { handleDeviceMessage, handleDeviceClose } from "./ws/device.js";
|
|
import {
|
|
handleDashboardMessage,
|
|
handleDashboardClose,
|
|
} from "./ws/dashboard.js";
|
|
import type { WebSocketData } from "./ws/sessions.js";
|
|
import { devices } from "./routes/devices.js";
|
|
import { goals } from "./routes/goals.js";
|
|
import { health } from "./routes/health.js";
|
|
|
|
const app = new Hono();
|
|
|
|
// CORS for dashboard
|
|
app.use(
|
|
"*",
|
|
cors({
|
|
origin: env.CORS_ORIGIN,
|
|
allowHeaders: ["Content-Type", "Authorization"],
|
|
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
credentials: true,
|
|
})
|
|
);
|
|
|
|
// Better Auth handler
|
|
app.on(["POST", "GET"], "/api/auth/*", (c) => {
|
|
return auth.handler(c.req.raw);
|
|
});
|
|
|
|
// REST routes
|
|
app.route("/devices", devices);
|
|
app.route("/goals", goals);
|
|
app.route("/health", health);
|
|
|
|
// Start server with WebSocket support
|
|
const server = Bun.serve<WebSocketData>({
|
|
port: env.PORT,
|
|
fetch(req, server) {
|
|
const url = new URL(req.url);
|
|
|
|
// WebSocket upgrade for device connections
|
|
if (url.pathname === "/ws/device") {
|
|
const upgraded = server.upgrade(req, {
|
|
data: { path: "/ws/device" as const, authenticated: false },
|
|
});
|
|
if (upgraded) return undefined;
|
|
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
}
|
|
|
|
// WebSocket upgrade for dashboard connections
|
|
if (url.pathname === "/ws/dashboard") {
|
|
const upgraded = server.upgrade(req, {
|
|
data: { path: "/ws/dashboard" as const, authenticated: false },
|
|
});
|
|
if (upgraded) return undefined;
|
|
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
}
|
|
|
|
// Non-WebSocket requests go to Hono
|
|
return app.fetch(req);
|
|
},
|
|
websocket: {
|
|
idleTimeout: 120,
|
|
sendPings: true,
|
|
open(ws) {
|
|
console.log(`WebSocket opened: ${ws.data.path}`);
|
|
},
|
|
message(ws, message) {
|
|
const raw =
|
|
typeof message === "string"
|
|
? message
|
|
: new TextDecoder().decode(message);
|
|
|
|
if (ws.data.path === "/ws/device") {
|
|
handleDeviceMessage(ws, raw).catch((err) => {
|
|
console.error(`Device message handler error: ${err}`);
|
|
});
|
|
} else if (ws.data.path === "/ws/dashboard") {
|
|
handleDashboardMessage(ws, raw).catch((err) => {
|
|
console.error(`Dashboard message handler error: ${err}`);
|
|
});
|
|
}
|
|
},
|
|
close(ws, code, reason) {
|
|
console.log(`WebSocket closed: ${ws.data.path} device=${ws.data.deviceId ?? "unknown"} code=${code} reason=${reason}`);
|
|
if (ws.data.path === "/ws/device") {
|
|
handleDeviceClose(ws);
|
|
} else if (ws.data.path === "/ws/dashboard") {
|
|
handleDashboardClose(ws);
|
|
}
|
|
},
|
|
},
|
|
});
|
|
|
|
console.log(`Server running on port ${server.port}`);
|