fix: resolve runtime URLs from injected desktop API base
Uses the injected desktop API base URL at call time Fixes packaged desktop WebSocket URL generation Adds regression coverage for injected runtime URLs
This commit is contained in:
@@ -8,6 +8,19 @@ import {
|
|||||||
import { setRuntimeBearerToken, setRuntimeUrlAuthToken } from './runtime-auth';
|
import { setRuntimeBearerToken, setRuntimeUrlAuthToken } from './runtime-auth';
|
||||||
|
|
||||||
describe('createRuntimeUrlResolver', () => {
|
describe('createRuntimeUrlResolver', () => {
|
||||||
|
const withWindow = <T>(value: unknown, callback: () => T): T => {
|
||||||
|
const originalWindow = globalThis.window;
|
||||||
|
try {
|
||||||
|
Object.defineProperty(globalThis, 'window', {
|
||||||
|
configurable: true,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
return callback();
|
||||||
|
} finally {
|
||||||
|
Object.defineProperty(globalThis, 'window', { configurable: true, value: originalWindow });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
test('preserves relative same-origin URLs by default', () => {
|
test('preserves relative same-origin URLs by default', () => {
|
||||||
const urls = createRuntimeUrlResolver({ currentHref: () => 'http://127.0.0.1:3000/app' });
|
const urls = createRuntimeUrlResolver({ currentHref: () => 'http://127.0.0.1:3000/app' });
|
||||||
|
|
||||||
@@ -52,6 +65,29 @@ describe('createRuntimeUrlResolver', () => {
|
|||||||
expect(urls.websocket('/api/terminal/ws')).toBe('ws://localhost:5173/api/terminal/ws');
|
expect(urls.websocket('/api/terminal/ws')).toBe('ws://localhost:5173/api/terminal/ws');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('uses injected desktop API base URL for packaged WebSocket URLs', () => {
|
||||||
|
withWindow({
|
||||||
|
location: { origin: 'openchamber-ui://app', href: 'openchamber-ui://app/index.html' },
|
||||||
|
__OPENCHAMBER_API_BASE_URL__: 'http://127.0.0.1:57123',
|
||||||
|
}, () => {
|
||||||
|
const urls = createRuntimeUrlResolver({});
|
||||||
|
|
||||||
|
expect(urls.websocket('/api/global/event/ws')).toBe('ws://127.0.0.1:57123/api/global/event/ws');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reads injected desktop API base URL at call time', () => {
|
||||||
|
withWindow({
|
||||||
|
location: { origin: 'openchamber-ui://app', href: 'openchamber-ui://app/index.html' },
|
||||||
|
}, () => {
|
||||||
|
const urls = createRuntimeUrlResolver({});
|
||||||
|
(window as typeof window & { __OPENCHAMBER_API_BASE_URL__?: string }).__OPENCHAMBER_API_BASE_URL__ = 'http://127.0.0.1:57123';
|
||||||
|
|
||||||
|
expect(urls.api('/api/config/settings')).toBe('http://127.0.0.1:57123/api/config/settings');
|
||||||
|
expect(urls.websocket('/api/global/event/ws')).toBe('ws://127.0.0.1:57123/api/global/event/ws');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('allows runtime-wide resolver configuration', () => {
|
test('allows runtime-wide resolver configuration', () => {
|
||||||
const previous = getRuntimeUrlResolver();
|
const previous = getRuntimeUrlResolver();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ const normalizeBaseUrl = (value: string | null | undefined): string => {
|
|||||||
return value.trim().replace(/\/+$/, '');
|
return value.trim().replace(/\/+$/, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const readInjectedApiBaseUrl = (): string => {
|
||||||
|
if (typeof window === 'undefined') return '';
|
||||||
|
const injected = (window as typeof window & { __OPENCHAMBER_API_BASE_URL__?: string }).__OPENCHAMBER_API_BASE_URL__;
|
||||||
|
return normalizeBaseUrl(injected);
|
||||||
|
};
|
||||||
|
|
||||||
const currentHref = (config: RuntimeUrlConfig): string => {
|
const currentHref = (config: RuntimeUrlConfig): string => {
|
||||||
const configured = config.currentHref?.();
|
const configured = config.currentHref?.();
|
||||||
if (configured) return configured;
|
if (configured) return configured;
|
||||||
@@ -107,11 +113,14 @@ const toWebSocketUrl = (candidate: string, config: RuntimeUrlConfig): string =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createRuntimeUrlResolver = (config: RuntimeUrlConfig = {}): RuntimeUrlResolver => {
|
export const createRuntimeUrlResolver = (config: RuntimeUrlConfig = {}): RuntimeUrlResolver => {
|
||||||
const apiBaseUrl = normalizeBaseUrl(config.apiBaseUrl);
|
const configuredApiBaseUrl = normalizeBaseUrl(config.apiBaseUrl);
|
||||||
const realtimeBaseUrl = normalizeBaseUrl(config.realtimeBaseUrl) || apiBaseUrl;
|
const configuredRealtimeBaseUrl = normalizeBaseUrl(config.realtimeBaseUrl);
|
||||||
|
|
||||||
const http = (path: string, query?: RuntimeUrlQuery): string => buildHttpUrl(apiBaseUrl, path, query);
|
const apiBaseUrl = (): string => configuredApiBaseUrl || readInjectedApiBaseUrl();
|
||||||
const realtime = (path: string, query?: RuntimeUrlQuery): string => buildHttpUrl(realtimeBaseUrl, path, query);
|
const realtimeBaseUrl = (): string => configuredRealtimeBaseUrl || apiBaseUrl();
|
||||||
|
|
||||||
|
const http = (path: string, query?: RuntimeUrlQuery): string => buildHttpUrl(apiBaseUrl(), path, query);
|
||||||
|
const realtime = (path: string, query?: RuntimeUrlQuery): string => buildHttpUrl(realtimeBaseUrl(), path, query);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api: http,
|
api: http,
|
||||||
|
|||||||
Reference in New Issue
Block a user