mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-20 21:51:28 +00:00
refactor: dedupe browser trimmed readers
This commit is contained in:
@@ -2,7 +2,10 @@ import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { ResolvedBrowserConfig } from "./config.js";
|
||||
|
||||
export type BrowserExecutable = {
|
||||
@@ -115,7 +118,7 @@ function execText(
|
||||
encoding: "utf8",
|
||||
maxBuffer,
|
||||
});
|
||||
return String(output ?? "").trim() || null;
|
||||
return normalizeOptionalString(output) ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -192,7 +195,7 @@ function detectDefaultChromiumExecutableMac(): BrowserExecutable | null {
|
||||
if (!appPathRaw) {
|
||||
return null;
|
||||
}
|
||||
const appPath = appPathRaw.trim().replace(/\/$/, "");
|
||||
const appPath = appPathRaw.replace(/\/$/, "");
|
||||
const exeName = execText("/usr/bin/defaults", [
|
||||
"read",
|
||||
path.join(appPath, "Contents", "Info"),
|
||||
@@ -201,7 +204,7 @@ function detectDefaultChromiumExecutableMac(): BrowserExecutable | null {
|
||||
if (!exeName) {
|
||||
return null;
|
||||
}
|
||||
const exePath = path.join(appPath, "Contents", "MacOS", exeName.trim());
|
||||
const exePath = path.join(appPath, "Contents", "MacOS", exeName);
|
||||
if (!exists(exePath)) {
|
||||
return null;
|
||||
}
|
||||
@@ -430,12 +433,12 @@ function readWindowsCommandForProgId(progId: string): string | null {
|
||||
return null;
|
||||
}
|
||||
const match = output.match(/REG_\w+\s+(.+)$/im);
|
||||
return match?.[1]?.trim() || null;
|
||||
return normalizeOptionalString(match?.[1]) ?? null;
|
||||
}
|
||||
|
||||
function expandWindowsEnvVars(value: string): string {
|
||||
return value.replace(/%([^%]+)%/g, (_match, name) => {
|
||||
const key = String(name ?? "").trim();
|
||||
const key = normalizeOptionalString(name) ?? "";
|
||||
return key ? (process.env[key] ?? `%${key}%`) : _match;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type ChildProcess, type ChildProcessWithoutNullStreams, spawn } from "n
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { ensurePortAvailable } from "../infra/ports.js";
|
||||
import { rawDataToString } from "../infra/ws.js";
|
||||
@@ -196,7 +197,7 @@ export async function getChromeWebSocketUrl(
|
||||
return cdpUrl;
|
||||
}
|
||||
const version = await fetchChromeVersion(cdpUrl, timeoutMs, ssrfPolicy);
|
||||
const wsUrl = String(version?.webSocketDebuggerUrl ?? "").trim();
|
||||
const wsUrl = normalizeOptionalString(version?.webSocketDebuggerUrl) ?? "";
|
||||
if (!wsUrl) {
|
||||
return null;
|
||||
}
|
||||
@@ -409,7 +410,8 @@ export async function launchOpenClawChrome(
|
||||
}
|
||||
|
||||
if (!(await isChromeReachable(profile.cdpUrl))) {
|
||||
const stderrOutput = Buffer.concat(stderrChunks).toString("utf8").trim();
|
||||
const stderrOutput =
|
||||
normalizeOptionalString(Buffer.concat(stderrChunks).toString("utf8")) ?? "";
|
||||
const stderrHint = stderrOutput
|
||||
? `\nChrome stderr:\n${stderrOutput.slice(0, CHROME_STDERR_HINT_MAX_CHARS)}`
|
||||
: "";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { BrowserFormField } from "./client-actions-core.js";
|
||||
|
||||
export const DEFAULT_FILL_FIELD_TYPE = "text";
|
||||
@@ -5,11 +6,11 @@ export const DEFAULT_FILL_FIELD_TYPE = "text";
|
||||
type BrowserFormFieldValue = NonNullable<BrowserFormField["value"]>;
|
||||
|
||||
export function normalizeBrowserFormFieldRef(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
return normalizeOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
export function normalizeBrowserFormFieldType(value: unknown): string {
|
||||
const type = typeof value === "string" ? value.trim() : "";
|
||||
const type = normalizeOptionalString(value) ?? "";
|
||||
return type || DEFAULT_FILL_FIELD_TYPE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { hasProxyEnvConfigured } from "../infra/net/proxy-env.js";
|
||||
import {
|
||||
isPrivateNetworkAllowedByPolicy,
|
||||
@@ -46,7 +47,7 @@ export async function assertBrowserNavigationAllowed(
|
||||
lookupFn?: LookupFn;
|
||||
} & BrowserNavigationPolicyOptions,
|
||||
): Promise<void> {
|
||||
const rawUrl = String(opts.url ?? "").trim();
|
||||
const rawUrl = normalizeOptionalString(opts.url) ?? "";
|
||||
if (!rawUrl) {
|
||||
throw new InvalidBrowserNavigationUrlError("url is required");
|
||||
}
|
||||
@@ -94,7 +95,7 @@ export async function assertBrowserNavigationResultAllowed(
|
||||
lookupFn?: LookupFn;
|
||||
} & BrowserNavigationPolicyOptions,
|
||||
): Promise<void> {
|
||||
const rawUrl = String(opts.url ?? "").trim();
|
||||
const rawUrl = normalizeOptionalString(opts.url) ?? "";
|
||||
if (!rawUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type {
|
||||
Browser,
|
||||
BrowserContext,
|
||||
@@ -146,7 +147,7 @@ function roleRefsKey(cdpUrl: string, targetId: string) {
|
||||
}
|
||||
|
||||
function isBlockedTarget(cdpUrl: string, targetId?: string): boolean {
|
||||
const normalizedTargetId = targetId?.trim() || "";
|
||||
const normalizedTargetId = normalizeOptionalString(targetId) ?? "";
|
||||
if (!normalizedTargetId) {
|
||||
return false;
|
||||
}
|
||||
@@ -154,7 +155,7 @@ function isBlockedTarget(cdpUrl: string, targetId?: string): boolean {
|
||||
}
|
||||
|
||||
function markTargetBlocked(cdpUrl: string, targetId?: string): void {
|
||||
const normalizedTargetId = targetId?.trim() || "";
|
||||
const normalizedTargetId = normalizeOptionalString(targetId) ?? "";
|
||||
if (!normalizedTargetId) {
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +163,7 @@ function markTargetBlocked(cdpUrl: string, targetId?: string): void {
|
||||
}
|
||||
|
||||
function clearBlockedTarget(cdpUrl: string, targetId?: string): void {
|
||||
const normalizedTargetId = targetId?.trim() || "";
|
||||
const normalizedTargetId = normalizeOptionalString(targetId) ?? "";
|
||||
if (!normalizedTargetId) {
|
||||
return;
|
||||
}
|
||||
@@ -237,7 +238,7 @@ export function rememberRoleRefsForTarget(opts: {
|
||||
frameSelector?: string;
|
||||
mode?: NonNullable<PageState["roleRefsMode"]>;
|
||||
}): void {
|
||||
const targetId = opts.targetId.trim();
|
||||
const targetId = normalizeOptionalString(opts.targetId) ?? "";
|
||||
if (!targetId) {
|
||||
return;
|
||||
}
|
||||
@@ -267,12 +268,13 @@ export function storeRoleRefsForTarget(opts: {
|
||||
state.roleRefs = opts.refs;
|
||||
state.roleRefsFrameSelector = opts.frameSelector;
|
||||
state.roleRefsMode = opts.mode;
|
||||
if (!opts.targetId?.trim()) {
|
||||
const targetId = normalizeOptionalString(opts.targetId);
|
||||
if (!targetId) {
|
||||
return;
|
||||
}
|
||||
rememberRoleRefsForTarget({
|
||||
cdpUrl: opts.cdpUrl,
|
||||
targetId: opts.targetId,
|
||||
targetId,
|
||||
refs: opts.refs,
|
||||
frameSelector: opts.frameSelector,
|
||||
mode: opts.mode,
|
||||
@@ -284,7 +286,7 @@ export function restoreRoleRefsForTarget(opts: {
|
||||
targetId?: string;
|
||||
page: Page;
|
||||
}): void {
|
||||
const targetId = opts.targetId?.trim() || "";
|
||||
const targetId = normalizeOptionalString(opts.targetId) ?? "";
|
||||
if (!targetId) {
|
||||
return;
|
||||
}
|
||||
@@ -523,7 +525,7 @@ async function pageTargetId(page: Page): Promise<string | null> {
|
||||
const session = await page.context().newCDPSession(page);
|
||||
try {
|
||||
const info = (await session.send("Target.getTargetInfo")) as TargetInfoResponse;
|
||||
const targetId = String(info?.targetInfo?.targetId ?? "").trim();
|
||||
const targetId = normalizeOptionalString(info?.targetInfo?.targetId) ?? "";
|
||||
return targetId || null;
|
||||
} finally {
|
||||
await session.detach().catch(() => {});
|
||||
@@ -703,7 +705,7 @@ async function closeBlockedNavigationTarget(opts: {
|
||||
// Quarantine the concrete page first; then persist by target id when available.
|
||||
markPageRefBlocked(opts.cdpUrl, opts.page);
|
||||
const resolvedTargetId = await pageTargetId(opts.page).catch(() => null);
|
||||
const fallbackTargetId = opts.targetId?.trim() || "";
|
||||
const fallbackTargetId = normalizeOptionalString(opts.targetId) ?? "";
|
||||
const targetIdToBlock = resolvedTargetId || fallbackTargetId;
|
||||
if (targetIdToBlock) {
|
||||
markTargetBlocked(opts.cdpUrl, targetIdToBlock);
|
||||
@@ -899,8 +901,9 @@ async function tryTerminateExecutionViaCdp(opts: {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = pages.find((p) => String(p.id ?? "").trim() === opts.targetId);
|
||||
const wsUrlRaw = String(target?.webSocketDebuggerUrl ?? "").trim();
|
||||
const targetId = normalizeOptionalString(opts.targetId) ?? "";
|
||||
const target = pages.find((p) => normalizeOptionalString(p.id) === targetId);
|
||||
const wsUrlRaw = normalizeOptionalString(target?.webSocketDebuggerUrl) ?? "";
|
||||
if (!wsUrlRaw) {
|
||||
return;
|
||||
}
|
||||
@@ -931,8 +934,9 @@ async function tryTerminateExecutionViaCdp(opts: {
|
||||
send("Target.attachToTarget", { targetId: opts.targetId, flatten: true }),
|
||||
1500,
|
||||
)) as { sessionId?: unknown };
|
||||
if (typeof attached?.sessionId === "string" && attached.sessionId.trim()) {
|
||||
sessionId = attached.sessionId;
|
||||
const attachedSessionId = normalizeOptionalString(attached?.sessionId);
|
||||
if (attachedSessionId) {
|
||||
sessionId = attachedSessionId;
|
||||
}
|
||||
}
|
||||
await runWithTimeout(send("Runtime.terminateExecution", undefined, sessionId), 1500);
|
||||
@@ -990,7 +994,7 @@ export async function forceDisconnectPlaywrightForTarget(opts: {
|
||||
|
||||
// Best-effort: kill any stuck JS to unblock the target's execution context before we
|
||||
// disconnect Playwright's CDP connection.
|
||||
const targetId = opts.targetId?.trim() || "";
|
||||
const targetId = normalizeOptionalString(opts.targetId) ?? "";
|
||||
if (targetId) {
|
||||
await tryTerminateExecutionViaCdp({ cdpUrl: normalized, targetId }).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import type { BrowserActRequest, BrowserFormField } from "./client-actions-core.js";
|
||||
@@ -231,7 +232,7 @@ export async function pressKeyViaPlaywright(opts: {
|
||||
delayMs?: number;
|
||||
ssrfPolicy?: SsrFPolicy;
|
||||
}): Promise<void> {
|
||||
const key = String(opts.key ?? "").trim();
|
||||
const key = normalizeOptionalString(opts.key) ?? "";
|
||||
if (!key) {
|
||||
throw new Error("key is required");
|
||||
}
|
||||
@@ -336,7 +337,7 @@ export async function evaluateViaPlaywright(opts: {
|
||||
timeoutMs?: number;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<unknown> {
|
||||
const fnText = String(opts.fn ?? "").trim();
|
||||
const fnText = normalizeOptionalString(opts.fn) ?? "";
|
||||
if (!fnText) {
|
||||
throw new Error("function is required");
|
||||
}
|
||||
@@ -512,13 +513,13 @@ export async function waitForViaPlaywright(opts: {
|
||||
});
|
||||
}
|
||||
if (opts.selector) {
|
||||
const selector = String(opts.selector).trim();
|
||||
const selector = normalizeOptionalString(opts.selector) ?? "";
|
||||
if (selector) {
|
||||
await page.locator(selector).first().waitFor({ state: "visible", timeout });
|
||||
}
|
||||
}
|
||||
if (opts.url) {
|
||||
const url = String(opts.url).trim();
|
||||
const url = normalizeOptionalString(opts.url) ?? "";
|
||||
if (url) {
|
||||
await page.waitForURL(url, { timeout });
|
||||
}
|
||||
@@ -527,7 +528,7 @@ export async function waitForViaPlaywright(opts: {
|
||||
await page.waitForLoadState(opts.loadState, { timeout });
|
||||
}
|
||||
if (opts.fn) {
|
||||
const fn = String(opts.fn).trim();
|
||||
const fn = normalizeOptionalString(opts.fn) ?? "";
|
||||
if (fn) {
|
||||
await page.waitForFunction(fn, { timeout });
|
||||
}
|
||||
@@ -709,8 +710,8 @@ export async function setInputFilesViaPlaywright(opts: {
|
||||
if (!opts.paths.length) {
|
||||
throw new Error("paths are required");
|
||||
}
|
||||
const inputRef = typeof opts.inputRef === "string" ? opts.inputRef.trim() : "";
|
||||
const element = typeof opts.element === "string" ? opts.element.trim() : "";
|
||||
const inputRef = normalizeOptionalString(opts.inputRef) ?? "";
|
||||
const element = normalizeOptionalString(opts.element) ?? "";
|
||||
if (inputRef && element) {
|
||||
throw new Error("inputRef and element are mutually exclusive");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { ensurePageState, getPageForTargetId } from "./pw-session.js";
|
||||
import { normalizeTimeoutMs } from "./pw-tools-core.shared.js";
|
||||
@@ -16,7 +17,7 @@ export async function responseBodyViaPlaywright(opts: {
|
||||
body: string;
|
||||
truncated?: boolean;
|
||||
}> {
|
||||
const pattern = String(opts.url ?? "").trim();
|
||||
const pattern = normalizeOptionalString(opts.url) ?? "";
|
||||
if (!pattern) {
|
||||
throw new Error("url is required");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { parseRoleRef } from "./pw-role-snapshot.js";
|
||||
|
||||
@@ -21,7 +22,7 @@ export function bumpDownloadArmId(): number {
|
||||
}
|
||||
|
||||
export function requireRef(value: unknown): string {
|
||||
const raw = typeof value === "string" ? value.trim() : "";
|
||||
const raw = normalizeOptionalString(value) ?? "";
|
||||
const roleRef = raw ? parseRoleRef(raw) : null;
|
||||
const ref = roleRef ?? (raw.startsWith("@") ? raw.slice(1) : raw);
|
||||
if (!ref) {
|
||||
@@ -34,8 +35,8 @@ export function requireRefOrSelector(
|
||||
ref: string | undefined,
|
||||
selector: string | undefined,
|
||||
): { ref?: string; selector?: string } {
|
||||
const trimmedRef = typeof ref === "string" ? ref.trim() : "";
|
||||
const trimmedSelector = typeof selector === "string" ? selector.trim() : "";
|
||||
const trimmedRef = normalizeOptionalString(ref) ?? "";
|
||||
const trimmedSelector = normalizeOptionalString(selector) ?? "";
|
||||
if (!trimmedRef && !trimmedSelector) {
|
||||
throw new Error("ref or selector is required");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { type AriaSnapshotNode, formatAriaSnapshot, type RawAXNode } from "./cdp.js";
|
||||
import { assertBrowserNavigationAllowed, withBrowserNavigationPolicy } from "./navigation-guard.js";
|
||||
@@ -140,7 +141,7 @@ export async function snapshotRoleViaPlaywright(opts: {
|
||||
}
|
||||
|
||||
if (opts.refsMode === "aria") {
|
||||
if (opts.selector?.trim() || opts.frameSelector?.trim()) {
|
||||
if (normalizeOptionalString(opts.selector) || normalizeOptionalString(opts.frameSelector)) {
|
||||
throw new Error("refs=aria does not support selector/frame snapshots yet.");
|
||||
}
|
||||
const maybe = page as unknown as WithSnapshotForAI;
|
||||
@@ -166,8 +167,8 @@ export async function snapshotRoleViaPlaywright(opts: {
|
||||
};
|
||||
}
|
||||
|
||||
const frameSelector = opts.frameSelector?.trim() || "";
|
||||
const selector = opts.selector?.trim() || "";
|
||||
const frameSelector = normalizeOptionalString(opts.frameSelector) ?? "";
|
||||
const selector = normalizeOptionalString(opts.selector) ?? "";
|
||||
const locator = frameSelector
|
||||
? selector
|
||||
? page.frameLocator(frameSelector).locator(selector)
|
||||
@@ -213,7 +214,7 @@ export async function navigateViaPlaywright(opts: {
|
||||
);
|
||||
};
|
||||
|
||||
const url = String(opts.url ?? "").trim();
|
||||
const url = normalizeOptionalString(opts.url) ?? "";
|
||||
if (!url) {
|
||||
throw new Error("url is required");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { devices as playwrightDevices } from "playwright-core";
|
||||
import { ensurePageState, getPageForTargetId } from "./pw-session.js";
|
||||
import { withPageScopedCdpClient } from "./pw-session.page-cdp.js";
|
||||
@@ -69,7 +70,7 @@ export async function setGeolocationViaPlaywright(opts: {
|
||||
accuracy: typeof opts.accuracy === "number" ? opts.accuracy : undefined,
|
||||
});
|
||||
const origin =
|
||||
opts.origin?.trim() ||
|
||||
normalizeOptionalString(opts.origin) ||
|
||||
(() => {
|
||||
try {
|
||||
return new URL(page.url()).origin;
|
||||
@@ -99,7 +100,7 @@ export async function setLocaleViaPlaywright(opts: {
|
||||
}): Promise<void> {
|
||||
const page = await getPageForTargetId(opts);
|
||||
ensurePageState(page);
|
||||
const locale = String(opts.locale ?? "").trim();
|
||||
const locale = normalizeOptionalString(opts.locale) ?? "";
|
||||
if (!locale) {
|
||||
throw new Error("locale is required");
|
||||
}
|
||||
@@ -127,7 +128,7 @@ export async function setTimezoneViaPlaywright(opts: {
|
||||
}): Promise<void> {
|
||||
const page = await getPageForTargetId(opts);
|
||||
ensurePageState(page);
|
||||
const timezoneId = String(opts.timezoneId ?? "").trim();
|
||||
const timezoneId = normalizeOptionalString(opts.timezoneId) ?? "";
|
||||
if (!timezoneId) {
|
||||
throw new Error("timezoneId is required");
|
||||
}
|
||||
@@ -159,7 +160,7 @@ export async function setDeviceViaPlaywright(opts: {
|
||||
}): Promise<void> {
|
||||
const page = await getPageForTargetId(opts);
|
||||
ensurePageState(page);
|
||||
const name = String(opts.name ?? "").trim();
|
||||
const name = normalizeOptionalString(opts.name) ?? "";
|
||||
if (!name) {
|
||||
throw new Error("device name is required");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export function sanitizeUntrustedFileName(fileName: string, fallbackName: string): string {
|
||||
const trimmed = String(fileName ?? "").trim();
|
||||
const trimmed = normalizeOptionalString(fileName) ?? "";
|
||||
if (!trimmed) {
|
||||
return fallbackName;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { fetchOk, normalizeCdpHttpBaseForJsonEndpoints } from "./cdp.helpers.js";
|
||||
import { appendCdpPath } from "./cdp.js";
|
||||
import { closeChromeMcpTab, focusChromeMcpTab } from "./chrome-mcp.js";
|
||||
@@ -56,7 +57,7 @@ export function createProfileSelectionOps({
|
||||
};
|
||||
|
||||
const pickDefault = () => {
|
||||
const last = profileState.lastTargetId?.trim() || "";
|
||||
const last = normalizeOptionalString(profileState.lastTargetId) ?? "";
|
||||
const lastResolved = last ? resolveById(last) : null;
|
||||
if (lastResolved && lastResolved !== "AMBIGUOUS") {
|
||||
return lastResolved;
|
||||
|
||||
Reference in New Issue
Block a user