diff --git a/src/cliproxy/management-api-client.ts b/src/cliproxy/management-api-client.ts index 7fd95ec4..52e89a1b 100644 --- a/src/cliproxy/management-api-client.ts +++ b/src/cliproxy/management-api-client.ts @@ -28,7 +28,7 @@ const DEFAULT_HTTPS_PORT = 443; * Get effective port based on config and protocol. */ function getEffectivePort(port: number | undefined, protocol: 'http' | 'https'): number { - if (port !== undefined && port > 0 && port <= 65535) { + if (port !== undefined && Number.isInteger(port) && port > 0 && port <= 65535) { return port; } return protocol === 'https' ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT; diff --git a/src/cliproxy/sync/auto-sync-watcher.ts b/src/cliproxy/sync/auto-sync-watcher.ts index 91046296..48d91139 100644 --- a/src/cliproxy/sync/auto-sync-watcher.ts +++ b/src/cliproxy/sync/auto-sync-watcher.ts @@ -152,6 +152,9 @@ export async function stopAutoSyncWatcher(): Promise { watcherInstance = null; log('Watcher stopped'); } + + // Reset flag to prevent stale state + isSyncing = false; } /** diff --git a/src/cliproxy/sync/profile-mapper.ts b/src/cliproxy/sync/profile-mapper.ts index 6929fec3..b3796ef4 100644 --- a/src/cliproxy/sync/profile-mapper.ts +++ b/src/cliproxy/sync/profile-mapper.ts @@ -111,7 +111,11 @@ export function mapProfileToClaudeKey(profile: SyncableProfile): ClaudeKey | nul const baseUrl = env.ANTHROPIC_BASE_URL; // Generate prefix from profile name (e.g., "glm" -> "glm-") - const prefix = `${sanitizeProfileName(profile.name)}-`; + const sanitizedName = sanitizeProfileName(profile.name); + if (!sanitizedName || sanitizedName === '') { + return null; // Skip profiles with invalid names + } + const prefix = `${sanitizedName}-`; // Load model aliases for this profile const aliases = getProfileAliases(profile.name); diff --git a/src/web-server/routes/cliproxy-sync-routes.ts b/src/web-server/routes/cliproxy-sync-routes.ts index 4b8de047..c0917943 100644 --- a/src/web-server/routes/cliproxy-sync-routes.ts +++ b/src/web-server/routes/cliproxy-sync-routes.ts @@ -127,14 +127,17 @@ router.get('/aliases', async (_req: Request, res: Response): Promise => { router.post('/aliases', async (req: Request, res: Response): Promise => { try { const { profile, from, to } = req.body; + const trimmedProfile = typeof profile === 'string' ? profile.trim() : ''; + const trimmedFrom = typeof from === 'string' ? from.trim() : ''; + const trimmedTo = typeof to === 'string' ? to.trim() : ''; - if (!profile || !from || !to) { + if (!trimmedProfile || !trimmedFrom || !trimmedTo) { res.status(400).json({ error: 'Missing required fields: profile, from, to' }); return; } - addProfileAlias(profile, from, to); - res.json({ success: true, profile, from, to }); + addProfileAlias(trimmedProfile, trimmedFrom, trimmedTo); + res.json({ success: true, profile: trimmedProfile, from: trimmedFrom, to: trimmedTo }); } catch (error) { res.status(500).json({ error: (error as Error).message }); } @@ -148,14 +151,16 @@ router.post('/aliases', async (req: Request, res: Response): Promise => { router.delete('/aliases', async (req: Request, res: Response): Promise => { try { const { profile, from } = req.body; + const trimmedProfile = typeof profile === 'string' ? profile.trim() : ''; + const trimmedFrom = typeof from === 'string' ? from.trim() : ''; - if (!profile || !from) { + if (!trimmedProfile || !trimmedFrom) { res.status(400).json({ error: 'Missing required fields: profile, from' }); return; } - const removed = removeProfileAlias(profile, from); - res.json({ success: removed, profile, from }); + const removed = removeProfileAlias(trimmedProfile, trimmedFrom); + res.json({ success: removed, profile: trimmedProfile, from: trimmedFrom }); } catch (error) { res.status(500).json({ error: (error as Error).message }); } diff --git a/ui/src/hooks/use-cliproxy-sync.ts b/ui/src/hooks/use-cliproxy-sync.ts index f5ddd517..9611ae38 100644 --- a/ui/src/hooks/use-cliproxy-sync.ts +++ b/ui/src/hooks/use-cliproxy-sync.ts @@ -66,8 +66,14 @@ export interface AliasesResponse { async function fetchSyncStatus(): Promise { const response = await fetch('/api/cliproxy/sync/status'); if (!response.ok) { - const error = await response.json(); - throw new Error(error.message || 'Failed to fetch sync status'); + let message = 'Failed to fetch sync status'; + try { + const error = await response.json(); + message = error.error || error.message || message; + } catch { + // Non-JSON response (e.g., 502 Bad Gateway) + } + throw new Error(message); } return response.json(); } @@ -78,8 +84,14 @@ async function fetchSyncStatus(): Promise { async function fetchSyncPreview(): Promise { const response = await fetch('/api/cliproxy/sync/preview'); if (!response.ok) { - const error = await response.json(); - throw new Error(error.message || 'Failed to fetch sync preview'); + let message = 'Failed to fetch sync preview'; + try { + const error = await response.json(); + message = error.error || error.message || message; + } catch { + // Non-JSON response (e.g., 502 Bad Gateway) + } + throw new Error(message); } return response.json(); } @@ -93,13 +105,18 @@ async function executeSync(): Promise { headers: { 'Content-Type': 'application/json' }, }); - const data = await response.json(); - if (!response.ok) { - throw new Error(data.error || 'Sync failed'); + let message = 'Sync failed'; + try { + const data = await response.json(); + message = data.error || data.message || message; + } catch { + // Non-JSON response (e.g., 502 Bad Gateway) + } + throw new Error(message); } - return data; + return response.json(); } /** @@ -108,8 +125,14 @@ async function executeSync(): Promise { async function fetchAliases(): Promise { const response = await fetch('/api/cliproxy/sync/aliases'); if (!response.ok) { - const error = await response.json(); - throw new Error(error.message || 'Failed to fetch aliases'); + let message = 'Failed to fetch aliases'; + try { + const error = await response.json(); + message = error.error || error.message || message; + } catch { + // Non-JSON response (e.g., 502 Bad Gateway) + } + throw new Error(message); } return response.json(); } @@ -129,8 +152,14 @@ async function addAlias(params: { }); if (!response.ok) { - const error = await response.json(); - throw new Error(error.error || 'Failed to add alias'); + let message = 'Failed to add alias'; + try { + const error = await response.json(); + message = error.error || error.message || message; + } catch { + // Non-JSON response (e.g., 502 Bad Gateway) + } + throw new Error(message); } return response.json(); @@ -150,8 +179,14 @@ async function removeAlias(params: { }); if (!response.ok) { - const error = await response.json(); - throw new Error(error.error || 'Failed to remove alias'); + let message = 'Failed to remove alias'; + try { + const error = await response.json(); + message = error.error || error.message || message; + } catch { + // Non-JSON response (e.g., 502 Bad Gateway) + } + throw new Error(message); } return response.json();