import { Link } from "@tanstack/react-router"; import { useMutation } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; import { mintEditorSession } from "~/fns/editor"; import { FieldError } from "~/ui/Field"; /** * The workbench runs on its own origin, so it is a normal (not opaque) document there: VS Code needs * IndexedDB, workers and webviews. It still can't reach forge cookies or pages, because those live on * a different origin, and the CSP on that origin pins what it may load and contact. */ const FRAME_SANDBOX = "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads"; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } /** * Hosts VS Code for the Web for one ref. This page mints a ref-scoped editor token with the session * cookie and hands it to the editor origin over postMessage (exact target origin, re-checked after the * await), and re-mints when the editor asks for a refresh. The editor never sees cookies. */ export function WorkspaceEditor({ owner, repo, gitRef, editorOrigin, openPath, startContainer = false, }: { owner: string; repo: string; gitRef: string; editorOrigin: string | null; openPath: string | null; /** From `?container=1` ("Open in container"): the extension boots a container and opens a terminal. */ startContainer?: boolean; }) { const frame = useRef(null); const [error, setError] = useState( editorOrigin ? null : "The editor origin isn't configured (set EDITOR_ORIGIN).", ); const mint = useMutation({ mutationFn: mintEditorSession }); const mintSession = mint.mutateAsync; useEffect(() => { if (!editorOrigin) return; const origin = editorOrigin; let handing = false; let handedLoad: unknown = null; async function handOver(target: Window, source: MessageEventSource | null, load: unknown) { if (handing || (load !== null && load === handedLoad)) return; handing = true; try { const session = await mintSession({ data: { owner, repo, ref: gitRef } }); if (frame.current?.contentWindow !== source) return; target.postMessage({ type: "gitfor-editor:init", session: { ...session, openPath, startContainer } }, origin); if (load !== null) handedLoad = load; } catch (failure) { setError(failure instanceof Error ? failure.message : "Couldn't start the editor"); } finally { handing = false; } } function onMessage(event: MessageEvent) { const target = frame.current?.contentWindow; const message: unknown = event.data; if (!target || event.source !== target || event.origin !== origin || !isRecord(message)) return; if (message.type === "gitfor-editor:ready") void handOver(target, event.source, message.load ?? "initial"); if (message.type === "gitfor-editor:refresh") void handOver(target, event.source, null); if (message.type === "gitfor-editor:error") setError(String(message.message ?? "The editor failed to load")); } window.addEventListener("message", onMessage); return () => window.removeEventListener("message", onMessage); }, [owner, repo, gitRef, editorOrigin, openPath, startContainer, mintSession]); return (
← {owner}/{repo} {gitRef} {error && {error}}
{editorOrigin && (