/** * Unified API adapter. * * When running inside Electron, the preload script exposes `window.electronAPI`. * When running in a browser (e.g. via the HTTP server), we fall back to an * HTTP+SSE client that implements the same interface. * * All renderer code should import `api` from this module instead of * accessing `window.electronAPI` directly. * * The instance is resolved lazily on first property access so that test code * can install mocks on `window.electronAPI` before the adapter resolves. */import { HttpAPIClient } from './httpClient';import type { ElectronAPI } from '@shared/types/api';/** * Resolves the base URL for the HTTP API client. * * - Electron "server mode" (browser opened via ?port=XXXX): use explicit port on 127.0.0.1 * - Standalone/Docker (page served by the same server): use window.location.origin * to avoid cross-origin issues (localhost vs 127.0.0.1) */function getHttpBaseUrl(): string { const params = new URLSearchParams(window.location.search); const explicitPort = params.get('port'); if (explicitPort) { return `http://127.0.0.1:${parseInt(explicitPort, 10)}`; } return window.location.origin;}let httpClient: HttpAPIClient | null = null;function getImpl(): ElectronAPI { if (window.electronAPI) return window.electronAPI; // Lazily create the HTTP client only when actually needed (browser mode). // Caching avoids creating multiple EventSource connections. if (!httpClient) { httpClient = new HttpAPIClient(getHttpBaseUrl()); } return httpClient;}/** * Proxy that lazily resolves the underlying ElectronAPI on first property access. * In Electron: delegates to `window.electronAPI` (set by preload). * In browser: delegates to `HttpAPIClient` (created on first use). * In tests: delegates to whatever mock is installed on `window.electronAPI`. *//** * Whether the app is running inside Electron (true) or in a browser via HTTP server (false). * Use this to hide Electron-only UI (settings, traffic lights, etc.) in browser mode. */export const isElectronMode = (): boolean => !!window.electronAPI;export const api: ElectronAPI = new Proxy({} as ElectronAPI, { get(_target, prop, receiver) { const impl = getImpl(); const value = Reflect.get(impl, prop, receiver) as unknown; if (typeof value === 'function') { return (value as (...args: unknown[]) => unknown).bind(impl); } return value; },});
/**
* Unified API adapter.
*
* When running inside Electron, the preload script exposes `window.electronAPI`.
* When running in a browser (e.g. via the HTTP server), we fall back to an
* HTTP+SSE client that implements the same interface.
*
* All renderer code should import `api` from this module instead of
* accessing `window.electronAPI` directly.
*
* The instance is resolved lazily on first property access so that test code
* can install mocks on `window.electronAPI` before the adapter resolves.
*/
import { HttpAPIClient } from './httpClient';
import type { ElectronAPI } from '@shared/types/api';
/**
* Resolves the base URL for the HTTP API client.
*
* - Electron "server mode" (browser opened via ?port=XXXX): use explicit port on 127.0.0.1
* - Standalone/Docker (page served by the same server): use window.location.origin
* to avoid cross-origin issues (localhost vs 127.0.0.1)
*/
function getHttpBaseUrl(): string {
const params = new URLSearchParams(window.location.search);
const explicitPort = params.get('port');
if (explicitPort) {
return `http://127.0.0.1:${parseInt(explicitPort, 10)}`;
}
return window.location.origin;
}
let httpClient: HttpAPIClient | null = null;
function getImpl(): ElectronAPI {
if (window.electronAPI) return window.electronAPI;
// Lazily create the HTTP client only when actually needed (browser mode).
// Caching avoids creating multiple EventSource connections.
if (!httpClient) {
httpClient = new HttpAPIClient(getHttpBaseUrl());
}
return httpClient;
}
/**
* Proxy that lazily resolves the underlying ElectronAPI on first property access.
* In Electron: delegates to `window.electronAPI` (set by preload).
* In browser: delegates to `HttpAPIClient` (created on first use).
* In tests: delegates to whatever mock is installed on `window.electronAPI`.
*/
/**
* Whether the app is running inside Electron (true) or in a browser via HTTP server (false).
* Use this to hide Electron-only UI (settings, traffic lights, etc.) in browser mode.
*/
export const isElectronMode = (): boolean => !!window.electronAPI;
export const api: ElectronAPI = new Proxy({} as ElectronAPI, {
get(_target, prop, receiver) {
const impl = getImpl();
const value = Reflect.get(impl, prop, receiver) as unknown;
if (typeof value === 'function') {
return (value as (...args: unknown[]) => unknown).bind(impl);
}
return value;
},
});