import { z } from "zod";
import { badRequest, forbidden, HttpError, unauthorized } from "../errors";
import { isSafeTreePath } from "../git/writer";
import { limitBrowse, limitWrite } from "../limits";
import { json } from "./http";
import { editorRoomStub } from "./room-client";
import type { RoomJoin } from "./room";
import { canWrite, EDITOR_TOKEN_PREFIX, resolveEditorToken } from "./session";
import type { EditorSession } from "./session";
import { EDITOR_WS_PROTOCOL } from "~/lib/editor-protocol";
const MAX_EDITS = 200;
const agentName = z
.string()
.trim()
.min(1)
.max(60)
.regex(/^[\w .@-]+$/, "Agent names may use letters, digits, spaces and . _ @ -");
function requireBranch(session: EditorSession): void {
if (session.revision.kind !== "branch") throw badRequest("Live editing needs a branch");
}
function roomTarget(session: EditorSession) {
return { repoId: session.repo.id, branch: session.revision.name, head: session.revision.sha };
}
/**
* WebSocket upgrade for the co-editing room. Browsers can't set headers on sockets, so the editor token
* travels as a second subprotocol next to `gitfor-editor.v1`; it is never put in the URL (logs, history).
*/
export async function handleRoomSocket(request: Request, url: URL): Promise<Response> {
const protocols = (request.headers.get("sec-websocket-protocol") ?? "").split(",").map((part) => part.trim());
if (!protocols.includes(EDITOR_WS_PROTOCOL)) throw badRequest(`Use the ${EDITOR_WS_PROTOCOL} subprotocol`);
const token = protocols.find((part) => part.startsWith(`${EDITOR_TOKEN_PREFIX}_`));
if (!token) throw unauthorized("An editor session token is required");
const session = await resolveEditorToken(token);
await limitBrowse(session.viewer, request);
requireBranch(session);
const agent = url.searchParams.get("agent");