import type express from "express"; import type { BrowserRouteContext } from "../server-context.js"; import { handleRouteError, readBody, requirePwAi, resolveProfileContext, } from "./agent.shared.js"; import { jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js"; export function registerBrowserAgentStorageRoutes( app: express.Express, ctx: BrowserRouteContext, ) { app.get("/cookies", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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; const result = await pw.cookiesGetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, }); res.json({ ok: true, targetId: tab.targetId, ...result }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/cookies/set", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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) : null; 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; await pw.cookiesSetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, cookie: { name: toStringOrEmpty(cookie.name), value: toStringOrEmpty(cookie.value), url: toStringOrEmpty(cookie.url) || undefined, domain: toStringOrEmpty(cookie.domain) || undefined, path: toStringOrEmpty(cookie.path) || undefined, expires: toNumber(cookie.expires) ?? undefined, httpOnly: toBoolean(cookie.httpOnly) ?? undefined, secure: toBoolean(cookie.secure) ?? undefined, sameSite: cookie.sameSite === "Lax" || cookie.sameSite === "None" || cookie.sameSite === "Strict" ? (cookie.sameSite as "Lax" | "None" | "Strict") : undefined, }, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/cookies/clear", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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; await pw.cookiesClearViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.get("/storage/:kind", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); if (!profileCtx) return; const kind = toStringOrEmpty(req.params.kind); 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; const result = await pw.storageGetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, kind, key: key.trim() || undefined, }); res.json({ ok: true, targetId: tab.targetId, ...result }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/storage/:kind/set", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); if (!profileCtx) return; const kind = toStringOrEmpty(req.params.kind); 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"); 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; await pw.storageSetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, kind, key, value, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/storage/:kind/clear", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); if (!profileCtx) return; const kind = toStringOrEmpty(req.params.kind); 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; await pw.storageClearViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, kind, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/offline", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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"); try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "offline"); if (!pw) return; await pw.setOfflineViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, offline, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/headers", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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) : null; if (!headers) return jsonError(res, 400, "headers is required"); const parsed: Record = {}; for (const [k, v] of Object.entries(headers)) { if (typeof v === "string") parsed[k] = v; } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "headers"); if (!pw) return; await pw.setExtraHTTPHeadersViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, headers: parsed, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/credentials", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); if (!profileCtx) return; const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const clear = toBoolean(body.clear) ?? false; const username = toStringOrEmpty(body.username) || undefined; const password = typeof body.password === "string" ? body.password : undefined; try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "http credentials"); if (!pw) return; await pw.setHttpCredentialsViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, username, password, clear, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/geolocation", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); if (!profileCtx) return; const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const clear = toBoolean(body.clear) ?? false; const latitude = toNumber(body.latitude); const longitude = toNumber(body.longitude); const accuracy = toNumber(body.accuracy) ?? undefined; const origin = toStringOrEmpty(body.origin) || undefined; try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "geolocation"); if (!pw) return; await pw.setGeolocationViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, latitude, longitude, accuracy, origin, clear, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/media", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); if (!profileCtx) return; const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const schemeRaw = toStringOrEmpty(body.colorScheme); const colorScheme = schemeRaw === "dark" || schemeRaw === "light" || schemeRaw === "no-preference" ? (schemeRaw as "dark" | "light" | "no-preference") : schemeRaw === "none" ? null : 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; await pw.emulateMediaViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, colorScheme, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/timezone", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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"); try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "timezone"); if (!pw) return; await pw.setTimezoneViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, timezoneId, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/locale", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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"); try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "locale"); if (!pw) return; await pw.setLocaleViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, locale, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); app.post("/set/device", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); 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"); try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "device emulation"); if (!pw) return; await pw.setDeviceViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, name, }); res.json({ ok: true, targetId: tab.targetId }); } catch (err) { handleRouteError(ctx, res, err); } }); }