mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 05:12:43 +00:00
refactor(browser): reuse shared tab context in snapshot routes
This commit is contained in:
@@ -18,82 +18,90 @@ import {
|
|||||||
readBody,
|
readBody,
|
||||||
requirePwAi,
|
requirePwAi,
|
||||||
resolveProfileContext,
|
resolveProfileContext,
|
||||||
|
withPlaywrightRouteContext,
|
||||||
|
withRouteTabContext,
|
||||||
} from "./agent.shared.js";
|
} from "./agent.shared.js";
|
||||||
import type { BrowserRouteRegistrar } from "./types.js";
|
import type { BrowserResponse, BrowserRouteRegistrar } from "./types.js";
|
||||||
import { jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js";
|
import { jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js";
|
||||||
|
|
||||||
|
async function saveBrowserMediaResponse(params: {
|
||||||
|
res: BrowserResponse;
|
||||||
|
buffer: Buffer;
|
||||||
|
contentType: string;
|
||||||
|
maxBytes: number;
|
||||||
|
targetId: string;
|
||||||
|
url: string;
|
||||||
|
}) {
|
||||||
|
await ensureMediaDir();
|
||||||
|
const saved = await saveMediaBuffer(
|
||||||
|
params.buffer,
|
||||||
|
params.contentType,
|
||||||
|
"browser",
|
||||||
|
params.maxBytes,
|
||||||
|
);
|
||||||
|
params.res.json({
|
||||||
|
ok: true,
|
||||||
|
path: path.resolve(saved.path),
|
||||||
|
targetId: params.targetId,
|
||||||
|
url: params.url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function registerBrowserAgentSnapshotRoutes(
|
export function registerBrowserAgentSnapshotRoutes(
|
||||||
app: BrowserRouteRegistrar,
|
app: BrowserRouteRegistrar,
|
||||||
ctx: BrowserRouteContext,
|
ctx: BrowserRouteContext,
|
||||||
) {
|
) {
|
||||||
app.post("/navigate", async (req, res) => {
|
app.post("/navigate", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const url = toStringOrEmpty(body.url);
|
const url = toStringOrEmpty(body.url);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return jsonError(res, 400, "url is required");
|
return jsonError(res, 400, "url is required");
|
||||||
}
|
}
|
||||||
try {
|
await withPlaywrightRouteContext({
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
req,
|
||||||
const pw = await requirePwAi(res, "navigate");
|
res,
|
||||||
if (!pw) {
|
ctx,
|
||||||
return;
|
targetId,
|
||||||
}
|
feature: "navigate",
|
||||||
const result = await pw.navigateViaPlaywright({
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
const result = await pw.navigateViaPlaywright({
|
||||||
targetId: tab.targetId,
|
cdpUrl,
|
||||||
url,
|
targetId: tab.targetId,
|
||||||
});
|
url,
|
||||||
res.json({ ok: true, targetId: tab.targetId, ...result });
|
});
|
||||||
} catch (err) {
|
res.json({ ok: true, targetId: tab.targetId, ...result });
|
||||||
handleRouteError(ctx, res, err);
|
},
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/pdf", async (req, res) => {
|
app.post("/pdf", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
||||||
try {
|
await withPlaywrightRouteContext({
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
req,
|
||||||
const pw = await requirePwAi(res, "pdf");
|
res,
|
||||||
if (!pw) {
|
ctx,
|
||||||
return;
|
targetId,
|
||||||
}
|
feature: "pdf",
|
||||||
const pdf = await pw.pdfViaPlaywright({
|
run: async ({ cdpUrl, tab, pw }) => {
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
const pdf = await pw.pdfViaPlaywright({
|
||||||
targetId: tab.targetId,
|
cdpUrl,
|
||||||
});
|
targetId: tab.targetId,
|
||||||
await ensureMediaDir();
|
});
|
||||||
const saved = await saveMediaBuffer(
|
await saveBrowserMediaResponse({
|
||||||
pdf.buffer,
|
res,
|
||||||
"application/pdf",
|
buffer: pdf.buffer,
|
||||||
"browser",
|
contentType: "application/pdf",
|
||||||
pdf.buffer.byteLength,
|
maxBytes: pdf.buffer.byteLength,
|
||||||
);
|
targetId: tab.targetId,
|
||||||
res.json({
|
url: tab.url,
|
||||||
ok: true,
|
});
|
||||||
path: path.resolve(saved.path),
|
},
|
||||||
targetId: tab.targetId,
|
});
|
||||||
url: tab.url,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
handleRouteError(ctx, res, err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/screenshot", async (req, res) => {
|
app.post("/screenshot", async (req, res) => {
|
||||||
const profileCtx = resolveProfileContext(req, res, ctx);
|
|
||||||
if (!profileCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = readBody(req);
|
const body = readBody(req);
|
||||||
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
const targetId = toStringOrEmpty(body.targetId) || undefined;
|
||||||
const fullPage = toBoolean(body.fullPage) ?? false;
|
const fullPage = toBoolean(body.fullPage) ?? false;
|
||||||
@@ -105,54 +113,55 @@ export function registerBrowserAgentSnapshotRoutes(
|
|||||||
return jsonError(res, 400, "fullPage is not supported for element screenshots");
|
return jsonError(res, 400, "fullPage is not supported for element screenshots");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await withRouteTabContext({
|
||||||
const tab = await profileCtx.ensureTabAvailable(targetId);
|
req,
|
||||||
let buffer: Buffer;
|
res,
|
||||||
const shouldUsePlaywright =
|
ctx,
|
||||||
profileCtx.profile.driver === "extension" || !tab.wsUrl || Boolean(ref) || Boolean(element);
|
targetId,
|
||||||
if (shouldUsePlaywright) {
|
run: async ({ profileCtx, tab, cdpUrl }) => {
|
||||||
const pw = await requirePwAi(res, "screenshot");
|
let buffer: Buffer;
|
||||||
if (!pw) {
|
const shouldUsePlaywright =
|
||||||
return;
|
profileCtx.profile.driver === "extension" ||
|
||||||
|
!tab.wsUrl ||
|
||||||
|
Boolean(ref) ||
|
||||||
|
Boolean(element);
|
||||||
|
if (shouldUsePlaywright) {
|
||||||
|
const pw = await requirePwAi(res, "screenshot");
|
||||||
|
if (!pw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const snap = await pw.takeScreenshotViaPlaywright({
|
||||||
|
cdpUrl,
|
||||||
|
targetId: tab.targetId,
|
||||||
|
ref,
|
||||||
|
element,
|
||||||
|
fullPage,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
buffer = snap.buffer;
|
||||||
|
} else {
|
||||||
|
buffer = await captureScreenshot({
|
||||||
|
wsUrl: tab.wsUrl ?? "",
|
||||||
|
fullPage,
|
||||||
|
format: type,
|
||||||
|
quality: type === "jpeg" ? 85 : undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const snap = await pw.takeScreenshotViaPlaywright({
|
|
||||||
cdpUrl: profileCtx.profile.cdpUrl,
|
|
||||||
targetId: tab.targetId,
|
|
||||||
ref,
|
|
||||||
element,
|
|
||||||
fullPage,
|
|
||||||
type,
|
|
||||||
});
|
|
||||||
buffer = snap.buffer;
|
|
||||||
} else {
|
|
||||||
buffer = await captureScreenshot({
|
|
||||||
wsUrl: tab.wsUrl ?? "",
|
|
||||||
fullPage,
|
|
||||||
format: type,
|
|
||||||
quality: type === "jpeg" ? 85 : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalized = await normalizeBrowserScreenshot(buffer, {
|
const normalized = await normalizeBrowserScreenshot(buffer, {
|
||||||
maxSide: DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE,
|
maxSide: DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE,
|
||||||
maxBytes: DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES,
|
maxBytes: DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES,
|
||||||
});
|
});
|
||||||
await ensureMediaDir();
|
await saveBrowserMediaResponse({
|
||||||
const saved = await saveMediaBuffer(
|
res,
|
||||||
normalized.buffer,
|
buffer: normalized.buffer,
|
||||||
normalized.contentType ?? `image/${type}`,
|
contentType: normalized.contentType ?? `image/${type}`,
|
||||||
"browser",
|
maxBytes: DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES,
|
||||||
DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES,
|
targetId: tab.targetId,
|
||||||
);
|
url: tab.url,
|
||||||
res.json({
|
});
|
||||||
ok: true,
|
},
|
||||||
path: path.resolve(saved.path),
|
});
|
||||||
targetId: tab.targetId,
|
|
||||||
url: tab.url,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
handleRouteError(ctx, res, err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/snapshot", async (req, res) => {
|
app.get("/snapshot", async (req, res) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user