98 lines · 3.8 KB
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<string, unknown> {
  return typeof value === "object" && value !== null;
}

function committedResult(data: Record<string, unknown>): 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<HTMLIFrameElement>(null);
  const [error, setError] = useState<string | null>(null);
  const [committed, setCommitted] = useState<Committed | null>(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 {