function normalizeControlUrl(value) { const raw = String(value || "").trim(); if (!raw) return ""; const withProtocol = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`; const url = new URL(withProtocol); if (!["http:", "https:"].includes(url.protocol)) { throw new Error("Gateway URL must use http or https"); } url.hash = ""; url.search = ""; url.pathname = url.pathname.replace(/\/api\/shared-proxy\/?$/, "") || "/"; return url.toString().replace(/\/$/, ""); } function proxyHostFromHeader(hostHeader) { const raw = String(hostHeader || "").trim(); if (!raw) return ""; if (raw.startsWith("[")) { const end = raw.indexOf("]"); return end > 0 ? raw.slice(1, end) : ""; } return raw.split(":")[0]; } function normalizeProxyInfo(proxy) { if (!proxy || typeof proxy !== "object") return null; const host = String(proxy.host || "").trim(); const port = Number.parseInt(proxy.port, 10); const protocol = proxy.protocol === "http" ? "http" : "socks5"; if (!host || !Number.isInteger(port) || port <= 0 || port > 65535) { return null; } return { host, port, protocol }; } export function buildSharedProxyInfo({ appMode, proxyPort, running, hostHeader, sharedProxyHost, }) { const host = String(sharedProxyHost || "").trim() || proxyHostFromHeader(hostHeader); const port = Number.parseInt(proxyPort, 10); const available = appMode === "gateway" && Boolean(running) && host && Number.isInteger(port) && port > 0 && port <= 65535; const proxy = available ? { host, port, protocol: "socks5", httpUrl: `http://${host}:${port}`, socksUrl: `socks5://${host}:${port}`, } : null; return { success: true, available, mode: appMode, proxy, }; } export async function checkSharedProxyGateway(controlUrl, fetchImpl = fetch) { const baseUrl = normalizeControlUrl(controlUrl); const response = await fetchImpl(`${baseUrl}/api/shared-proxy`, { headers: { accept: "application/json" }, }); const data = await response.json().catch(() => ({})); if (!response.ok || data.success === false) { throw new Error(data.error || `Gateway returned ${response.status}`); } if (!data.available) { throw new Error("Gateway shared proxy is not available"); } const sharedProxy = normalizeProxyInfo(data.proxy); if (!sharedProxy) { throw new Error("Gateway returned invalid shared proxy settings"); } return { sharedProxyEnabled: true, sharedProxyControlUrl: baseUrl, sharedProxy, }; }