fix(web): raise connection-test timeout for cold upstreams
A cold/idle self-hosted Honcho can take ~5s on its first request (DB pool, tunnel wake); the hardcoded 5s budget aborted just before the response and reported a live instance as 'Connection timed out'. Extract CONNECTION_TIMEOUT_MS (15s), make checkConnection's timeout injectable, and cover the budget behavior.
This commit is contained in:
@@ -7,6 +7,13 @@ const STORE_KEY = "openconcho:instances";
|
||||
|
||||
export const HONCHO_CLOUD_URL = "https://api.honcho.dev";
|
||||
|
||||
/**
|
||||
* Connection-test timeout. Generous because a cold/idle self-hosted Honcho (DB
|
||||
* pool spin-up, tunnel wake) can take several seconds on its first request — a
|
||||
* tight 5s budget reported live-and-reachable instances as "Connection timed out".
|
||||
*/
|
||||
export const CONNECTION_TIMEOUT_MS = 15_000;
|
||||
|
||||
function normalizeBaseUrl(url: string): string {
|
||||
return url.trim().replace(/\/+$/, "").toLowerCase();
|
||||
}
|
||||
@@ -162,6 +169,7 @@ export type HealthStatus = "ok" | "auth-required" | "unreachable" | "checking";
|
||||
export async function checkConnection(
|
||||
baseUrl: string,
|
||||
token?: string,
|
||||
timeoutMs: number = CONNECTION_TIMEOUT_MS,
|
||||
): Promise<{ status: HealthStatus; message: string }> {
|
||||
try {
|
||||
const { baseUrl: base, headers, fetch } = dispatchFor({ baseUrl, token });
|
||||
@@ -169,7 +177,7 @@ export async function checkConnection(
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({}),
|
||||
signal: AbortSignal.timeout(5000),
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
|
||||
const reject = res.headers.get(PROXY_REJECT_HEADER);
|
||||
|
||||
@@ -45,3 +45,30 @@ describe("checkConnection — web proxy mode", () => {
|
||||
expect(res.message).toMatch(/allowlist/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkConnection — timeout budget", () => {
|
||||
// A fetch that resolves after `ms`, but rejects early if the abort signal fires —
|
||||
// mirrors how a real slow upstream interacts with AbortSignal.timeout.
|
||||
function delayedFetch(ms: number) {
|
||||
return (_url: string, init: { signal?: AbortSignal }) =>
|
||||
new Promise<Response>((resolve, reject) => {
|
||||
const timer = setTimeout(() => resolve(new Response("{}", { status: 200 })), ms);
|
||||
init.signal?.addEventListener("abort", () => {
|
||||
clearTimeout(timer);
|
||||
reject(new DOMException("The operation timed out", "TimeoutError"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("reports unreachable when the upstream is slower than the timeout budget", async () => {
|
||||
httpFetchMock.mockImplementation(delayedFetch(80));
|
||||
const res = await checkConnection("https://slow.example.net", undefined, 20);
|
||||
expect(res.status).toBe("unreachable");
|
||||
});
|
||||
|
||||
it("succeeds when a slow upstream responds within the (cold-start) budget", async () => {
|
||||
httpFetchMock.mockImplementation(delayedFetch(20));
|
||||
const res = await checkConnection("https://slow.example.net", undefined, 200);
|
||||
expect(res.status).toBe("ok");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user