refactor(ui): extract proxy base-URL and header resolution into src/lib/http

Moves the stateful API-target resolution out of the networking.tsx god-module
into src/lib/http/proxyBase.ts: proxyBaseUrl, serverRootPath, the worker-URL
localStorage handling, getProxyBaseUrl, updateProxyBaseUrl, switchToWorkerUrl,
getWindowLocation, and the global header name get/set. networking.tsx now imports
what it uses and re-exports the previously-public symbols, so its ~300 importers
are untouched.

This flips the one backwards dependency the http layer had: src/lib/http no longer
needs to reach into the component tree for the base URL, which is what was forcing
the new openapi-fetch client (api.ts) to import from networking. It's a pure
relocation; the singleton state is shared because one module instance is imported
by both networking and the http client. Drops the dead updateServerRootPath, and
replaces the isLocal console.log silencing with an inline NODE_ENV check.

Adds proxyBase.test.ts covering the getProxyBaseUrl window-origin fallback, the
worker-URL switch and its non-http-scheme rejection, and the header-name get/set.
This commit is contained in:
ryan-crabbe-berri 2026-06-06 20:18:33 -07:00
parent 3448bf79f8
commit be6cb0e924
3 changed files with 178 additions and 108 deletions

View file

@ -42,108 +42,31 @@ import { jsonFields } from "./common_components/check_openapi_schema";
import NotificationsManager from "./molecules/notifications_manager";
import type { MCPUserEnvVarsStatus } from "./mcp_tools/types";
import { createApiClient, deriveErrorMessage } from "@/lib/http/client";
import { resolveApiBase } from "@/lib/http/resolveApiBase";
import {
proxyBaseUrl,
defaultProxyBaseUrl,
globalLitellmHeaderName,
getProxyBaseUrl,
getGlobalLitellmHeaderName,
getWindowLocation,
updateProxyBaseUrl,
} from "@/lib/http/proxyBase";
export { deriveErrorMessage };
export { ApiError } from "@/lib/http/client";
export {
proxyBaseUrl,
serverRootPath,
getProxyBaseUrl,
switchToWorkerUrl,
setGlobalLitellmHeaderName,
getGlobalLitellmHeaderName,
} from "@/lib/http/proxyBase";
const isLocal = process.env.NODE_ENV === "development";
// In dev, if NEXT_PUBLIC_USE_REWRITES=true the Next.js dev server proxies API calls
// to the backend — use relative URLs (null) so rewrites can intercept them.
const resolveDefaultBase = (fallback: string | null): string | null =>
process.env.NEXT_PUBLIC_BASE_URL
? process.env.NEXT_PUBLIC_BASE_URL
: isLocal && process.env.NEXT_PUBLIC_USE_REWRITES !== "true"
? "http://localhost:4000"
: fallback;
const defaultProxyBaseUrl = resolveDefaultBase(null);
const defaultServerRootPath = "/";
export let serverRootPath = defaultServerRootPath;
const WORKER_URL_KEY = "litellm_worker_url";
// If a worker URL is in localStorage, use it as the initial proxyBaseUrl.
// This survives page navigation and the sessionStorage.clear() in user_dashboard.
const _rawWorkerUrl = typeof window !== "undefined" ? window.localStorage.getItem(WORKER_URL_KEY) : null;
// Validate stored worker URL — reject non-HTTP schemes to prevent exfiltration
const _initialWorkerUrl = (() => {
if (!_rawWorkerUrl) return null;
try {
const parsed = new URL(_rawWorkerUrl);
if (parsed.protocol === "http:" || parsed.protocol === "https:") return _rawWorkerUrl;
} catch {
/* invalid URL */
}
// Invalid URL in storage — clear it
if (typeof window !== "undefined") window.localStorage.removeItem(WORKER_URL_KEY);
return null;
})();
export let proxyBaseUrl: string | null = _initialWorkerUrl ?? defaultProxyBaseUrl;
if (isLocal != true) {
if (process.env.NODE_ENV !== "development") {
console.log = function () {};
}
const getWindowLocation = () => {
if (typeof window === "undefined") {
return null;
}
return window.location;
};
const updateProxyBaseUrl = (serverRootPath: string, receivedProxyBaseUrl: string | null = null) => {
/**
* Special function for updating the proxy base url. Should only be called by getUiConfig.
*/
// If a worker URL is in localStorage, don't let getUiConfig overwrite it
if (typeof window !== "undefined" && window.localStorage.getItem(WORKER_URL_KEY)) {
return;
}
proxyBaseUrl = resolveApiBase({
explicitBase: receivedProxyBaseUrl || resolveDefaultBase(getWindowLocation()?.origin ?? null),
serverRootPath,
});
};
const updateServerRootPath = (receivedServerRootPath: string) => {
serverRootPath = receivedServerRootPath;
};
export const getProxyBaseUrl = (): string => {
if (proxyBaseUrl) {
return proxyBaseUrl;
}
const browserLocation = getWindowLocation();
return browserLocation?.origin ?? "";
};
/**
* Switch API calls to point at a worker (or back to the control plane).
* Persists to localStorage so it survives page navigation and the
* sessionStorage.clear() in user_dashboard. Also updates the module-level
* proxyBaseUrl so in-flight code in this JS execution sees the new value
* immediately.
*/
function isValidHttpUrl(url: string): boolean {
try {
const parsed = new URL(url);
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
}
export function switchToWorkerUrl(workerUrl: string | null): void {
if (workerUrl && !isValidHttpUrl(workerUrl)) {
return;
}
if (typeof window !== "undefined") {
if (workerUrl) {
window.localStorage.setItem(WORKER_URL_KEY, workerUrl);
} else {
window.localStorage.removeItem(WORKER_URL_KEY);
}
}
proxyBaseUrl = workerUrl ?? defaultProxyBaseUrl;
}
const HTTP_REQUEST = {
GET: "GET",
POST: "POST",
@ -356,21 +279,8 @@ export const getAgentCreateMetadata = async (): Promise<AgentCreateInfo[]> => {
return jsonData;
};
// Global variable for the header name
let globalLitellmHeaderName: string = "Authorization";
const MCP_AUTH_HEADER: string = "x-mcp-auth";
// Function to set the global header name
export function setGlobalLitellmHeaderName(headerName: string = "Authorization") {
console.log(`setGlobalLitellmHeaderName: ${headerName}`);
globalLitellmHeaderName = headerName;
}
// Function to get the global header name
export function getGlobalLitellmHeaderName(): string {
return globalLitellmHeaderName;
}
const apiClient = createApiClient({
getBaseUrl: getProxyBaseUrl,
getAuthHeaderName: getGlobalLitellmHeaderName,

View file

@ -0,0 +1,52 @@
import { describe, it, expect, beforeEach } from "vitest";
import {
getProxyBaseUrl,
switchToWorkerUrl,
getGlobalLitellmHeaderName,
setGlobalLitellmHeaderName,
} from "./proxyBase";
const WORKER_URL_KEY = "litellm_worker_url";
describe("proxyBase", () => {
beforeEach(() => {
window.localStorage.clear();
switchToWorkerUrl(null);
setGlobalLitellmHeaderName("Authorization");
});
describe("getProxyBaseUrl", () => {
it("falls back to the window origin when no base is configured", () => {
expect(getProxyBaseUrl()).toBe(window.location.origin);
});
});
describe("switchToWorkerUrl", () => {
it("points the base at a valid worker URL and persists it", () => {
switchToWorkerUrl("https://worker.example.com");
expect(getProxyBaseUrl()).toBe("https://worker.example.com");
expect(window.localStorage.getItem(WORKER_URL_KEY)).toBe("https://worker.example.com");
});
it("rejects a non-http(s) scheme and leaves the base unchanged", () => {
switchToWorkerUrl("javascript:alert(1)");
expect(getProxyBaseUrl()).toBe(window.location.origin);
expect(window.localStorage.getItem(WORKER_URL_KEY)).toBeNull();
});
it("clears the worker URL and falls back when passed null", () => {
switchToWorkerUrl("https://worker.example.com");
switchToWorkerUrl(null);
expect(getProxyBaseUrl()).toBe(window.location.origin);
expect(window.localStorage.getItem(WORKER_URL_KEY)).toBeNull();
});
});
describe("global header name", () => {
it("defaults to Authorization and reflects updates", () => {
expect(getGlobalLitellmHeaderName()).toBe("Authorization");
setGlobalLitellmHeaderName("x-litellm-api-key");
expect(getGlobalLitellmHeaderName()).toBe("x-litellm-api-key");
});
});
});

View file

@ -0,0 +1,108 @@
import { resolveApiBase } from "./resolveApiBase";
const isLocal = process.env.NODE_ENV === "development";
// In dev, if NEXT_PUBLIC_USE_REWRITES=true the Next.js dev server proxies API calls
// to the backend — use relative URLs (null) so rewrites can intercept them.
const resolveDefaultBase = (fallback: string | null): string | null =>
process.env.NEXT_PUBLIC_BASE_URL
? process.env.NEXT_PUBLIC_BASE_URL
: isLocal && process.env.NEXT_PUBLIC_USE_REWRITES !== "true"
? "http://localhost:4000"
: fallback;
export const defaultProxyBaseUrl = resolveDefaultBase(null);
const defaultServerRootPath = "/";
export let serverRootPath = defaultServerRootPath;
const WORKER_URL_KEY = "litellm_worker_url";
// If a worker URL is in localStorage, use it as the initial proxyBaseUrl.
// This survives page navigation and the sessionStorage.clear() in user_dashboard.
const _rawWorkerUrl = typeof window !== "undefined" ? window.localStorage.getItem(WORKER_URL_KEY) : null;
// Validate stored worker URL — reject non-HTTP schemes to prevent exfiltration
const _initialWorkerUrl = (() => {
if (!_rawWorkerUrl) return null;
try {
const parsed = new URL(_rawWorkerUrl);
if (parsed.protocol === "http:" || parsed.protocol === "https:") return _rawWorkerUrl;
} catch {
/* invalid URL */
}
// Invalid URL in storage — clear it
if (typeof window !== "undefined") window.localStorage.removeItem(WORKER_URL_KEY);
return null;
})();
export let proxyBaseUrl: string | null = _initialWorkerUrl ?? defaultProxyBaseUrl;
export const getWindowLocation = () => {
if (typeof window === "undefined") {
return null;
}
return window.location;
};
export const updateProxyBaseUrl = (serverRootPath: string, receivedProxyBaseUrl: string | null = null) => {
/**
* Special function for updating the proxy base url. Should only be called by getUiConfig.
*/
// If a worker URL is in localStorage, don't let getUiConfig overwrite it
if (typeof window !== "undefined" && window.localStorage.getItem(WORKER_URL_KEY)) {
return;
}
proxyBaseUrl = resolveApiBase({
explicitBase: receivedProxyBaseUrl || resolveDefaultBase(getWindowLocation()?.origin ?? null),
serverRootPath,
});
};
export const getProxyBaseUrl = (): string => {
if (proxyBaseUrl) {
return proxyBaseUrl;
}
const browserLocation = getWindowLocation();
return browserLocation?.origin ?? "";
};
/**
* Switch API calls to point at a worker (or back to the control plane).
* Persists to localStorage so it survives page navigation and the
* sessionStorage.clear() in user_dashboard. Also updates the module-level
* proxyBaseUrl so in-flight code in this JS execution sees the new value
* immediately.
*/
function isValidHttpUrl(url: string): boolean {
try {
const parsed = new URL(url);
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
}
export function switchToWorkerUrl(workerUrl: string | null): void {
if (workerUrl && !isValidHttpUrl(workerUrl)) {
return;
}
if (typeof window !== "undefined") {
if (workerUrl) {
window.localStorage.setItem(WORKER_URL_KEY, workerUrl);
} else {
window.localStorage.removeItem(WORKER_URL_KEY);
}
}
proxyBaseUrl = workerUrl ?? defaultProxyBaseUrl;
}
// Global variable for the header name
export let globalLitellmHeaderName: string = "Authorization";
// Function to set the global header name
export function setGlobalLitellmHeaderName(headerName: string = "Authorization") {
console.log(`setGlobalLitellmHeaderName: ${headerName}`);
globalLitellmHeaderName = headerName;
}
// Function to get the global header name
export function getGlobalLitellmHeaderName(): string {
return globalLitellmHeaderName;
}