omniroute / src /app /api /settings /proxy /test /route.ts
igorkurgin's picture
Deploy OmniRoute to Hugging Face Space
35743bd verified
Raw
History Blame Contribute Delete
5.88 kB
import { request as undiciRequest } from "undici";
import {
createProxyDispatcher,
isSocks5ProxyEnabled,
proxyConfigToUrl,
proxyUrlForLogs,
} from "@omniroute/open-sse/utils/proxyDispatcher.ts";
import { testProxySchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse";
import { getProxyById } from "@/lib/localDb";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
const BASE_SUPPORTED_PROXY_TYPES = new Set(["http", "https"]);
function getErrorMessage(error: unknown, fallbackMessage: string): string {
if (error instanceof Error && error.message) {
return error.message;
}
return fallbackMessage;
}
function getSupportedProxyTypes() {
if (isSocks5ProxyEnabled()) {
return new Set([...BASE_SUPPORTED_PROXY_TYPES, "socks5"]);
}
return BASE_SUPPORTED_PROXY_TYPES;
}
function supportedTypesMessage() {
return isSocks5ProxyEnabled() ? "http, https, or socks5" : "http or https";
}
/**
* POST /api/settings/proxy/test — test proxy connectivity
* Body: { proxy: { type, host, port, username?, password? } }
* Returns: { success, publicIp?, latencyMs?, error? }
*/
export async function POST(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
let rawBody: unknown;
try {
rawBody = await request.json();
} catch {
return createErrorResponse({
status: 400,
message: "Invalid JSON body",
type: "invalid_request",
});
}
try {
const validation = validateBody(testProxySchema, rawBody);
if (isValidationFailure(validation)) {
return createErrorResponse({
status: 400,
message: validation.error.message,
details: validation.error.details,
type: "invalid_request",
});
}
let { proxy } = validation.data;
// If a proxyId is provided, look up the real (non-redacted) credentials from DB.
// The frontend sends redacted credentials (***) from listProxies(), so we need
// the actual secrets for testing.
const body = rawBody as Record<string, unknown>;
const proxyId = typeof body.proxyId === "string" ? body.proxyId.trim() : null;
if (proxyId) {
const dbProxy = await getProxyById(proxyId, { includeSecrets: true });
if (dbProxy) {
proxy = {
...proxy,
host: proxy.host || dbProxy.host,
port: proxy.port || String(dbProxy.port),
type: proxy.type || dbProxy.type,
username: dbProxy.username,
password: dbProxy.password,
};
}
}
const proxyType = String(proxy.type || "http").toLowerCase();
if (proxyType === "socks5" && !isSocks5ProxyEnabled()) {
return createErrorResponse({
status: 400,
message: "SOCKS5 proxy is disabled (set ENABLE_SOCKS5_PROXY=true to enable)",
type: "invalid_request",
});
}
if (proxyType.startsWith("socks") && proxyType !== "socks5") {
return createErrorResponse({
status: 400,
message: `proxy.type must be ${supportedTypesMessage()}`,
type: "invalid_request",
});
}
if (!getSupportedProxyTypes().has(proxyType)) {
return createErrorResponse({
status: 400,
message: `proxy.type must be ${supportedTypesMessage()}`,
type: "invalid_request",
});
}
let proxyUrl: string;
try {
const normalizedProxyUrl = proxyConfigToUrl(
{
type: proxyType,
host: proxy.host,
port: proxy.port,
username: proxy.username || "",
password: proxy.password || "",
},
{ allowSocks5: isSocks5ProxyEnabled() }
);
if (!normalizedProxyUrl) {
return createErrorResponse({
status: 400,
message: "Invalid proxy configuration",
type: "invalid_request",
});
}
proxyUrl = normalizedProxyUrl;
} catch (proxyError) {
return createErrorResponse({
status: 400,
message: getErrorMessage(proxyError, "Invalid proxy configuration"),
type: "invalid_request",
});
}
const publicProxyUrl = proxyUrlForLogs(proxyUrl);
const startTime = Date.now();
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
const dispatcher = createProxyDispatcher(proxyUrl);
try {
const result = await undiciRequest("https://api64.ipify.org?format=json", {
method: "GET",
dispatcher,
signal: controller.signal,
headersTimeout: 10000,
bodyTimeout: 10000,
});
const responseText = await result.body.text();
let parsed: { ip?: string };
try {
const parsedJson = JSON.parse(responseText);
if (parsedJson && typeof parsedJson === "object") {
parsed = parsedJson as { ip?: string };
} else {
parsed = { ip: String(parsedJson) };
}
} catch {
parsed = { ip: responseText.trim() };
}
return Response.json({
success: true,
publicIp: parsed.ip || null,
latencyMs: Date.now() - startTime,
proxyUrl: publicProxyUrl,
});
} catch (fetchError) {
return Response.json({
success: false,
error:
fetchError instanceof Error && fetchError.name === "AbortError"
? "Connection timeout (10s)"
: getErrorMessage(fetchError, "Connection failed"),
latencyMs: Date.now() - startTime,
proxyUrl: publicProxyUrl,
});
} finally {
clearTimeout(timeout);
}
} catch (error) {
return createErrorResponseFromUnknown(error, "Unexpected server error");
}
}