chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -16,7 +16,9 @@ export const ACT_KINDS = [
export type ActKind = (typeof ACT_KINDS)[number];
export function isActKind(value: unknown): value is ActKind {
if (typeof value !== "string") return false;
if (typeof value !== "string") {
return false;
}
return (ACT_KINDS as readonly string[]).includes(value);
}
@@ -32,7 +34,9 @@ const ALLOWED_CLICK_MODIFIERS = new Set<ClickModifier>([
]);
export function parseClickButton(raw: string): ClickButton | undefined {
if (raw === "left" || raw === "right" || raw === "middle") return raw;
if (raw === "left" || raw === "right" || raw === "middle") {
return raw;
}
return undefined;
}

View File

@@ -22,7 +22,9 @@ export function registerBrowserAgentActRoutes(
) {
app.post("/act", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const kindRaw = toStringOrEmpty(body.kind);
if (!isActKind(kindRaw)) {
@@ -38,18 +40,24 @@ export function registerBrowserAgentActRoutes(
const tab = await profileCtx.ensureTabAvailable(targetId);
const cdpUrl = profileCtx.profile.cdpUrl;
const pw = await requirePwAi(res, `act:${kind}`);
if (!pw) return;
if (!pw) {
return;
}
const evaluateEnabled = ctx.state().resolved.evaluateEnabled;
switch (kind) {
case "click": {
const ref = toStringOrEmpty(body.ref);
if (!ref) return jsonError(res, 400, "ref is required");
if (!ref) {
return jsonError(res, 400, "ref is required");
}
const doubleClick = toBoolean(body.doubleClick) ?? false;
const timeoutMs = toNumber(body.timeoutMs);
const buttonRaw = toStringOrEmpty(body.button) || "";
const button = buttonRaw ? parseClickButton(buttonRaw) : undefined;
if (buttonRaw && !button) return jsonError(res, 400, "button must be left|right|middle");
if (buttonRaw && !button) {
return jsonError(res, 400, "button must be left|right|middle");
}
const modifiersRaw = toStringArray(body.modifiers) ?? [];
const parsedModifiers = parseClickModifiers(modifiersRaw);
@@ -63,16 +71,26 @@ export function registerBrowserAgentActRoutes(
ref,
doubleClick,
};
if (button) clickRequest.button = button;
if (modifiers) clickRequest.modifiers = modifiers;
if (timeoutMs) clickRequest.timeoutMs = timeoutMs;
if (button) {
clickRequest.button = button;
}
if (modifiers) {
clickRequest.modifiers = modifiers;
}
if (timeoutMs) {
clickRequest.timeoutMs = timeoutMs;
}
await pw.clickViaPlaywright(clickRequest);
return res.json({ ok: true, targetId: tab.targetId, url: tab.url });
}
case "type": {
const ref = toStringOrEmpty(body.ref);
if (!ref) return jsonError(res, 400, "ref is required");
if (typeof body.text !== "string") return jsonError(res, 400, "text is required");
if (!ref) {
return jsonError(res, 400, "ref is required");
}
if (typeof body.text !== "string") {
return jsonError(res, 400, "text is required");
}
const text = body.text;
const submit = toBoolean(body.submit) ?? false;
const slowly = toBoolean(body.slowly) ?? false;
@@ -85,13 +103,17 @@ export function registerBrowserAgentActRoutes(
submit,
slowly,
};
if (timeoutMs) typeRequest.timeoutMs = timeoutMs;
if (timeoutMs) {
typeRequest.timeoutMs = timeoutMs;
}
await pw.typeViaPlaywright(typeRequest);
return res.json({ ok: true, targetId: tab.targetId });
}
case "press": {
const key = toStringOrEmpty(body.key);
if (!key) return jsonError(res, 400, "key is required");
if (!key) {
return jsonError(res, 400, "key is required");
}
const delayMs = toNumber(body.delayMs);
await pw.pressKeyViaPlaywright({
cdpUrl,
@@ -103,7 +125,9 @@ export function registerBrowserAgentActRoutes(
}
case "hover": {
const ref = toStringOrEmpty(body.ref);
if (!ref) return jsonError(res, 400, "ref is required");
if (!ref) {
return jsonError(res, 400, "ref is required");
}
const timeoutMs = toNumber(body.timeoutMs);
await pw.hoverViaPlaywright({
cdpUrl,
@@ -115,21 +139,27 @@ export function registerBrowserAgentActRoutes(
}
case "scrollIntoView": {
const ref = toStringOrEmpty(body.ref);
if (!ref) return jsonError(res, 400, "ref is required");
if (!ref) {
return jsonError(res, 400, "ref is required");
}
const timeoutMs = toNumber(body.timeoutMs);
const scrollRequest: Parameters<typeof pw.scrollIntoViewViaPlaywright>[0] = {
cdpUrl,
targetId: tab.targetId,
ref,
};
if (timeoutMs) scrollRequest.timeoutMs = timeoutMs;
if (timeoutMs) {
scrollRequest.timeoutMs = timeoutMs;
}
await pw.scrollIntoViewViaPlaywright(scrollRequest);
return res.json({ ok: true, targetId: tab.targetId });
}
case "drag": {
const startRef = toStringOrEmpty(body.startRef);
const endRef = toStringOrEmpty(body.endRef);
if (!startRef || !endRef) return jsonError(res, 400, "startRef and endRef are required");
if (!startRef || !endRef) {
return jsonError(res, 400, "startRef and endRef are required");
}
const timeoutMs = toNumber(body.timeoutMs);
await pw.dragViaPlaywright({
cdpUrl,
@@ -143,7 +173,9 @@ export function registerBrowserAgentActRoutes(
case "select": {
const ref = toStringOrEmpty(body.ref);
const values = toStringArray(body.values);
if (!ref || !values?.length) return jsonError(res, 400, "ref and values are required");
if (!ref || !values?.length) {
return jsonError(res, 400, "ref and values are required");
}
const timeoutMs = toNumber(body.timeoutMs);
await pw.selectOptionViaPlaywright({
cdpUrl,
@@ -158,11 +190,15 @@ export function registerBrowserAgentActRoutes(
const rawFields = Array.isArray(body.fields) ? body.fields : [];
const fields = rawFields
.map((field) => {
if (!field || typeof field !== "object") return null;
if (!field || typeof field !== "object") {
return null;
}
const rec = field as Record<string, unknown>;
const ref = toStringOrEmpty(rec.ref);
const type = toStringOrEmpty(rec.type);
if (!ref || !type) return null;
if (!ref || !type) {
return null;
}
const value =
typeof rec.value === "string" ||
typeof rec.value === "number" ||
@@ -174,7 +210,9 @@ export function registerBrowserAgentActRoutes(
return parsed;
})
.filter((field): field is BrowserFormField => field !== null);
if (!fields.length) return jsonError(res, 400, "fields are required");
if (!fields.length) {
return jsonError(res, 400, "fields are required");
}
const timeoutMs = toNumber(body.timeoutMs);
await pw.fillFormViaPlaywright({
cdpUrl,
@@ -187,7 +225,9 @@ export function registerBrowserAgentActRoutes(
case "resize": {
const width = toNumber(body.width);
const height = toNumber(body.height);
if (!width || !height) return jsonError(res, 400, "width and height are required");
if (!width || !height) {
return jsonError(res, 400, "width and height are required");
}
await pw.resizeViewportViaPlaywright({
cdpUrl,
targetId: tab.targetId,
@@ -262,7 +302,9 @@ export function registerBrowserAgentActRoutes(
);
}
const fn = toStringOrEmpty(body.fn);
if (!fn) return jsonError(res, 400, "fn is required");
if (!fn) {
return jsonError(res, 400, "fn is required");
}
const ref = toStringOrEmpty(body.ref) || undefined;
const result = await pw.evaluateViaPlaywright({
cdpUrl,
@@ -292,7 +334,9 @@ export function registerBrowserAgentActRoutes(
app.post("/hooks/file-chooser", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const ref = toStringOrEmpty(body.ref) || undefined;
@@ -300,11 +344,15 @@ export function registerBrowserAgentActRoutes(
const element = toStringOrEmpty(body.element) || undefined;
const paths = toStringArray(body.paths) ?? [];
const timeoutMs = toNumber(body.timeoutMs);
if (!paths.length) return jsonError(res, 400, "paths are required");
if (!paths.length) {
return jsonError(res, 400, "paths are required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "file chooser hook");
if (!pw) return;
if (!pw) {
return;
}
if (inputRef || element) {
if (ref) {
return jsonError(res, 400, "ref cannot be combined with inputRef/element");
@@ -339,17 +387,23 @@ export function registerBrowserAgentActRoutes(
app.post("/hooks/dialog", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const accept = toBoolean(body.accept);
const promptText = toStringOrEmpty(body.promptText) || undefined;
const timeoutMs = toNumber(body.timeoutMs);
if (accept === undefined) return jsonError(res, 400, "accept is required");
if (accept === undefined) {
return jsonError(res, 400, "accept is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "dialog hook");
if (!pw) return;
if (!pw) {
return;
}
await pw.armDialogViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -365,7 +419,9 @@ export function registerBrowserAgentActRoutes(
app.post("/wait/download", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const out = toStringOrEmpty(body.path) || undefined;
@@ -373,7 +429,9 @@ export function registerBrowserAgentActRoutes(
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "wait for download");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.waitForDownloadViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -388,18 +446,26 @@ export function registerBrowserAgentActRoutes(
app.post("/download", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const ref = toStringOrEmpty(body.ref);
const out = toStringOrEmpty(body.path);
const timeoutMs = toNumber(body.timeoutMs);
if (!ref) return jsonError(res, 400, "ref is required");
if (!out) return jsonError(res, 400, "path is required");
if (!ref) {
return jsonError(res, 400, "ref is required");
}
if (!out) {
return jsonError(res, 400, "path is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "download");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.downloadViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -415,17 +481,23 @@ export function registerBrowserAgentActRoutes(
app.post("/response/body", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const url = toStringOrEmpty(body.url);
const timeoutMs = toNumber(body.timeoutMs);
const maxChars = toNumber(body.maxChars);
if (!url) return jsonError(res, 400, "url is required");
if (!url) {
return jsonError(res, 400, "url is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "response body");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.responseBodyViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -441,15 +513,21 @@ export function registerBrowserAgentActRoutes(
app.post("/highlight", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const ref = toStringOrEmpty(body.ref);
if (!ref) return jsonError(res, 400, "ref is required");
if (!ref) {
return jsonError(res, 400, "ref is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "highlight");
if (!pw) return;
if (!pw) {
return;
}
await pw.highlightViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,

View File

@@ -13,14 +13,18 @@ export function registerBrowserAgentDebugRoutes(
) {
app.get("/console", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
const level = typeof req.query.level === "string" ? req.query.level : "";
try {
const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
const pw = await requirePwAi(res, "console messages");
if (!pw) return;
if (!pw) {
return;
}
const messages = await pw.getConsoleMessagesViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -34,14 +38,18 @@ export function registerBrowserAgentDebugRoutes(
app.get("/errors", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
const clear = toBoolean(req.query.clear) ?? false;
try {
const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
const pw = await requirePwAi(res, "page errors");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.getPageErrorsViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -55,7 +63,9 @@ export function registerBrowserAgentDebugRoutes(
app.get("/requests", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
const filter = typeof req.query.filter === "string" ? req.query.filter : "";
const clear = toBoolean(req.query.clear) ?? false;
@@ -63,7 +73,9 @@ export function registerBrowserAgentDebugRoutes(
try {
const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
const pw = await requirePwAi(res, "network requests");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.getNetworkRequestsViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -78,7 +90,9 @@ export function registerBrowserAgentDebugRoutes(
app.post("/trace/start", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const screenshots = toBoolean(body.screenshots) ?? undefined;
@@ -87,7 +101,9 @@ export function registerBrowserAgentDebugRoutes(
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "trace start");
if (!pw) return;
if (!pw) {
return;
}
await pw.traceStartViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -103,14 +119,18 @@ export function registerBrowserAgentDebugRoutes(
app.post("/trace/stop", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const out = toStringOrEmpty(body.path) || "";
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "trace stop");
if (!pw) return;
if (!pw) {
return;
}
const id = crypto.randomUUID();
const dir = "/tmp/openclaw";
await fs.mkdir(dir, { recursive: true });

View File

@@ -16,13 +16,17 @@ export const SELECTOR_UNSUPPORTED_MESSAGE = [
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 {};
if (!body || typeof body !== "object" || Array.isArray(body)) {
return {};
}
return body;
}
export function handleRouteError(ctx: BrowserRouteContext, res: BrowserResponse, err: unknown) {
const mapped = ctx.mapTabError(err);
if (mapped) return jsonError(res, mapped.status, mapped.message);
if (mapped) {
return jsonError(res, mapped.status, mapped.message);
}
jsonError(res, 500, String(err));
}
@@ -48,7 +52,9 @@ export async function requirePwAi(
feature: string,
): Promise<PwAiModule | null> {
const mod = await getPwAiModule();
if (mod) return mod;
if (mod) {
return mod;
}
jsonError(
res,
501,

View File

@@ -29,15 +29,21 @@ export function registerBrowserAgentSnapshotRoutes(
) {
app.post("/navigate", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const url = toStringOrEmpty(body.url);
const targetId = toStringOrEmpty(body.targetId) || undefined;
if (!url) return jsonError(res, 400, "url is required");
if (!url) {
return jsonError(res, 400, "url is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "navigate");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.navigateViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -51,13 +57,17 @@ export function registerBrowserAgentSnapshotRoutes(
app.post("/pdf", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "pdf");
if (!pw) return;
if (!pw) {
return;
}
const pdf = await pw.pdfViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -82,7 +92,9 @@ export function registerBrowserAgentSnapshotRoutes(
app.post("/screenshot", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const fullPage = toBoolean(body.fullPage) ?? false;
@@ -101,7 +113,9 @@ export function registerBrowserAgentSnapshotRoutes(
profileCtx.profile.driver === "extension" || !tab.wsUrl || Boolean(ref) || Boolean(element);
if (shouldUsePlaywright) {
const pw = await requirePwAi(res, "screenshot");
if (!pw) return;
if (!pw) {
return;
}
const snap = await pw.takeScreenshotViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -144,7 +158,9 @@ export function registerBrowserAgentSnapshotRoutes(
app.get("/snapshot", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
const mode = req.query.mode === "efficient" ? "efficient" : undefined;
const labels = toBoolean(req.query.labels) ?? undefined;
@@ -187,7 +203,9 @@ export function registerBrowserAgentSnapshotRoutes(
}
if (format === "ai") {
const pw = await requirePwAi(res, "ai snapshot");
if (!pw) return;
if (!pw) {
return;
}
const wantsRoleSnapshot =
labels === true ||
mode === "efficient" ||
@@ -282,7 +300,9 @@ export function registerBrowserAgentSnapshotRoutes(
// Extension relay doesn't expose per-page WS URLs; run AX snapshot via Playwright CDP session.
// Also covers cases where wsUrl is missing/unusable.
return requirePwAi(res, "aria snapshot").then(async (pw) => {
if (!pw) return null;
if (!pw) {
return null;
}
return await pw.snapshotAriaViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -293,7 +313,9 @@ export function registerBrowserAgentSnapshotRoutes(
: snapshotAria({ wsUrl: tab.wsUrl ?? "", limit });
const resolved = await Promise.resolve(snap);
if (!resolved) return;
if (!resolved) {
return;
}
return res.json({
ok: true,
format,

View File

@@ -9,12 +9,16 @@ export function registerBrowserAgentStorageRoutes(
) {
app.get("/cookies", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
try {
const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
const pw = await requirePwAi(res, "cookies");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.cookiesGetViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -27,18 +31,24 @@ export function registerBrowserAgentStorageRoutes(
app.post("/cookies/set", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const cookie =
body.cookie && typeof body.cookie === "object" && !Array.isArray(body.cookie)
? (body.cookie as Record<string, unknown>)
: null;
if (!cookie) return jsonError(res, 400, "cookie is required");
if (!cookie) {
return jsonError(res, 400, "cookie is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "cookies set");
if (!pw) return;
if (!pw) {
return;
}
await pw.cookiesSetViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -65,13 +75,17 @@ export function registerBrowserAgentStorageRoutes(
app.post("/cookies/clear", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "cookies clear");
if (!pw) return;
if (!pw) {
return;
}
await pw.cookiesClearViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -84,16 +98,21 @@ export function registerBrowserAgentStorageRoutes(
app.get("/storage/:kind", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const kind = toStringOrEmpty(req.params.kind);
if (kind !== "local" && kind !== "session")
if (kind !== "local" && kind !== "session") {
return jsonError(res, 400, "kind must be local|session");
}
const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
const key = typeof req.query.key === "string" ? req.query.key : "";
try {
const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
const pw = await requirePwAi(res, "storage get");
if (!pw) return;
if (!pw) {
return;
}
const result = await pw.storageGetViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -108,19 +127,26 @@ export function registerBrowserAgentStorageRoutes(
app.post("/storage/:kind/set", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const kind = toStringOrEmpty(req.params.kind);
if (kind !== "local" && kind !== "session")
if (kind !== "local" && kind !== "session") {
return jsonError(res, 400, "kind must be local|session");
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const key = toStringOrEmpty(body.key);
if (!key) return jsonError(res, 400, "key is required");
if (!key) {
return jsonError(res, 400, "key is required");
}
const value = typeof body.value === "string" ? body.value : "";
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "storage set");
if (!pw) return;
if (!pw) {
return;
}
await pw.storageSetViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -136,16 +162,21 @@ export function registerBrowserAgentStorageRoutes(
app.post("/storage/:kind/clear", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const kind = toStringOrEmpty(req.params.kind);
if (kind !== "local" && kind !== "session")
if (kind !== "local" && kind !== "session") {
return jsonError(res, 400, "kind must be local|session");
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "storage clear");
if (!pw) return;
if (!pw) {
return;
}
await pw.storageClearViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -159,15 +190,21 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/offline", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const offline = toBoolean(body.offline);
if (offline === undefined) return jsonError(res, 400, "offline is required");
if (offline === undefined) {
return jsonError(res, 400, "offline is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "offline");
if (!pw) return;
if (!pw) {
return;
}
await pw.setOfflineViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -181,22 +218,30 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/headers", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const headers =
body.headers && typeof body.headers === "object" && !Array.isArray(body.headers)
? (body.headers as Record<string, unknown>)
: null;
if (!headers) return jsonError(res, 400, "headers is required");
if (!headers) {
return jsonError(res, 400, "headers is required");
}
const parsed: Record<string, string> = {};
for (const [k, v] of Object.entries(headers)) {
if (typeof v === "string") parsed[k] = v;
if (typeof v === "string") {
parsed[k] = v;
}
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "headers");
if (!pw) return;
if (!pw) {
return;
}
await pw.setExtraHTTPHeadersViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -210,7 +255,9 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/credentials", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const clear = toBoolean(body.clear) ?? false;
@@ -219,7 +266,9 @@ export function registerBrowserAgentStorageRoutes(
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "http credentials");
if (!pw) return;
if (!pw) {
return;
}
await pw.setHttpCredentialsViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -235,7 +284,9 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/geolocation", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const clear = toBoolean(body.clear) ?? false;
@@ -246,7 +297,9 @@ export function registerBrowserAgentStorageRoutes(
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "geolocation");
if (!pw) return;
if (!pw) {
return;
}
await pw.setGeolocationViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -264,7 +317,9 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/media", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const schemeRaw = toStringOrEmpty(body.colorScheme);
@@ -274,12 +329,15 @@ export function registerBrowserAgentStorageRoutes(
: schemeRaw === "none"
? null
: undefined;
if (colorScheme === undefined)
if (colorScheme === undefined) {
return jsonError(res, 400, "colorScheme must be dark|light|no-preference|none");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "media emulation");
if (!pw) return;
if (!pw) {
return;
}
await pw.emulateMediaViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -293,15 +351,21 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/timezone", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const timezoneId = toStringOrEmpty(body.timezoneId);
if (!timezoneId) return jsonError(res, 400, "timezoneId is required");
if (!timezoneId) {
return jsonError(res, 400, "timezoneId is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "timezone");
if (!pw) return;
if (!pw) {
return;
}
await pw.setTimezoneViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -315,15 +379,21 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/locale", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const locale = toStringOrEmpty(body.locale);
if (!locale) return jsonError(res, 400, "locale is required");
if (!locale) {
return jsonError(res, 400, "locale is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "locale");
if (!pw) return;
if (!pw) {
return;
}
await pw.setLocaleViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
@@ -337,15 +407,21 @@ export function registerBrowserAgentStorageRoutes(
app.post("/set/device", async (req, res) => {
const profileCtx = resolveProfileContext(req, res, ctx);
if (!profileCtx) return;
if (!profileCtx) {
return;
}
const body = readBody(req);
const targetId = toStringOrEmpty(body.targetId) || undefined;
const name = toStringOrEmpty(body.name);
if (!name) return jsonError(res, 400, "name is required");
if (!name) {
return jsonError(res, 400, "name is required");
}
try {
const tab = await profileCtx.ensureTabAvailable(targetId);
const pw = await requirePwAi(res, "device emulation");
if (!pw) return;
if (!pw) {
return;
}
await pw.setDeviceViaPlaywright({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,

View File

@@ -131,7 +131,9 @@ export function registerBrowserBasicRoutes(app: BrowserRouteRegistrar, ctx: Brow
| "extension"
| "";
if (!name) return jsonError(res, 400, "name is required");
if (!name) {
return jsonError(res, 400, "name is required");
}
try {
const service = createBrowserProfilesService(ctx);
@@ -163,7 +165,9 @@ export function registerBrowserBasicRoutes(app: BrowserRouteRegistrar, ctx: Brow
// Delete a profile
app.delete("/profiles/:name", async (req, res) => {
const name = toStringOrEmpty(req.params.name);
if (!name) return jsonError(res, 400, "profile name is required");
if (!name) {
return jsonError(res, 400, "profile name is required");
}
try {
const service = createBrowserProfilesService(ctx);

View File

@@ -55,7 +55,9 @@ function createRegistry() {
}
function normalizePath(path: string) {
if (!path) return "/";
if (!path) {
return "/";
}
return path.startsWith("/") ? path : `/${path}`;
}
@@ -71,7 +73,9 @@ export function createBrowserRouteDispatcher(ctx: BrowserRouteContext) {
const body = req.body;
const match = registry.routes.find((route) => {
if (route.method !== method) return false;
if (route.method !== method) {
return false;
}
return route.regex.test(path);
});
if (!match) {

View File

@@ -5,10 +5,14 @@ import type { BrowserRouteRegistrar } from "./types.js";
export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) {
app.get("/tabs", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
try {
const reachable = await profileCtx.isReachable(300);
if (!reachable) return res.json({ running: false, tabs: [] as unknown[] });
if (!reachable) {
return res.json({ running: false, tabs: [] as unknown[] });
}
const tabs = await profileCtx.listTabs();
res.json({ running: true, tabs });
} catch (err) {
@@ -18,9 +22,13 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
app.post("/tabs/open", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const url = toStringOrEmpty((req.body as { url?: unknown })?.url);
if (!url) return jsonError(res, 400, "url is required");
if (!url) {
return jsonError(res, 400, "url is required");
}
try {
await profileCtx.ensureBrowserAvailable();
const tab = await profileCtx.openTab(url);
@@ -32,45 +40,65 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
app.post("/tabs/focus", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const targetId = toStringOrEmpty((req.body as { targetId?: unknown })?.targetId);
if (!targetId) return jsonError(res, 400, "targetId is required");
if (!targetId) {
return jsonError(res, 400, "targetId is required");
}
try {
if (!(await profileCtx.isReachable(300))) return jsonError(res, 409, "browser not running");
if (!(await profileCtx.isReachable(300))) {
return jsonError(res, 409, "browser not running");
}
await profileCtx.focusTab(targetId);
res.json({ ok: true });
} catch (err) {
const mapped = ctx.mapTabError(err);
if (mapped) return jsonError(res, mapped.status, mapped.message);
if (mapped) {
return jsonError(res, mapped.status, mapped.message);
}
jsonError(res, 500, String(err));
}
});
app.delete("/tabs/:targetId", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const targetId = toStringOrEmpty(req.params.targetId);
if (!targetId) return jsonError(res, 400, "targetId is required");
if (!targetId) {
return jsonError(res, 400, "targetId is required");
}
try {
if (!(await profileCtx.isReachable(300))) return jsonError(res, 409, "browser not running");
if (!(await profileCtx.isReachable(300))) {
return jsonError(res, 409, "browser not running");
}
await profileCtx.closeTab(targetId);
res.json({ ok: true });
} catch (err) {
const mapped = ctx.mapTabError(err);
if (mapped) return jsonError(res, mapped.status, mapped.message);
if (mapped) {
return jsonError(res, mapped.status, mapped.message);
}
jsonError(res, 500, String(err));
}
});
app.post("/tabs/action", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const action = toStringOrEmpty((req.body as { action?: unknown })?.action);
const index = toNumber((req.body as { index?: unknown })?.index);
try {
if (action === "list") {
const reachable = await profileCtx.isReachable(300);
if (!reachable) return res.json({ ok: true, tabs: [] as unknown[] });
if (!reachable) {
return res.json({ ok: true, tabs: [] as unknown[] });
}
const tabs = await profileCtx.listTabs();
return res.json({ ok: true, tabs });
}
@@ -84,16 +112,22 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
if (action === "close") {
const tabs = await profileCtx.listTabs();
const target = typeof index === "number" ? tabs[index] : tabs.at(0);
if (!target) return jsonError(res, 404, "tab not found");
if (!target) {
return jsonError(res, 404, "tab not found");
}
await profileCtx.closeTab(target.targetId);
return res.json({ ok: true, targetId: target.targetId });
}
if (action === "select") {
if (typeof index !== "number") return jsonError(res, 400, "index is required");
if (typeof index !== "number") {
return jsonError(res, 400, "index is required");
}
const tabs = await profileCtx.listTabs();
const target = tabs[index];
if (!target) return jsonError(res, 404, "tab not found");
if (!target) {
return jsonError(res, 404, "tab not found");
}
await profileCtx.focusTab(target.targetId);
return res.json({ ok: true, targetId: target.targetId });
}
@@ -101,7 +135,9 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
return jsonError(res, 400, "unknown tab action");
} catch (err) {
const mapped = ctx.mapTabError(err);
if (mapped) return jsonError(res, mapped.status, mapped.message);
if (mapped) {
return jsonError(res, mapped.status, mapped.message);
}
jsonError(res, 500, String(err));
}
});

View File

@@ -37,7 +37,9 @@ export function jsonError(res: BrowserResponse, status: number, message: string)
}
export function toStringOrEmpty(value: unknown) {
if (typeof value === "string") return value.trim();
if (typeof value === "string") {
return value.trim();
}
if (typeof value === "number" || typeof value === "boolean") {
return String(value).trim();
}
@@ -45,7 +47,9 @@ export function toStringOrEmpty(value: unknown) {
}
export function toNumber(value: unknown) {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" && value.trim()) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : undefined;
@@ -61,7 +65,9 @@ export function toBoolean(value: unknown) {
}
export function toStringArray(value: unknown): string[] | undefined {
if (!Array.isArray(value)) return undefined;
if (!Array.isArray(value)) {
return undefined;
}
const strings = value.map((v) => toStringOrEmpty(v)).filter(Boolean);
return strings.length ? strings : undefined;
}