9router/src/lib/network/proxyTest.js
2026-03-09 15:52:36 +07:00

91 lines
2.4 KiB
JavaScript

import { ProxyAgent, fetch as undiciFetch } from "undici";
const DEFAULT_TEST_URL = "https://google.com/";
const DEFAULT_TIMEOUT_MS = 8000;
function getErrorMessage(err) {
if (!err) return "Unknown error";
const base = err?.message || String(err);
const causeCode = err?.cause?.code || err?.code;
const causeMessage = err?.cause?.message;
if (causeMessage && causeMessage !== base) {
return causeCode ? `${base}: ${causeMessage} (${causeCode})` : `${base}: ${causeMessage}`;
}
if (causeCode && !base.includes(causeCode)) {
return `${base} (${causeCode})`;
}
return base;
}
function normalizeString(value) {
if (value === undefined || value === null) return "";
return String(value).trim();
}
export async function testProxyUrl({ proxyUrl, testUrl, timeoutMs } = {}) {
const normalizedProxyUrl = normalizeString(proxyUrl);
if (!normalizedProxyUrl) {
return { ok: false, status: 400, error: "proxyUrl is required" };
}
const normalizedTestUrl = normalizeString(testUrl) || DEFAULT_TEST_URL;
const timeoutMsRaw = Number(timeoutMs);
const normalizedTimeoutMs =
Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0
? Math.min(timeoutMsRaw, 30000)
: DEFAULT_TIMEOUT_MS;
let dispatcher;
try {
try {
dispatcher = new ProxyAgent({ uri: normalizedProxyUrl });
} catch (err) {
return {
ok: false,
status: 400,
error: `Invalid proxy URL: ${err?.message || String(err)}`,
};
}
const controller = new AbortController();
const startedAt = Date.now();
const timer = setTimeout(() => controller.abort(), normalizedTimeoutMs);
try {
const res = await undiciFetch(normalizedTestUrl, {
method: "HEAD",
dispatcher,
signal: controller.signal,
headers: {
"User-Agent": "9Router",
},
});
return {
ok: res.ok,
status: res.status,
statusText: res.statusText,
url: normalizedTestUrl,
elapsedMs: Date.now() - startedAt,
};
} catch (err) {
const message =
err?.name === "AbortError"
? "Proxy test timed out"
: getErrorMessage(err);
return { ok: false, status: 500, error: message };
} finally {
clearTimeout(timer);
}
} finally {
try {
await dispatcher?.close?.();
} catch {
// ignore
}
}
}