mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 21:58:26 +00:00
refactor(browser): dedupe storage and download route parsing
This commit is contained in:
@@ -18,9 +18,34 @@ import {
|
|||||||
resolvePathWithinRoot,
|
resolvePathWithinRoot,
|
||||||
resolvePathsWithinRoot,
|
resolvePathsWithinRoot,
|
||||||
} from "./path-output.js";
|
} from "./path-output.js";
|
||||||
import type { BrowserRouteRegistrar } from "./types.js";
|
import type { BrowserResponse, BrowserRouteRegistrar } from "./types.js";
|
||||||
import { jsonError, toBoolean, toNumber, toStringArray, toStringOrEmpty } from "./utils.js";
|
import { jsonError, toBoolean, toNumber, toStringArray, toStringOrEmpty } from "./utils.js";
|
||||||
|
|
||||||
|
function resolveDownloadPathOrRespond(res: BrowserResponse, requestedPath: string): string | null {
|
||||||
|
const downloadPathResult = resolvePathWithinRoot({
|
||||||
|
rootDir: DEFAULT_DOWNLOAD_DIR,
|
||||||
|
requestedPath,
|
||||||
|
scopeLabel: "downloads directory",
|
||||||
|
});
|
||||||
|
if (!downloadPathResult.ok) {
|
||||||
|
res.status(400).json({ error: downloadPathResult.error });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return downloadPathResult.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDownloadRequestBase(cdpUrl: string, targetId: string, timeoutMs: number | undefined) {
|
||||||
|
return {
|
||||||
|
cdpUrl,
|
||||||
|
targetId,
|
||||||
|
timeoutMs: timeoutMs ?? undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function respondWithDownloadResult(res: BrowserResponse, targetId: string, result: unknown) {
|
||||||
|
res.json({ ok: true, targetId, download: result });
|
||||||
|
}
|
||||||
|
|
||||||
export function registerBrowserAgentActRoutes(
|
export function registerBrowserAgentActRoutes(
|
||||||
app: BrowserRouteRegistrar,
|
app: BrowserRouteRegistrar,
|
||||||
ctx: BrowserRouteContext,
|
ctx: BrowserRouteContext,
|
||||||
@@ -443,24 +468,18 @@ export function registerBrowserAgentActRoutes(
|
|||||||
run: async ({ cdpUrl, tab, pw }) => {
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
let downloadPath: string | undefined;
|
let downloadPath: string | undefined;
|
||||||
if (out.trim()) {
|
if (out.trim()) {
|
||||||
const downloadPathResult = resolvePathWithinRoot({
|
const resolvedDownloadPath = resolveDownloadPathOrRespond(res, out);
|
||||||
rootDir: DEFAULT_DOWNLOAD_DIR,
|
if (!resolvedDownloadPath) {
|
||||||
requestedPath: out,
|
|
||||||
scopeLabel: "downloads directory",
|
|
||||||
});
|
|
||||||
if (!downloadPathResult.ok) {
|
|
||||||
res.status(400).json({ error: downloadPathResult.error });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
downloadPath = downloadPathResult.path;
|
downloadPath = resolvedDownloadPath;
|
||||||
}
|
}
|
||||||
|
const requestBase = buildDownloadRequestBase(cdpUrl, tab.targetId, timeoutMs);
|
||||||
const result = await pw.waitForDownloadViaPlaywright({
|
const result = await pw.waitForDownloadViaPlaywright({
|
||||||
cdpUrl,
|
...requestBase,
|
||||||
targetId: tab.targetId,
|
|
||||||
path: downloadPath,
|
path: downloadPath,
|
||||||
timeoutMs: timeoutMs ?? undefined,
|
|
||||||
});
|
});
|
||||||
res.json({ ok: true, targetId: tab.targetId, download: result });
|
respondWithDownloadResult(res, tab.targetId, result);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -485,23 +504,17 @@ export function registerBrowserAgentActRoutes(
|
|||||||
targetId,
|
targetId,
|
||||||
feature: "download",
|
feature: "download",
|
||||||
run: async ({ cdpUrl, tab, pw }) => {
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
const downloadPathResult = resolvePathWithinRoot({
|
const downloadPath = resolveDownloadPathOrRespond(res, out);
|
||||||
rootDir: DEFAULT_DOWNLOAD_DIR,
|
if (!downloadPath) {
|
||||||
requestedPath: out,
|
|
||||||
scopeLabel: "downloads directory",
|
|
||||||
});
|
|
||||||
if (!downloadPathResult.ok) {
|
|
||||||
res.status(400).json({ error: downloadPathResult.error });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const requestBase = buildDownloadRequestBase(cdpUrl, tab.targetId, timeoutMs);
|
||||||
const result = await pw.downloadViaPlaywright({
|
const result = await pw.downloadViaPlaywright({
|
||||||
cdpUrl,
|
...requestBase,
|
||||||
targetId: tab.targetId,
|
|
||||||
ref,
|
ref,
|
||||||
path: downloadPathResult.path,
|
path: downloadPath,
|
||||||
timeoutMs: timeoutMs ?? undefined,
|
|
||||||
});
|
});
|
||||||
res.json({ ok: true, targetId: tab.targetId, download: result });
|
respondWithDownloadResult(res, tab.targetId, result);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { parseStorageKind, parseStorageMutationRequest } from "./agent.storage.js";
|
import {
|
||||||
|
parseRequiredStorageMutationRequest,
|
||||||
|
parseStorageKind,
|
||||||
|
parseStorageMutationRequest,
|
||||||
|
} from "./agent.storage.js";
|
||||||
|
|
||||||
describe("browser storage route parsing", () => {
|
describe("browser storage route parsing", () => {
|
||||||
describe("parseStorageKind", () => {
|
describe("parseStorageKind", () => {
|
||||||
@@ -37,4 +41,25 @@ describe("browser storage route parsing", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("parseRequiredStorageMutationRequest", () => {
|
||||||
|
it("returns parsed request for supported kinds", () => {
|
||||||
|
expect(
|
||||||
|
parseRequiredStorageMutationRequest("session", {
|
||||||
|
targetId: " tab-9 ",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
kind: "session",
|
||||||
|
targetId: "tab-9",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for unsupported kind", () => {
|
||||||
|
expect(
|
||||||
|
parseRequiredStorageMutationRequest("cookie", {
|
||||||
|
targetId: "tab-1",
|
||||||
|
}),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
resolveTargetIdFromQuery,
|
resolveTargetIdFromQuery,
|
||||||
withPlaywrightRouteContext,
|
withPlaywrightRouteContext,
|
||||||
} from "./agent.shared.js";
|
} from "./agent.shared.js";
|
||||||
import type { BrowserRouteRegistrar } from "./types.js";
|
import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js";
|
||||||
import { jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js";
|
import { jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js";
|
||||||
|
|
||||||
type StorageKind = "local" | "session";
|
type StorageKind = "local" | "session";
|
||||||
@@ -27,6 +27,42 @@ export function parseStorageMutationRequest(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseRequiredStorageMutationRequest(
|
||||||
|
kindParam: unknown,
|
||||||
|
body: Record<string, unknown>,
|
||||||
|
): { kind: StorageKind; targetId: string | undefined } | null {
|
||||||
|
const parsed = parseStorageMutationRequest(kindParam, body);
|
||||||
|
if (!parsed.kind) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
kind: parsed.kind,
|
||||||
|
targetId: parsed.targetId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStorageMutationOrRespond(
|
||||||
|
res: BrowserResponse,
|
||||||
|
kindParam: unknown,
|
||||||
|
body: Record<string, unknown>,
|
||||||
|
) {
|
||||||
|
const parsed = parseRequiredStorageMutationRequest(kindParam, body);
|
||||||
|
if (!parsed) {
|
||||||
|
jsonError(res, 400, "kind must be local|session");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStorageMutationFromRequest(req: BrowserRequest, res: BrowserResponse) {
|
||||||
|
const body = readBody(req);
|
||||||
|
const parsed = parseStorageMutationOrRespond(res, req.params.kind, body);
|
||||||
|
if (!parsed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { body, parsed };
|
||||||
|
}
|
||||||
|
|
||||||
export function registerBrowserAgentStorageRoutes(
|
export function registerBrowserAgentStorageRoutes(
|
||||||
app: BrowserRouteRegistrar,
|
app: BrowserRouteRegistrar,
|
||||||
ctx: BrowserRouteContext,
|
ctx: BrowserRouteContext,
|
||||||
@@ -139,29 +175,27 @@ export function registerBrowserAgentStorageRoutes(
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post("/storage/:kind/set", async (req, res) => {
|
app.post("/storage/:kind/set", async (req, res) => {
|
||||||
const body = readBody(req);
|
const mutation = parseStorageMutationFromRequest(req, res);
|
||||||
const parsed = parseStorageMutationRequest(req.params.kind, body);
|
if (!mutation) {
|
||||||
if (!parsed.kind) {
|
return;
|
||||||
return jsonError(res, 400, "kind must be local|session");
|
|
||||||
}
|
}
|
||||||
const kind = parsed.kind;
|
const key = toStringOrEmpty(mutation.body.key);
|
||||||
const key = toStringOrEmpty(body.key);
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return jsonError(res, 400, "key is required");
|
return jsonError(res, 400, "key is required");
|
||||||
}
|
}
|
||||||
const value = typeof body.value === "string" ? body.value : "";
|
const value = typeof mutation.body.value === "string" ? mutation.body.value : "";
|
||||||
|
|
||||||
await withPlaywrightRouteContext({
|
await withPlaywrightRouteContext({
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
ctx,
|
ctx,
|
||||||
targetId: parsed.targetId,
|
targetId: mutation.parsed.targetId,
|
||||||
feature: "storage set",
|
feature: "storage set",
|
||||||
run: async ({ cdpUrl, tab, pw }) => {
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
await pw.storageSetViaPlaywright({
|
await pw.storageSetViaPlaywright({
|
||||||
cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
kind,
|
kind: mutation.parsed.kind,
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
@@ -171,24 +205,22 @@ export function registerBrowserAgentStorageRoutes(
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post("/storage/:kind/clear", async (req, res) => {
|
app.post("/storage/:kind/clear", async (req, res) => {
|
||||||
const body = readBody(req);
|
const mutation = parseStorageMutationFromRequest(req, res);
|
||||||
const parsed = parseStorageMutationRequest(req.params.kind, body);
|
if (!mutation) {
|
||||||
if (!parsed.kind) {
|
return;
|
||||||
return jsonError(res, 400, "kind must be local|session");
|
|
||||||
}
|
}
|
||||||
const kind = parsed.kind;
|
|
||||||
|
|
||||||
await withPlaywrightRouteContext({
|
await withPlaywrightRouteContext({
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
ctx,
|
ctx,
|
||||||
targetId: parsed.targetId,
|
targetId: mutation.parsed.targetId,
|
||||||
feature: "storage clear",
|
feature: "storage clear",
|
||||||
run: async ({ cdpUrl, tab, pw }) => {
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
await pw.storageClearViaPlaywright({
|
await pw.storageClearViaPlaywright({
|
||||||
cdpUrl,
|
cdpUrl,
|
||||||
targetId: tab.targetId,
|
targetId: tab.targetId,
|
||||||
kind,
|
kind: mutation.parsed.kind,
|
||||||
});
|
});
|
||||||
res.json({ ok: true, targetId: tab.targetId });
|
res.json({ ok: true, targetId: tab.targetId });
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user