mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-12 09:11:12 +00:00
144 lines
4.1 KiB
TypeScript
144 lines
4.1 KiB
TypeScript
import type { PwAiModule } from "../pw-ai-module.js";
|
|
import { getPwAiModule as getPwAiModuleBase } from "../pw-ai-module.js";
|
|
import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
|
|
import type { BrowserRequest, BrowserResponse } from "./types.js";
|
|
import { getProfileContext, jsonError } from "./utils.js";
|
|
|
|
export const SELECTOR_UNSUPPORTED_MESSAGE = [
|
|
"Error: 'selector' is not supported. Use 'ref' from snapshot instead.",
|
|
"",
|
|
"Example workflow:",
|
|
"1. snapshot action to get page state with refs",
|
|
'2. act with ref: "e123" to interact with element',
|
|
"",
|
|
"This is more reliable for modern SPAs.",
|
|
].join("\n");
|
|
|
|
export function readBody(req: BrowserRequest): Record<string, unknown> {
|
|
const body = req.body as Record<string, unknown> | undefined;
|
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
return {};
|
|
}
|
|
return body;
|
|
}
|
|
|
|
export function resolveTargetIdFromBody(body: Record<string, unknown>): string | undefined {
|
|
const targetId = typeof body.targetId === "string" ? body.targetId.trim() : "";
|
|
return targetId || undefined;
|
|
}
|
|
|
|
export function resolveTargetIdFromQuery(query: Record<string, unknown>): string | undefined {
|
|
const targetId = typeof query.targetId === "string" ? query.targetId.trim() : "";
|
|
return targetId || undefined;
|
|
}
|
|
|
|
export function handleRouteError(ctx: BrowserRouteContext, res: BrowserResponse, err: unknown) {
|
|
const mapped = ctx.mapTabError(err);
|
|
if (mapped) {
|
|
return jsonError(res, mapped.status, mapped.message);
|
|
}
|
|
jsonError(res, 500, String(err));
|
|
}
|
|
|
|
export function resolveProfileContext(
|
|
req: BrowserRequest,
|
|
res: BrowserResponse,
|
|
ctx: BrowserRouteContext,
|
|
): ProfileContext | null {
|
|
const profileCtx = getProfileContext(req, ctx);
|
|
if ("error" in profileCtx) {
|
|
jsonError(res, profileCtx.status, profileCtx.error);
|
|
return null;
|
|
}
|
|
return profileCtx;
|
|
}
|
|
|
|
export async function getPwAiModule(): Promise<PwAiModule | null> {
|
|
return await getPwAiModuleBase({ mode: "soft" });
|
|
}
|
|
|
|
export async function requirePwAi(
|
|
res: BrowserResponse,
|
|
feature: string,
|
|
): Promise<PwAiModule | null> {
|
|
const mod = await getPwAiModule();
|
|
if (mod) {
|
|
return mod;
|
|
}
|
|
jsonError(
|
|
res,
|
|
501,
|
|
[
|
|
`Playwright is not available in this gateway build; '${feature}' is unsupported.`,
|
|
"Install the full Playwright package (not playwright-core) and restart the gateway, or reinstall with browser support.",
|
|
"Docs: /tools/browser#playwright-requirement",
|
|
].join("\n"),
|
|
);
|
|
return null;
|
|
}
|
|
|
|
type RouteTabContext = {
|
|
profileCtx: ProfileContext;
|
|
tab: Awaited<ReturnType<ProfileContext["ensureTabAvailable"]>>;
|
|
cdpUrl: string;
|
|
};
|
|
|
|
type RouteTabPwContext = RouteTabContext & {
|
|
pw: PwAiModule;
|
|
};
|
|
|
|
type RouteWithTabParams<T> = {
|
|
req: BrowserRequest;
|
|
res: BrowserResponse;
|
|
ctx: BrowserRouteContext;
|
|
targetId?: string;
|
|
run: (ctx: RouteTabContext) => Promise<T>;
|
|
};
|
|
|
|
export async function withRouteTabContext<T>(
|
|
params: RouteWithTabParams<T>,
|
|
): Promise<T | undefined> {
|
|
const profileCtx = resolveProfileContext(params.req, params.res, params.ctx);
|
|
if (!profileCtx) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
const tab = await profileCtx.ensureTabAvailable(params.targetId);
|
|
return await params.run({
|
|
profileCtx,
|
|
tab,
|
|
cdpUrl: profileCtx.profile.cdpUrl,
|
|
});
|
|
} catch (err) {
|
|
handleRouteError(params.ctx, params.res, err);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
type RouteWithPwParams<T> = {
|
|
req: BrowserRequest;
|
|
res: BrowserResponse;
|
|
ctx: BrowserRouteContext;
|
|
targetId?: string;
|
|
feature: string;
|
|
run: (ctx: RouteTabPwContext) => Promise<T>;
|
|
};
|
|
|
|
export async function withPlaywrightRouteContext<T>(
|
|
params: RouteWithPwParams<T>,
|
|
): Promise<T | undefined> {
|
|
return await withRouteTabContext({
|
|
req: params.req,
|
|
res: params.res,
|
|
ctx: params.ctx,
|
|
targetId: params.targetId,
|
|
run: async ({ profileCtx, tab, cdpUrl }) => {
|
|
const pw = await requirePwAi(params.res, params.feature);
|
|
if (!pw) {
|
|
return undefined as T | undefined;
|
|
}
|
|
return await params.run({ profileCtx, tab, cdpUrl, pw });
|
|
},
|
|
});
|
|
}
|