refactor: share browser client request helpers

This commit is contained in:
Vincent Koc
2026-05-29 10:19:51 +02:00
parent d4a17477b0
commit 628104662b

View File

@@ -15,6 +15,54 @@ export type { BrowserDoctorCheck, BrowserDoctorReport } from "./doctor.js";
const BROWSER_STATUS_REQUEST_TIMEOUT_MS = 7_500;
const BROWSER_DOCTOR_REQUEST_TIMEOUT_MS = 7_500;
const BROWSER_DEEP_DOCTOR_REQUEST_TIMEOUT_MS = 10_000;
const JSON_HEADERS = { "Content-Type": "application/json" };
type BrowserClientTimeoutOptions = {
timeoutMs?: number;
};
type BrowserClientProfileOptions = BrowserClientTimeoutOptions & {
profile?: string;
};
function resolveBrowserClientTimeoutMs(
opts: BrowserClientTimeoutOptions | undefined,
fallbackMs: number,
): number {
return typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: fallbackMs;
}
function withProfilePath(baseUrl: string | undefined, path: string, profile?: string): string {
return withBaseUrl(baseUrl, `${path}${buildProfileQuery(profile)}`);
}
async function sendProfilePost(
baseUrl: string | undefined,
path: string,
opts: BrowserClientProfileOptions | undefined,
fallbackTimeoutMs: number,
): Promise<void> {
await fetchBrowserJson(withProfilePath(baseUrl, path, opts?.profile), {
method: "POST",
timeoutMs: resolveBrowserClientTimeoutMs(opts, fallbackTimeoutMs),
});
}
async function sendTabTargetRequest(params: {
baseUrl: string | undefined;
path: string;
method: "POST" | "DELETE";
opts: BrowserClientProfileOptions | undefined;
body?: object;
}): Promise<void> {
await fetchBrowserJson(withProfilePath(params.baseUrl, params.path, params.opts?.profile), {
method: params.method,
...(params.body ? { headers: JSON_HEADERS, body: JSON.stringify(params.body) } : {}),
timeoutMs: resolveBrowserClientTimeoutMs(params.opts, 5000),
});
}
export type ProfileStatus = {
name: string;
@@ -75,12 +123,8 @@ export async function browserStatus(
baseUrl?: string,
opts?: { profile?: string; timeoutMs?: number },
): Promise<BrowserStatus> {
const q = buildProfileQuery(opts?.profile);
return await fetchBrowserJson<BrowserStatus>(withBaseUrl(baseUrl, `/${q}`), {
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: BROWSER_STATUS_REQUEST_TIMEOUT_MS,
return await fetchBrowserJson<BrowserStatus>(withProfilePath(baseUrl, "/", opts?.profile), {
timeoutMs: resolveBrowserClientTimeoutMs(opts, BROWSER_STATUS_REQUEST_TIMEOUT_MS),
});
}
@@ -110,10 +154,7 @@ export async function browserProfiles(
const res = await fetchBrowserJson<{ profiles: ProfileStatus[] }>(
withBaseUrl(baseUrl, `/profiles`),
{
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: 3000,
timeoutMs: resolveBrowserClientTimeoutMs(opts, 3000),
},
);
return res.profiles ?? [];
@@ -123,28 +164,14 @@ export async function browserStart(
baseUrl?: string,
opts?: { profile?: string; timeoutMs?: number },
): Promise<void> {
const q = buildProfileQuery(opts?.profile);
await fetchBrowserJson(withBaseUrl(baseUrl, `/start${q}`), {
method: "POST",
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: 15000,
});
await sendProfilePost(baseUrl, "/start", opts, 15000);
}
export async function browserStop(
baseUrl?: string,
opts?: { profile?: string; timeoutMs?: number },
): Promise<void> {
const q = buildProfileQuery(opts?.profile);
await fetchBrowserJson(withBaseUrl(baseUrl, `/stop${q}`), {
method: "POST",
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: 15000,
});
await sendProfilePost(baseUrl, "/stop", opts, 15000);
}
export async function browserResetProfile(
@@ -186,7 +213,7 @@ export async function browserCreateProfile(
withBaseUrl(baseUrl, `/profiles/create`),
{
method: "POST",
headers: { "Content-Type": "application/json" },
headers: JSON_HEADERS,
body: JSON.stringify({
name: opts.name,
color: opts.color,
@@ -222,14 +249,10 @@ export async function browserTabs(
baseUrl?: string,
opts?: { profile?: string; timeoutMs?: number },
): Promise<BrowserTab[]> {
const q = buildProfileQuery(opts?.profile);
const res = await fetchBrowserJson<{ running: boolean; tabs: BrowserTab[] }>(
withBaseUrl(baseUrl, `/tabs${q}`),
withProfilePath(baseUrl, "/tabs", opts?.profile),
{
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: 3000,
timeoutMs: resolveBrowserClientTimeoutMs(opts, 3000),
},
);
return res.tabs ?? [];
@@ -240,15 +263,11 @@ export async function browserOpenTab(
url: string,
opts?: { profile?: string; label?: string; timeoutMs?: number },
): Promise<BrowserTab> {
const q = buildProfileQuery(opts?.profile);
return await fetchBrowserJson<BrowserTab>(withBaseUrl(baseUrl, `/tabs/open${q}`), {
return await fetchBrowserJson<BrowserTab>(withProfilePath(baseUrl, "/tabs/open", opts?.profile), {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: JSON_HEADERS,
body: JSON.stringify({ url, ...(opts?.label ? { label: opts.label } : {}) }),
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: 15000,
timeoutMs: resolveBrowserClientTimeoutMs(opts, 15000),
});
}
@@ -257,16 +276,8 @@ export async function browserFocusTab(
targetId: string,
opts?: { profile?: string; timeoutMs?: number },
): Promise<void> {
const q = buildProfileQuery(opts?.profile);
await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/focus${q}`), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ targetId }),
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: 5000,
});
const body = { targetId };
await sendTabTargetRequest({ baseUrl, path: "/tabs/focus", method: "POST", opts, body });
}
export async function browserCloseTab(
@@ -274,14 +285,8 @@ export async function browserCloseTab(
targetId: string,
opts?: { profile?: string; timeoutMs?: number },
): Promise<void> {
const q = buildProfileQuery(opts?.profile);
await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/${encodeURIComponent(targetId)}${q}`), {
method: "DELETE",
timeoutMs:
typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(1, Math.floor(opts.timeoutMs))
: 5000,
});
const path = `/tabs/${encodeURIComponent(targetId)}`;
await sendTabTargetRequest({ baseUrl, path, method: "DELETE", opts });
}
export async function browserTabAction(