Files
mai-bot/dashboard/src/lib/api-base.ts
DrSmoothl b5cc361ce9 feat(electron): add main process, preload, store, protocol, and type definitions
- Add ElectronAPI type definitions and runtime contract (isElectron guard)
- Add electron-store with backend connection data model
- Add centralized API base URL utility (api-base.ts)
- Implement app:// custom protocol with API proxy
- Implement preload script with full contextBridge API
- Complete main process: BrowserWindow config, IPC handlers, window controls
- Register app:// scheme as privileged for secure renderer access
2026-03-03 00:57:50 +08:00

120 lines
3.7 KiB
TypeScript

/**
* Centralized API base URL utility
* Provides single source of truth for all URL construction across the application
* Handles environment-specific configuration (Electron, Browser DEV, Browser PROD)
*/
import type { BackendConnection } from '@/types/electron'
import { isElectron } from './runtime'
/**
* Get API base URL for HTTP/HTTPS requests
* - Electron: User-configured backend URL from main process
* - Browser DEV: Empty string (Vite proxy handles /api prefix)
* - Browser PROD: Empty string (same-origin deployment)
*/
export async function getApiBaseUrl(): Promise<string> {
if (isElectron()) {
// Electron: Get configured backend URL from IPC
const backendUrl = await window.electronAPI?.getActiveBackendUrl()
return backendUrl ?? ''
}
// Browser (DEV & PROD): Return empty string
// In DEV: Vite proxy forwards /api requests to backend
// In PROD: API is deployed on same origin as frontend
return ''
}
/**
* Get WebSocket base URL
* - Electron: Convert HTTP/HTTPS URL to WS/WSS
* - Browser DEV: ws://127.0.0.1:8001 (hardcoded, same as log-websocket.ts)
* - Browser PROD: Construct WS URL from window.location
*/
export async function getWsBaseUrl(): Promise<string> {
if (isElectron()) {
// Electron: Convert API URL protocol to WS protocol
const apiUrl = await getApiBaseUrl()
if (!apiUrl) {
return ''
}
// Convert http -> ws, https -> wss
return apiUrl.replace(/^https?/, (match) => {
return match === 'https' ? 'wss' : 'ws'
})
}
// Browser DEV: Use hardcoded WebSocket server
if (import.meta.env.DEV) {
return 'ws://127.0.0.1:8001'
}
// Browser PROD: Construct WS URL from current location
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
return `${protocol}//${host}`
}
/**
* Get synchronous API base URL for axios baseURL configuration
* Note: axios instance baseURL is set at module initialization time (synchronous).
* Since window.electronAPI.getActiveBackendUrl() is async, this function returns
* empty string. The actual Electron backend URL will be injected via axios request
* interceptor (Task 7) to support dynamic backend switching at runtime.
*/
export function getAxiosBaseUrl(): string {
// Always return empty string:
// - Browser: Vite proxy / same-origin handles paths
// - Electron: axios interceptor injects dynamic baseURL
return ''
}
/**
* Resolve full API path by prepending base URL if needed
* - Electron: Prepends configured backend URL
* - Browser: Path remains unchanged (proxy/same-origin handling)
*/
export async function resolveApiPath(path: string): Promise<string> {
if (isElectron()) {
const baseUrl = await getApiBaseUrl()
return baseUrl ? `${baseUrl}${path}` : path
}
// Browser: Path is used as-is
return path
}
/**
* Subscribe to backend URL changes
* Electron: Listens to IPC backend change events
* Browser: No-op (backend cannot change at runtime)
*
* @param callback Function called when backend URL changes
* @returns Unsubscribe function
*/
export function onBackendUrlChanged(
callback: (newUrl: string | null) => void
): () => void {
if (!isElectron()) {
// Browser: No-op, return empty unsubscribe function
return () => {}
}
// Electron: Register IPC listener and return unsubscribe function
if (!window.electronAPI?.onBackendChanged) {
return () => {}
}
// Wrap callback to extract URL from BackendConnection
const wrappedCallback = (backend: BackendConnection | null) => {
const url = backend?.url ?? null
callback(url)
}
// Get and return the unsubscribe function from preload
return window.electronAPI.onBackendChanged(wrappedCallback)
}