refactor(browser): share basic and tabs route helpers

This commit is contained in:
Peter Steinberger
2026-02-18 22:18:29 +00:00
parent ba49b970df
commit 42f34af776
2 changed files with 110 additions and 62 deletions

View File

@@ -1,9 +1,27 @@
import { resolveBrowserExecutableForPlatform } from "../chrome.executables.js"; import { resolveBrowserExecutableForPlatform } from "../chrome.executables.js";
import { createBrowserProfilesService } from "../profiles-service.js"; import { createBrowserProfilesService } from "../profiles-service.js";
import type { BrowserRouteContext } from "../server-context.js"; import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
import type { BrowserRouteRegistrar } from "./types.js"; import { resolveProfileContext } from "./agent.shared.js";
import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js";
import { getProfileContext, jsonError, toStringOrEmpty } from "./utils.js"; import { getProfileContext, jsonError, toStringOrEmpty } from "./utils.js";
async function withBasicProfileRoute(params: {
req: BrowserRequest;
res: BrowserResponse;
ctx: BrowserRouteContext;
run: (profileCtx: ProfileContext) => Promise<void>;
}) {
const profileCtx = resolveProfileContext(params.req, params.res, params.ctx);
if (!profileCtx) {
return;
}
try {
await params.run(profileCtx);
} catch (err) {
jsonError(params.res, 500, String(err));
}
}
export function registerBrowserBasicRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) { export function registerBrowserBasicRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) {
// List all profiles with their status // List all profiles with their status
app.get("/profiles", async (_req, res) => { app.get("/profiles", async (_req, res) => {
@@ -74,51 +92,45 @@ export function registerBrowserBasicRoutes(app: BrowserRouteRegistrar, ctx: Brow
// Start browser (profile-aware) // Start browser (profile-aware)
app.post("/start", async (req, res) => { app.post("/start", async (req, res) => {
const profileCtx = getProfileContext(req, ctx); await withBasicProfileRoute({
if ("error" in profileCtx) { req,
return jsonError(res, profileCtx.status, profileCtx.error); res,
} ctx,
run: async (profileCtx) => {
try { await profileCtx.ensureBrowserAvailable();
await profileCtx.ensureBrowserAvailable(); res.json({ ok: true, profile: profileCtx.profile.name });
res.json({ ok: true, profile: profileCtx.profile.name }); },
} catch (err) { });
jsonError(res, 500, String(err));
}
}); });
// Stop browser (profile-aware) // Stop browser (profile-aware)
app.post("/stop", async (req, res) => { app.post("/stop", async (req, res) => {
const profileCtx = getProfileContext(req, ctx); await withBasicProfileRoute({
if ("error" in profileCtx) { req,
return jsonError(res, profileCtx.status, profileCtx.error); res,
} ctx,
run: async (profileCtx) => {
try { const result = await profileCtx.stopRunningBrowser();
const result = await profileCtx.stopRunningBrowser(); res.json({
res.json({ ok: true,
ok: true, stopped: result.stopped,
stopped: result.stopped, profile: profileCtx.profile.name,
profile: profileCtx.profile.name, });
}); },
} catch (err) { });
jsonError(res, 500, String(err));
}
}); });
// Reset profile (profile-aware) // Reset profile (profile-aware)
app.post("/reset-profile", async (req, res) => { app.post("/reset-profile", async (req, res) => {
const profileCtx = getProfileContext(req, ctx); await withBasicProfileRoute({
if ("error" in profileCtx) { req,
return jsonError(res, profileCtx.status, profileCtx.error); res,
} ctx,
run: async (profileCtx) => {
try { const result = await profileCtx.resetProfile();
const result = await profileCtx.resetProfile(); res.json({ ok: true, profile: profileCtx.profile.name, ...result });
res.json({ ok: true, profile: profileCtx.profile.name, ...result }); },
} catch (err) { });
jsonError(res, 500, String(err));
}
}); });
// Create a new profile // Create a new profile

View File

@@ -48,6 +48,52 @@ async function withTabsProfileRoute(params: {
} }
} }
async function ensureBrowserRunning(profileCtx: ProfileContext, res: BrowserResponse) {
if (!(await profileCtx.isReachable(300))) {
jsonError(res, 409, "browser not running");
return false;
}
return true;
}
function resolveIndexedTab(
tabs: Awaited<ReturnType<ProfileContext["listTabs"]>>,
index: number | undefined,
) {
return typeof index === "number" ? tabs[index] : tabs.at(0);
}
function parseRequiredTargetId(res: BrowserResponse, rawTargetId: unknown): string | null {
const targetId = toStringOrEmpty(rawTargetId);
if (!targetId) {
jsonError(res, 400, "targetId is required");
return null;
}
return targetId;
}
async function runTabTargetMutation(params: {
req: BrowserRequest;
res: BrowserResponse;
ctx: BrowserRouteContext;
targetId: string;
mutate: (profileCtx: ProfileContext, targetId: string) => Promise<void>;
}) {
await withTabsProfileRoute({
req: params.req,
res: params.res,
ctx: params.ctx,
mapTabError: true,
run: async (profileCtx) => {
if (!(await ensureBrowserRunning(profileCtx, params.res))) {
return;
}
await params.mutate(profileCtx, params.targetId);
params.res.json({ ok: true });
},
});
}
export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) { export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) {
app.get("/tabs", async (req, res) => { app.get("/tabs", async (req, res) => {
await withTabsProfileRoute({ await withTabsProfileRoute({
@@ -84,43 +130,33 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
}); });
app.post("/tabs/focus", async (req, res) => { app.post("/tabs/focus", async (req, res) => {
const targetId = toStringOrEmpty((req.body as { targetId?: unknown })?.targetId); const targetId = parseRequiredTargetId(res, (req.body as { targetId?: unknown })?.targetId);
if (!targetId) { if (!targetId) {
return jsonError(res, 400, "targetId is required"); return;
} }
await runTabTargetMutation({
await withTabsProfileRoute({
req, req,
res, res,
ctx, ctx,
mapTabError: true, targetId,
run: async (profileCtx) => { mutate: async (profileCtx, id) => {
if (!(await profileCtx.isReachable(300))) { await profileCtx.focusTab(id);
return jsonError(res, 409, "browser not running");
}
await profileCtx.focusTab(targetId);
res.json({ ok: true });
}, },
}); });
}); });
app.delete("/tabs/:targetId", async (req, res) => { app.delete("/tabs/:targetId", async (req, res) => {
const targetId = toStringOrEmpty(req.params.targetId); const targetId = parseRequiredTargetId(res, req.params.targetId);
if (!targetId) { if (!targetId) {
return jsonError(res, 400, "targetId is required"); return;
} }
await runTabTargetMutation({
await withTabsProfileRoute({
req, req,
res, res,
ctx, ctx,
mapTabError: true, targetId,
run: async (profileCtx) => { mutate: async (profileCtx, id) => {
if (!(await profileCtx.isReachable(300))) { await profileCtx.closeTab(id);
return jsonError(res, 409, "browser not running");
}
await profileCtx.closeTab(targetId);
res.json({ ok: true });
}, },
}); });
}); });
@@ -152,7 +188,7 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
if (action === "close") { if (action === "close") {
const tabs = await profileCtx.listTabs(); const tabs = await profileCtx.listTabs();
const target = typeof index === "number" ? tabs[index] : tabs.at(0); const target = resolveIndexedTab(tabs, index);
if (!target) { if (!target) {
return jsonError(res, 404, "tab not found"); return jsonError(res, 404, "tab not found");
} }