mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 22:58:42 -10:00
fix(ui): route all /open-cmux callers through postOpenCmux (OPR.0.4.0.1)
Guard blocker: consolidated all 4 /open-cmux UI callers through the shared postOpenCmux helper (LiveNodeDetails, RigNode, LaunchCmuxButton + useCmuxLaunch). No direct fetch to /open-cmux remains in consumers. postOpenCmux owns the URL construction + terminalAuthHeaders(), so the runtime fetch-intercept test for postOpenCmux covers all callers. Source guard: verifies all 3 consumer files import postOpenCmux and have no direct fetch to /open-cmux. Fails closed on zero matches. 7/7 token-carry + 51/51 UI. tsc 0.
This commit is contained in:
1 parent
74541194c7
commit
342cd85cfa
4 files changed
+33
-44
No files matched your search
@@ -48,7 +48,7 @@ import {
|
||||
import { getRestoreStatusColorClass } from "../lib/restore-status-colors.js";
|
||||
import type { AgentSpecReview } from "../hooks/useSpecReview.js";
|
||||
import { RuntimeBadge, ToolMark } from "./graphics/RuntimeMark.js";
|
||||
import { terminalAuthHeaders } from "./mission-control/missionControlAuth.js";
|
||||
import { postOpenCmux } from "../hooks/useCmuxLaunch.js";
|
||||
|
||||
type Tab = "overview" | "details";
|
||||
|
||||
@@ -188,10 +188,7 @@ function ActionButtonsRow({ rigId, logicalId, data }: { rigId: string; logicalId
|
||||
};
|
||||
const handleOpenCmux = async () => {
|
||||
try {
|
||||
await fetch(
|
||||
`/api/rigs/${encodeURIComponent(rigId)}/nodes/${encodeURIComponent(logicalId)}/open-cmux`,
|
||||
{ method: "POST", headers: terminalAuthHeaders() },
|
||||
);
|
||||
await postOpenCmux({ rigId, logicalId });
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { TerminalPreviewPopover } from "./topology/TerminalPreviewPopover.js";
|
||||
import type { TopologyActivityVisual } from "../lib/topology-activity.js";
|
||||
import { formatCompactTokenCount, formatTokenTotalTitle, sumTokenCounts } from "../lib/token-format.js";
|
||||
import { formatRuntimeModel } from "../lib/runtime-brand.js";
|
||||
import { terminalAuthHeaders } from "./mission-control/missionControlAuth.js";
|
||||
import { postOpenCmux } from "../hooks/useCmuxLaunch.js";
|
||||
import { RuntimeBadge, ToolMark } from "./graphics/RuntimeMark.js";
|
||||
|
||||
interface RigNodeData {
|
||||
@@ -143,10 +143,8 @@ export function RigNode({ data }: { data: RigNodeData }) {
|
||||
e.stopPropagation();
|
||||
if (!data.rigId) return;
|
||||
try {
|
||||
const res = await fetch(`/api/rigs/${encodeURIComponent(data.rigId)}/nodes/${encodeURIComponent(data.logicalId)}/open-cmux`, { method: "POST", headers: terminalAuthHeaders() });
|
||||
if (res.ok) {
|
||||
flashFeedback("cmux");
|
||||
}
|
||||
const result = await postOpenCmux({ rigId: data.rigId, logicalId: data.logicalId });
|
||||
if (result.ok) flashFeedback("cmux");
|
||||
} catch { /* best-effort */ }
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { launchRigCmux } from "../../hooks/launchRigCmux.js";
|
||||
import { terminalAuthHeaders } from "../mission-control/missionControlAuth.js";
|
||||
import { postOpenCmux } from "../../hooks/useCmuxLaunch.js";
|
||||
|
||||
interface LaunchCmuxButtonProps {
|
||||
rigId: string;
|
||||
@@ -140,10 +140,12 @@ export function LaunchCmuxButton({ rigId }: LaunchCmuxButtonProps) {
|
||||
if (openMissingRef.current) openMissingRef.current.disabled = true;
|
||||
Promise.allSettled(
|
||||
ids.map(async (logicalId) => {
|
||||
const res = await fetch(`/api/rigs/${encodeURIComponent(rigId)}/nodes/${encodeURIComponent(logicalId)}/open-cmux`, { method: "POST", headers: terminalAuthHeaders() });
|
||||
if (!res.ok) return { ok: false };
|
||||
const body = await res.json().catch(() => ({})) as { ok?: boolean };
|
||||
return { ok: body.ok === true };
|
||||
try {
|
||||
const result = await postOpenCmux({ rigId, logicalId });
|
||||
return { ok: result.ok === true };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
}
|
||||
}),
|
||||
).then((results) => {
|
||||
const opened = results.filter((r) => r.status === "fulfilled" && (r as PromiseFulfilledResult<{ ok: boolean }>).value.ok).length;
|
||||
|
||||
@@ -44,17 +44,6 @@ describe("terminal-token carry on protected UI fetches", () => {
|
||||
expect(headers?.Authorization).toBe(`Bearer ${TOKEN}`);
|
||||
});
|
||||
|
||||
it("terminalAuthHeaders reads token from localStorage", async () => {
|
||||
const { terminalAuthHeaders } = await import("../src/components/mission-control/missionControlAuth.js");
|
||||
expect(terminalAuthHeaders().Authorization).toBe(`Bearer ${TOKEN}`);
|
||||
});
|
||||
|
||||
it("terminalAuthHeaders returns empty when no token", async () => {
|
||||
window.localStorage.removeItem("openrig.terminalBearerToken");
|
||||
const { terminalAuthHeaders } = await import("../src/components/mission-control/missionControlAuth.js");
|
||||
expect(terminalAuthHeaders().Authorization).toBeUndefined();
|
||||
});
|
||||
|
||||
it("postOpenCmux sends Authorization: Bearer <token>", async () => {
|
||||
const { calls } = stubFetch();
|
||||
const { postOpenCmux } = await import("../src/hooks/useCmuxLaunch.js");
|
||||
@@ -66,37 +55,40 @@ describe("terminal-token carry on protected UI fetches", () => {
|
||||
expect(headers?.Authorization).toBe(`Bearer ${TOKEN}`);
|
||||
});
|
||||
|
||||
it("source guard: all /open-cmux fetches carry terminalAuthHeaders", async () => {
|
||||
it("terminalAuthHeaders reads token from localStorage", async () => {
|
||||
const { terminalAuthHeaders } = await import("../src/components/mission-control/missionControlAuth.js");
|
||||
expect(terminalAuthHeaders().Authorization).toBe(`Bearer ${TOKEN}`);
|
||||
});
|
||||
|
||||
it("terminalAuthHeaders returns empty when no token", async () => {
|
||||
window.localStorage.removeItem("openrig.terminalBearerToken");
|
||||
const { terminalAuthHeaders } = await import("../src/components/mission-control/missionControlAuth.js");
|
||||
expect(terminalAuthHeaders().Authorization).toBeUndefined();
|
||||
});
|
||||
|
||||
it("source guard: all /open-cmux consumers use postOpenCmux (no direct fetch)", async () => {
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const srcDir = path.resolve(import.meta.dirname, "../src");
|
||||
const files = [
|
||||
"hooks/useCmuxLaunch.ts",
|
||||
"components/topology/LaunchCmuxButton.tsx",
|
||||
const consumers = [
|
||||
"components/LiveNodeDetails.tsx",
|
||||
"components/RigNode.tsx",
|
||||
"components/topology/LaunchCmuxButton.tsx",
|
||||
];
|
||||
for (const file of files) {
|
||||
for (const file of consumers) {
|
||||
const src = fs.readFileSync(path.join(srcDir, file), "utf-8");
|
||||
const cmuxFetches = [...src.matchAll(/fetch\([^)]*open-cmux[^)]*\)/gs)];
|
||||
for (const match of cmuxFetches) {
|
||||
expect(match[0]).toContain("terminalAuthHeaders()");
|
||||
}
|
||||
expect(src, `${file} must import postOpenCmux`).toContain("postOpenCmux");
|
||||
const lines = src.split("\n");
|
||||
const directFetchLines = lines.filter((l) => l.includes("open-cmux") && l.includes("fetch("));
|
||||
expect(directFetchLines.length, `${file} has direct fetch to /open-cmux`).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("source guard: useCmuxLaunch wires terminalAuthHeaders", async () => {
|
||||
it("source guard: postOpenCmux owns the /open-cmux fetch with terminalAuthHeaders", async () => {
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const src = fs.readFileSync(path.resolve(import.meta.dirname, "../src/hooks/useCmuxLaunch.ts"), "utf-8");
|
||||
expect(src).toContain("terminalAuthHeaders()");
|
||||
expect(src).toContain("headers: terminalAuthHeaders()");
|
||||
});
|
||||
|
||||
it("source guard: LaunchCmuxButton wires terminalAuthHeaders", async () => {
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
const src = fs.readFileSync(path.resolve(import.meta.dirname, "../src/components/topology/LaunchCmuxButton.tsx"), "utf-8");
|
||||
expect(src).toContain("headers: terminalAuthHeaders()");
|
||||
expect(src).toContain("open-cmux");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user