mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 03:18:26 +00:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user