fix(cli): improve browser control errors
This commit is contained in:
24
src/browser/client.test.ts
Normal file
24
src/browser/client.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { browserStatus } from "./client.js";
|
||||
|
||||
describe("browser client", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("wraps connection failures with a gateway hint", async () => {
|
||||
const refused = Object.assign(new Error("connect ECONNREFUSED 127.0.0.1"), {
|
||||
code: "ECONNREFUSED",
|
||||
});
|
||||
const fetchFailed = Object.assign(new TypeError("fetch failed"), {
|
||||
cause: refused,
|
||||
});
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(fetchFailed));
|
||||
|
||||
await expect(browserStatus("http://127.0.0.1:18791")).rejects.toThrow(
|
||||
/Start .*gateway/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,52 @@ export type ScreenshotResult = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
function unwrapCause(err: unknown): unknown {
|
||||
if (!err || typeof err !== "object") return null;
|
||||
const cause = (err as { cause?: unknown }).cause;
|
||||
return cause ?? null;
|
||||
}
|
||||
|
||||
function enhanceBrowserFetchError(
|
||||
url: string,
|
||||
err: unknown,
|
||||
timeoutMs: number,
|
||||
): Error {
|
||||
const cause = unwrapCause(err);
|
||||
const code =
|
||||
(cause && typeof cause === "object" && "code" in cause
|
||||
? String((cause as { code?: unknown }).code ?? "")
|
||||
: "") ||
|
||||
(err && typeof err === "object" && "code" in err
|
||||
? String((err as { code?: unknown }).code ?? "")
|
||||
: "");
|
||||
|
||||
const hint =
|
||||
"Start (or restart) the Clawdis gateway (Clawdis.app menubar, or `clawdis gateway`) and try again.";
|
||||
|
||||
if (code === "ECONNREFUSED") {
|
||||
return new Error(
|
||||
`Can't reach the clawd browser control server at ${url} (connection refused). ${hint}`,
|
||||
);
|
||||
}
|
||||
if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT") {
|
||||
return new Error(
|
||||
`Can't reach the clawd browser control server at ${url} (timed out after ${timeoutMs}ms). ${hint}`,
|
||||
);
|
||||
}
|
||||
|
||||
const msg = String(err);
|
||||
if (msg.toLowerCase().includes("abort")) {
|
||||
return new Error(
|
||||
`Can't reach the clawd browser control server at ${url} (timed out after ${timeoutMs}ms). ${hint}`,
|
||||
);
|
||||
}
|
||||
|
||||
return new Error(
|
||||
`Can't reach the clawd browser control server at ${url}. ${hint} (${msg})`,
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchJson<T>(
|
||||
url: string,
|
||||
init?: RequestInit & { timeoutMs?: number },
|
||||
@@ -35,8 +81,14 @@ async function fetchJson<T>(
|
||||
const timeoutMs = init?.timeoutMs ?? 5000;
|
||||
const ctrl = new AbortController();
|
||||
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
const res = await fetch(url, { ...init, signal: ctrl.signal } as RequestInit);
|
||||
clearTimeout(t);
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(url, { ...init, signal: ctrl.signal } as RequestInit);
|
||||
} catch (err) {
|
||||
throw enhanceBrowserFetchError(url, err, timeoutMs);
|
||||
} finally {
|
||||
clearTimeout(t);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(text ? `${res.status}: ${text}` : `HTTP ${res.status}`);
|
||||
|
||||
Reference in New Issue
Block a user