import type { Router } from "express"; import { getLocalAgentContext } from "../context/localContext.js"; import { AnswerViewStateError, AnswerViewStateStore, answerViewStateIdentitySchema } from "../sessions/answer-view-state-store.js"; import { SessionContextMismatchError, type SessionMetadataStore } from "../sessions/metadataStore.js"; export function registerAnswerViewStateRoutes(router: Router, options: { sessions: SessionMetadataStore; states: AnswerViewStateStore }) { const handle = (method: "get" | "put") => { router[method]("/session/:session_id/ui-state", async (req, res) => { res.setHeader("Cache-Control", "private, no-store"); try { const session = await options.sessions.get(getLocalAgentContext(req), req.params.session_id); if (!session) { res.status(404).json({ code: "SESSION_NOT_FOUND", detail: "session not found" }); return; } const context = { sessionId: session.sessionId, actorKey: session.actorKey, projectKey: session.projectKey }; if (method === "get") { const parsed = answerViewStateIdentitySchema.safeParse(req.query); if (!parsed.success) { res.status(400).json({ code: "ANSWER_VIEW_STATE_INVALID", detail: "invalid answer view identity" }); return; } res.json(await options.states.get(context, parsed.data)); } else res.json(await options.states.put(context, req.body)); } catch (error) { if (error instanceof AnswerViewStateError) { res.status(error.status).json({ code: error.code, detail: error.message, ...(error.current ? { current: error.current } : {}) }); return; } if (error instanceof SessionContextMismatchError) { res.status(404).json({ code: "SESSION_NOT_FOUND", detail: "session not found" }); return; } // Filesystem or corrupt record errors must not disclose host paths or saved form values. res.status(503).json({ code: "ANSWER_VIEW_STATE_UNAVAILABLE", detail: "answer view state unavailable" }); } }); }; handle("get"); handle("put"); }