📡 feat: Add Authenticated Proxy Mode for Browser RUM Telemetry (#13464)

This commit is contained in:
Ravi Kumar L 2026-06-02 03:11:35 +02:00 • committed by GitHub
parent 88e5a2f23b
commit a86e504a57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 511 additions and 22 deletions

View file

@ -103,4 +103,63 @@ describe('useRum', () => {
expect(mockInit).not.toHaveBeenCalled();
});
it('initializes proxy RUM with the LibreChat bearer token for same-origin ingest', async () => {
const fetchMock = jest.fn(() => Promise.resolve({}));
window.fetch = fetchMock;
mockUseGetStartupConfig.mockReturnValue({
data: {
rum: {
provider: 'hyperdx',
enabled: true,
url: '/api/rum',
serviceName: 'librechat-web',
authMode: 'proxy',
},
},
});
renderHook(() => useRum());
await waitFor(() => {
expect(mockInit).toHaveBeenCalledWith({
advancedNetworkCapture: false,
apiKey: 'librechat-rum-proxy',
consoleCapture: false,
disableReplay: true,
service: 'librechat-web',
tracePropagationTargets: undefined,
url: '/api/rum',
});
});
await window.fetch('/api/rum/v1/traces', { method: 'POST' });
const headers = fetchMock.mock.calls[0]?.[1]?.headers;
expect(headers).toBeInstanceOf(Headers);
expect((headers as Headers).get('authorization')).toBe('Bearer jwt-token');
});
it('does not initialize proxy RUM without an authenticated token', async () => {
mockUseAuthContext.mockReturnValue({
isAuthenticated: false,
token: undefined,
user: undefined,
});
mockUseGetStartupConfig.mockReturnValue({
data: {
rum: {
provider: 'hyperdx',
enabled: true,
url: '/api/rum',
serviceName: 'librechat-web',
authMode: 'proxy',
},
},
});
renderHook(() => useRum());
expect(mockInit).not.toHaveBeenCalled();
});
});

View file

@ -5,6 +5,11 @@ import { useGetStartupConfig } from '~/data-provider';
import { useAuthContext } from '~/hooks/AuthContext';
import { normalizeRumPath } from './routes';
const PROXY_API_KEY = 'librechat-rum-proxy';
let rumProxyToken: string | undefined;
let rumProxyFetchPatched = false;
type HyperDXBrowser = {
init: (config: {
advancedNetworkCapture: boolean;
@ -18,18 +23,70 @@ type HyperDXBrowser = {
setGlobalAttributes: (attributes: Record<string, string>) => void;
};
function shouldInitializeRum(config: TRumConfig | undefined): boolean {
function shouldInitializeRum(config: TRumConfig | undefined, token: string | undefined): boolean {
if (!config?.enabled || config.provider !== 'hyperdx' || !config.url || !config.serviceName) {
return false;
}
return config.authMode === 'publicToken' && !!config.publicToken;
if (config.authMode === 'publicToken') {
return !!config.publicToken;
}
return config.authMode === 'proxy' && !!token && !config.publicToken;
}
function getApiKey(config: TRumConfig): string {
function getApiKey(config: TRumConfig, token: string | undefined): string {
if (config.authMode === 'proxy') {
return token ? PROXY_API_KEY : '';
}
return config.publicToken ?? '';
}
function isRumProxyRequest(input: RequestInfo | URL, proxyUrl: string): boolean {
const rawUrl =
typeof Request !== 'undefined' && input instanceof Request ? input.url : input.toString();
const url = new URL(rawUrl, window.location.origin);
const proxy = new URL(proxyUrl, window.location.origin);
return url.origin === window.location.origin && url.pathname.startsWith(`${proxy.pathname}/`);
}
function withAuthorization(
input: RequestInfo | URL,
init: RequestInit | undefined,
token: string,
): [RequestInfo | URL, RequestInit | undefined] {
const headers = new Headers(
init?.headers ??
(typeof Request !== 'undefined' && input instanceof Request ? input.headers : undefined),
);
headers.set('authorization', `Bearer ${token}`);
if (typeof Request !== 'undefined' && input instanceof Request) {
return [new Request(input, { ...init, headers }), undefined];
}
return [input, { ...init, headers }];
}
function ensureRumProxyAuth(proxyUrl: string): void {
if (rumProxyFetchPatched || typeof window === 'undefined' || typeof window.fetch !== 'function') {
return;
}
const originalFetch = window.fetch.bind(window);
window.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
if (rumProxyToken && isRumProxyRequest(input, proxyUrl)) {
const [authorizedInput, authorizedInit] = withAuthorization(input, init, rumProxyToken);
return originalFetch(authorizedInput, authorizedInit);
}
return originalFetch(input, init);
};
rumProxyFetchPatched = true;
}
function buildGlobalAttributes(
user: TUser | undefined,
config: TRumConfig,
@ -56,7 +113,7 @@ async function loadHyperDX(): Promise<HyperDXBrowser> {
export default function useRum(): void {
const { data: startupConfig } = useGetStartupConfig();
const { user } = useAuthContext();
const { token, user } = useAuthContext();
const location = useLocation();
const initializedKeyRef = useRef<string | undefined>(undefined);
const sampledInitKeyRef = useRef<string | undefined>(undefined);
@ -75,13 +132,21 @@ export default function useRum(): void {
return;
}
if (!shouldInitializeRum(rumConfig)) {
if (!shouldInitializeRum(rumConfig, token)) {
if (rumConfig?.authMode === 'proxy') {
rumProxyToken = undefined;
}
return;
}
const config = rumConfig;
const apiKey = getApiKey(config, token);
if (config.authMode === 'proxy') {
rumProxyToken = token;
ensureRumProxyAuth(config.url);
}
const initKey = [config.url, config.serviceName, config.authMode, config.publicToken].join(':');
const initKey = [config.url, config.serviceName, config.authMode, apiKey].join(':');
if (initializedKeyRef.current === initKey) {
return;
@ -107,7 +172,7 @@ export default function useRum(): void {
HyperDX.init({
advancedNetworkCapture: config.advancedNetworkCapture ?? false,
apiKey: getApiKey(config),
apiKey,
consoleCapture: config.consoleCapture ?? false,
disableReplay: config.disableReplay ?? true,
service: config.serviceName,
@ -124,7 +189,7 @@ export default function useRum(): void {
return () => {
cancelled = true;
};
}, [rumConfig, user]);
}, [rumConfig, token, user]);
useEffect(() => {
hyperDxRef.current?.setGlobalAttributes(