mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 10:16:49 +00:00
- Remove destructive /start endpoint call from Dashboard OAuth dialog (was killing running CLIProxy Docker instances via killProcessOnPort) - Use /start-url + polling only (management API, non-destructive) - Auto-open browser tab via window.open() with manual fallback URL display - Add paste-callback CLI mode (--paste-callback flag) for headless/SSH - Use dynamic proxy target with management headers instead of hardcoded localhost - Extract timeout constants, restore invariant comment - Move hook-utils tests from src/__tests__/ to tests/unit/ (fixes tsc) - Add try-catch for preset apply, remove auth URL console.log
141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
/**
|
|
* Proxy Target Resolver
|
|
*
|
|
* Determines whether CLIProxyAPI requests should go to local or remote
|
|
* based on unified config. Used by stats-fetcher, auth-routes, and UI.
|
|
*/
|
|
|
|
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
|
import type { CliproxyServerConfig } from '../config/unified-config-types';
|
|
import {
|
|
CLIPROXY_DEFAULT_PORT,
|
|
getRemoteDefaultPort,
|
|
normalizeProtocol,
|
|
validateRemotePort,
|
|
} from './config-generator';
|
|
import { getEffectiveManagementSecret } from './auth-token-manager';
|
|
|
|
/** Resolved proxy target for making requests */
|
|
export interface ProxyTarget {
|
|
/** Target hostname or IP */
|
|
host: string;
|
|
/** Target port */
|
|
port: number;
|
|
/** Protocol (http/https) */
|
|
protocol: 'http' | 'https';
|
|
/** Optional auth token for API endpoints - only send header if defined and non-empty */
|
|
authToken?: string;
|
|
/** Optional management key for management API endpoints (/v0/management/*) */
|
|
managementKey?: string;
|
|
/** True if targeting remote server, false if local */
|
|
isRemote: boolean;
|
|
}
|
|
|
|
/**
|
|
* Load cliproxy_server configuration from unified config.
|
|
* Returns undefined if not configured.
|
|
*/
|
|
function loadCliproxyServerConfig(): CliproxyServerConfig | undefined {
|
|
const config = loadOrCreateUnifiedConfig();
|
|
return config.cliproxy_server;
|
|
}
|
|
|
|
/**
|
|
* Get the current CLIProxyAPI target based on unified config.
|
|
* Returns remote server config if enabled, otherwise localhost.
|
|
*/
|
|
export function getProxyTarget(): ProxyTarget {
|
|
const config = loadCliproxyServerConfig();
|
|
|
|
if (config?.remote?.enabled && config.remote?.host) {
|
|
// Normalize protocol (handles case sensitivity and invalid values)
|
|
const protocol = normalizeProtocol(config.remote.protocol);
|
|
// Validate port (returns undefined for invalid, triggering default)
|
|
const validatedPort = validateRemotePort(config.remote.port);
|
|
const port = validatedPort ?? getRemoteDefaultPort(protocol);
|
|
|
|
return {
|
|
host: config.remote.host,
|
|
port,
|
|
protocol,
|
|
authToken: config.remote.auth_token || undefined, // Empty string -> undefined
|
|
managementKey: config.remote.management_key || undefined, // Empty string -> undefined
|
|
isRemote: true,
|
|
};
|
|
}
|
|
|
|
const localPort = config?.local?.port ?? CLIPROXY_DEFAULT_PORT;
|
|
|
|
return {
|
|
host: '127.0.0.1',
|
|
port: localPort,
|
|
protocol: 'http',
|
|
isRemote: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build URL for proxy endpoint
|
|
* @param target Resolved proxy target
|
|
* @param path Endpoint path (e.g., '/v0/management/usage')
|
|
*/
|
|
export function buildProxyUrl(target: ProxyTarget, path: string): string {
|
|
// Normalize path to ensure leading slash
|
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
return `${target.protocol}://${target.host}:${target.port}${normalizedPath}`;
|
|
}
|
|
|
|
/**
|
|
* Build request headers for proxy requests
|
|
* Handles optional auth token - only adds Authorization header if token is set.
|
|
*
|
|
* @param target Resolved proxy target
|
|
* @param additionalHeaders Extra headers to merge
|
|
*/
|
|
export function buildProxyHeaders(
|
|
target: ProxyTarget,
|
|
additionalHeaders: Record<string, string> = {}
|
|
): Record<string, string> {
|
|
const headers: Record<string, string> = {
|
|
Accept: 'application/json',
|
|
...additionalHeaders,
|
|
};
|
|
|
|
// Only add auth header if token is configured
|
|
if (target.authToken) {
|
|
headers['Authorization'] = `Bearer ${target.authToken}`;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* Build request headers for management API endpoints (/v0/management/*).
|
|
* For remote targets: uses management_key, falls back to authToken.
|
|
* For local targets: uses the effective management secret from CCS config.
|
|
*
|
|
* @param target Resolved proxy target
|
|
* @param additionalHeaders Extra headers to merge
|
|
*/
|
|
export function buildManagementHeaders(
|
|
target: ProxyTarget,
|
|
additionalHeaders: Record<string, string> = {}
|
|
): Record<string, string> {
|
|
const headers: Record<string, string> = {
|
|
Accept: 'application/json',
|
|
...additionalHeaders,
|
|
};
|
|
|
|
// Remote: use configured management key or auth token
|
|
// Local: use CCS management secret (default: 'ccs')
|
|
const authKey = target.isRemote
|
|
? (target.managementKey ?? target.authToken)
|
|
: getEffectiveManagementSecret();
|
|
|
|
if (authKey) {
|
|
headers['Authorization'] = `Bearer ${authKey}`;
|
|
}
|
|
|
|
return headers;
|
|
}
|