refactor(canvas): remove internal A2UI handler factory (#108038)

* refactor(canvas): remove internal A2UI handler factory

* chore(deadcode): refresh export baseline
This commit is contained in:
Peter Steinberger
2026-07-14 22:41:27 -07:00
committed by GitHub
parent d702f6fb33
commit 5bbf30b852
3 changed files with 31 additions and 45 deletions

View File

@@ -146,23 +146,6 @@ async function handleA2uiHttpRequestWithRootResolver(
}
}
/** Creates an HTTP handler for a specific hosted A2UI asset root. */
export function createA2uiHttpRequestHandler(params: {
rootDir: string;
liveReload?: boolean;
}): (req: IncomingMessage, res: ServerResponse) => Promise<boolean> {
let rootRealPromise: Promise<string> | null = null;
return async (req, res) => {
rootRealPromise ??= fs.realpath(params.rootDir);
return await handleA2uiHttpRequestWithRootResolver(
req,
res,
async () => await rootRealPromise,
{ liveReload: params.liveReload },
);
};
}
/** Handles one HTTP request for the hosted A2UI asset surface. */
export async function handleA2uiHttpRequest(
req: IncomingMessage,

View File

@@ -121,14 +121,13 @@ async function captureHandlerResponse(
}
async function captureA2uiFixtureResponse(
rootDir: string,
url: string,
method = "GET",
liveReload = true,
): Promise<CapturedResponse> {
const { createA2uiHttpRequestHandler } = await import("./a2ui.js");
const { handleA2uiHttpRequest } = await import("./a2ui.js");
return await captureHttpResponse(
createA2uiHttpRequestHandler({ rootDir, liveReload }),
async (req, res) => await handleA2uiHttpRequest(req, res, { liveReload }),
url,
method,
);
@@ -661,59 +660,64 @@ describe("canvas host", () => {
});
it("serves A2UI scaffold and blocks traversal/symlink escapes", async () => {
const a2uiRoot = await createCaseDir();
const nestedAssetDir = path.join(a2uiRoot, "assets", "demo");
const fixtureEntryDir = await createCaseDir();
const a2uiRoot = path.join(fixtureEntryDir, "a2ui");
const nestedAssetDir = path.join(
a2uiRoot,
`test-assets-${Date.now()}-${Math.random().toString(16).slice(2)}`,
);
const nestedAssetUrlPath = path.basename(nestedAssetDir);
const bundlePath = path.join(a2uiRoot, "a2ui.bundle.js");
const linkName = `test-link-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`;
const linkPath = path.join(a2uiRoot, linkName);
await fs.mkdir(nestedAssetDir, { recursive: true });
await fs.writeFile(
path.join(a2uiRoot, "index.html"),
`<openclaw-a2ui-host></openclaw-a2ui-host>
<script>openclawCanvasA2UIAction</script>`,
"utf8",
);
await fs.writeFile(path.join(a2uiRoot, "a2ui.bundle.js"), "window.openclawA2UI = {};", "utf8");
await fs.writeFile(path.join(nestedAssetDir, "sample.txt"), "nested asset", "utf8");
await fs.symlink(path.join(process.cwd(), "package.json"), linkPath);
const originalArgv = [...process.argv];
try {
const res = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/`);
process.argv[1] = path.join(fixtureEntryDir, "openclaw.mjs");
await fs.mkdir(nestedAssetDir, { recursive: true });
await fs.writeFile(
path.join(a2uiRoot, "index.html"),
`<openclaw-a2ui-host></openclaw-a2ui-host>
<script>openclawCanvasA2UIAction</script>`,
"utf8",
);
await fs.writeFile(bundlePath, "window.openclawA2UI = {};", "utf8");
await fs.writeFile(path.join(nestedAssetDir, "sample.txt"), "nested asset", "utf8");
await fs.symlink(path.join(process.cwd(), "package.json"), linkPath);
const res = await captureA2uiFixtureResponse(`${A2UI_PATH}/`);
const html = res.body;
expect(res.status).toBe(200);
expect(html).toContain("openclaw-a2ui-host");
expect(html).toContain("openclawCanvasA2UIAction");
const noReloadRes = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/`, "GET", false);
const noReloadRes = await captureA2uiFixtureResponse(`${A2UI_PATH}/`, "GET", false);
expect(noReloadRes.body).toContain("openclawCanvasA2UIAction");
expect(noReloadRes.body).not.toContain(CANVAS_WS_PATH);
const bundleRes = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/a2ui.bundle.js`);
const bundleRes = await captureA2uiFixtureResponse(`${A2UI_PATH}/a2ui.bundle.js`);
const js = bundleRes.body;
expect(bundleRes.status).toBe(200);
expect(js).toContain("openclawA2UI");
const assetRes = await captureA2uiFixtureResponse(
a2uiRoot,
`${A2UI_PATH}/assets/demo/sample.txt`,
`${A2UI_PATH}/${nestedAssetUrlPath}/sample.txt`,
);
expect(assetRes.status).toBe(200);
expect(assetRes.headers["content-type"]).toBe("text/plain");
expect(assetRes.body).toBe("nested asset");
const traversalRes = await captureA2uiFixtureResponse(
a2uiRoot,
`${A2UI_PATH}/%2e%2e%2fpackage.json`,
);
const traversalRes = await captureA2uiFixtureResponse(`${A2UI_PATH}/%2e%2e%2fpackage.json`);
expect(traversalRes.status).toBe(404);
expect(traversalRes.body).toBe("not found");
const malformedRes = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/%E0%A4%A`);
const malformedRes = await captureA2uiFixtureResponse(`${A2UI_PATH}/%E0%A4%A`);
expect(malformedRes.status).toBe(404);
expect(malformedRes.body).toBe("not found");
const symlinkRes = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/${linkName}`);
const symlinkRes = await captureA2uiFixtureResponse(`${A2UI_PATH}/${linkName}`);
expect(symlinkRes.status).toBe(404);
expect(symlinkRes.body).toBe("not found");
} finally {
process.argv.splice(0, process.argv.length, ...originalArgv);
await fs.rm(linkPath, { force: true });
}
});

View File

@@ -2,7 +2,6 @@
// New entries fail CI. After deleting dead code, run `pnpm deadcode:exports:update`.
// Do not add entries to avoid fixing new findings.
export const KNIP_UNUSED_EXPORT_BASELINE = [
"extensions/canvas/src/host/a2ui.ts: createA2uiHttpRequestHandler",
"extensions/codex/src/session-catalog.ts: CODEX_TERMINAL_RESUME_COMMAND",
"extensions/codex/src/session-upstream-activity.ts: checkCodexUpstreamActivity (upstream)",
"extensions/codex/src/session-upstream-activity.ts: classifyCodexUpstreamTurns",