import { Link } from "@tanstack/react-router"; import { useEffect, useRef, useState } from "react"; import { mintEditorSession } from "~/fns/editor"; type Committed = { sha: string; branch: string; pullUrl: string | null }; const FRAME_SANDBOX = "allow-scripts allow-modals"; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function committedResult(data: Record): Committed | null { const result = data.result; if (!isRecord(result) || typeof result.sha !== "string" || typeof result.branch !== "string") return null; return { sha: result.sha, branch: result.branch, pullUrl: typeof result.pullUrl === "string" ? result.pullUrl : null, }; } /** * Hosts the workspace editor in a sandboxed, opaque-origin iframe. The frame never sees session cookies: * this page mints a ref-scoped token and hands it over with postMessage once the frame says it's ready. */ export function WorkspaceEditor({ owner, repo, gitRef }: { owner: string; repo: string; gitRef: string }) { const frame = useRef(null); const [error, setError] = useState(null); const [committed, setCommitted] = useState(null); useEffect(() => { let handedOver = false; async function onMessage(event: MessageEvent) { const target = frame.current?.contentWindow; const message: unknown = event.data; if (!target || event.source !== target || event.origin !== "null" || !isRecord(message)) return; if (message.type === "gitfor-editor:ready" && !handedOver) { handedOver = true; try { const session = await mintEditorSession({ data: { owner, repo, ref: gitRef } }); // The frame may have navigated during the await; only hand the token to the same browsing context. if (frame.current?.contentWindow !== event.source) return; target.postMessage({ type: "gitfor-editor:init", token: session.token }, "*"); } catch (failure) { setError(failure instanceof Error ? failure.message : "Couldn't start the editor"); } } if (message.type === "gitfor-editor:committed") setCommitted(committedResult(message)); } window.addEventListener("message", onMessage); return () => window.removeEventListener("message", onMessage); }, [owner, repo, gitRef]); const pullPath = committed?.pullUrl ? new URL(committed.pullUrl).pathname : null; return (
← {owner}/{repo} {gitRef} {error && {error}} {committed && ( Committed{" "} {committed.sha.slice(0, 7)} {" "} to {committed.branch} {pullPath && ( <> {" · "} View pull request )} )}