refactor(browser): dedupe tab route profile and error handling

This commit is contained in:
Peter Steinberger
2026-02-18 22:05:11 +00:00
parent 66c1b8b4f1
commit 06d2752a0f

View File

@@ -1,144 +1,180 @@
import type { BrowserRouteContext } from "../server-context.js"; import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
import type { BrowserRouteRegistrar } from "./types.js"; import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js";
import { getProfileContext, jsonError, toNumber, toStringOrEmpty } from "./utils.js"; import { getProfileContext, jsonError, toNumber, toStringOrEmpty } from "./utils.js";
function resolveTabsProfileContext(
req: BrowserRequest,
res: BrowserResponse,
ctx: BrowserRouteContext,
) {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) {
jsonError(res, profileCtx.status, profileCtx.error);
return null;
}
return profileCtx;
}
function handleTabsRouteError(
ctx: BrowserRouteContext,
res: BrowserResponse,
err: unknown,
opts?: { mapTabError?: boolean },
) {
if (opts?.mapTabError) {
const mapped = ctx.mapTabError(err);
if (mapped) {
return jsonError(res, mapped.status, mapped.message);
}
}
return jsonError(res, 500, String(err));
}
async function withTabsProfileRoute(params: {
req: BrowserRequest;
res: BrowserResponse;
ctx: BrowserRouteContext;
mapTabError?: boolean;
run: (profileCtx: ProfileContext) => Promise<void>;
}) {
const profileCtx = resolveTabsProfileContext(params.req, params.res, params.ctx);
if (!profileCtx) {
return;
}
try {
await params.run(profileCtx);
} catch (err) {
handleTabsRouteError(params.ctx, params.res, err, { mapTabError: params.mapTabError });
}
}
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) => {
const profileCtx = getProfileContext(req, ctx); await withTabsProfileRoute({
if ("error" in profileCtx) { req,
return jsonError(res, profileCtx.status, profileCtx.error); res,
} ctx,
try { run: async (profileCtx) => {
const reachable = await profileCtx.isReachable(300); const reachable = await profileCtx.isReachable(300);
if (!reachable) { if (!reachable) {
return res.json({ running: false, tabs: [] as unknown[] }); return res.json({ running: false, tabs: [] as unknown[] });
} }
const tabs = await profileCtx.listTabs(); const tabs = await profileCtx.listTabs();
res.json({ running: true, tabs }); res.json({ running: true, tabs });
} catch (err) { },
jsonError(res, 500, String(err)); });
}
}); });
app.post("/tabs/open", async (req, res) => { app.post("/tabs/open", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const url = toStringOrEmpty((req.body as { url?: unknown })?.url); const url = toStringOrEmpty((req.body as { url?: unknown })?.url);
if (!url) { if (!url) {
return jsonError(res, 400, "url is required"); return jsonError(res, 400, "url is required");
} }
try {
await profileCtx.ensureBrowserAvailable(); await withTabsProfileRoute({
const tab = await profileCtx.openTab(url); req,
res.json(tab); res,
} catch (err) { ctx,
jsonError(res, 500, String(err)); run: async (profileCtx) => {
} await profileCtx.ensureBrowserAvailable();
const tab = await profileCtx.openTab(url);
res.json(tab);
},
});
}); });
app.post("/tabs/focus", async (req, res) => { app.post("/tabs/focus", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const targetId = toStringOrEmpty((req.body as { targetId?: unknown })?.targetId); const targetId = toStringOrEmpty((req.body as { targetId?: unknown })?.targetId);
if (!targetId) { if (!targetId) {
return jsonError(res, 400, "targetId is required"); return jsonError(res, 400, "targetId is required");
} }
try {
if (!(await profileCtx.isReachable(300))) { await withTabsProfileRoute({
return jsonError(res, 409, "browser not running"); req,
} res,
await profileCtx.focusTab(targetId); ctx,
res.json({ ok: true }); mapTabError: true,
} catch (err) { run: async (profileCtx) => {
const mapped = ctx.mapTabError(err); if (!(await profileCtx.isReachable(300))) {
if (mapped) { return jsonError(res, 409, "browser not running");
return jsonError(res, mapped.status, mapped.message); }
} await profileCtx.focusTab(targetId);
jsonError(res, 500, String(err)); res.json({ ok: true });
} },
});
}); });
app.delete("/tabs/:targetId", async (req, res) => { app.delete("/tabs/:targetId", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const targetId = toStringOrEmpty(req.params.targetId); const targetId = toStringOrEmpty(req.params.targetId);
if (!targetId) { if (!targetId) {
return jsonError(res, 400, "targetId is required"); return jsonError(res, 400, "targetId is required");
} }
try {
if (!(await profileCtx.isReachable(300))) { await withTabsProfileRoute({
return jsonError(res, 409, "browser not running"); req,
} res,
await profileCtx.closeTab(targetId); ctx,
res.json({ ok: true }); mapTabError: true,
} catch (err) { run: async (profileCtx) => {
const mapped = ctx.mapTabError(err); if (!(await profileCtx.isReachable(300))) {
if (mapped) { return jsonError(res, 409, "browser not running");
return jsonError(res, mapped.status, mapped.message); }
} await profileCtx.closeTab(targetId);
jsonError(res, 500, String(err)); res.json({ ok: true });
} },
});
}); });
app.post("/tabs/action", async (req, res) => { app.post("/tabs/action", async (req, res) => {
const profileCtx = getProfileContext(req, ctx);
if ("error" in profileCtx) {
return jsonError(res, profileCtx.status, profileCtx.error);
}
const action = toStringOrEmpty((req.body as { action?: unknown })?.action); const action = toStringOrEmpty((req.body as { action?: unknown })?.action);
const index = toNumber((req.body as { index?: unknown })?.index); 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[] });
}
const tabs = await profileCtx.listTabs();
return res.json({ ok: true, tabs });
}
if (action === "new") { await withTabsProfileRoute({
await profileCtx.ensureBrowserAvailable(); req,
const tab = await profileCtx.openTab("about:blank"); res,
return res.json({ ok: true, tab }); ctx,
} mapTabError: true,
run: async (profileCtx) => {
if (action === "close") { if (action === "list") {
const tabs = await profileCtx.listTabs(); const reachable = await profileCtx.isReachable(300);
const target = typeof index === "number" ? tabs[index] : tabs.at(0); if (!reachable) {
if (!target) { return res.json({ ok: true, tabs: [] as unknown[] });
return jsonError(res, 404, "tab not found"); }
const tabs = await profileCtx.listTabs();
return res.json({ ok: true, tabs });
} }
await profileCtx.closeTab(target.targetId);
return res.json({ ok: true, targetId: target.targetId });
}
if (action === "select") { if (action === "new") {
if (typeof index !== "number") { await profileCtx.ensureBrowserAvailable();
return jsonError(res, 400, "index is required"); const tab = await profileCtx.openTab("about:blank");
return res.json({ ok: true, tab });
} }
const tabs = await profileCtx.listTabs();
const target = tabs[index];
if (!target) {
return jsonError(res, 404, "tab not found");
}
await profileCtx.focusTab(target.targetId);
return res.json({ ok: true, targetId: target.targetId });
}
return jsonError(res, 400, "unknown tab action"); if (action === "close") {
} catch (err) { const tabs = await profileCtx.listTabs();
const mapped = ctx.mapTabError(err); const target = typeof index === "number" ? tabs[index] : tabs.at(0);
if (mapped) { if (!target) {
return jsonError(res, mapped.status, mapped.message); return jsonError(res, 404, "tab not found");
} }
jsonError(res, 500, String(err)); 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");
}
const tabs = await profileCtx.listTabs();
const target = tabs[index];
if (!target) {
return jsonError(res, 404, "tab not found");
}
await profileCtx.focusTab(target.targetId);
return res.json({ ok: true, targetId: target.targetId });
}
return jsonError(res, 400, "unknown tab action");
},
});
}); });
} }