claude-code-best/packages/remote-control-server/web/src/hooks/useTokens.ts
2026-05-01 21:39:30 +08:00

137 lines
3.5 KiB
TypeScript

import { useState, useCallback } from 'react'
export interface TokenEntry {
id: string
token: string
label: string
}
const TOKENS_KEY = 'rcs_tokens'
const ACTIVE_TOKEN_KEY = 'rcs_uuid'
const DEFAULT_ID = '__default__'
function generateId(): string {
return `tk_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`
}
/** Ensure the existing rcs_uuid is present as the default token entry */
function ensureDefault(tokens: TokenEntry[]): TokenEntry[] {
if (tokens.some(t => t.id === DEFAULT_ID)) return tokens
let uuid: string | null = null
try {
uuid = localStorage.getItem('rcs_uuid')
} catch {
// ignore
}
if (!uuid) return tokens
return [{ id: DEFAULT_ID, token: uuid, label: 'Default' }, ...tokens]
}
function loadTokens(): TokenEntry[] {
let tokens: TokenEntry[] = []
try {
const raw = localStorage.getItem(TOKENS_KEY)
if (raw) {
const parsed = JSON.parse(raw)
if (Array.isArray(parsed)) tokens = parsed
}
} catch {
// ignore
}
return ensureDefault(tokens)
}
function loadActiveTokenId(tokens: TokenEntry[]): string {
// Try saved active token
try {
const saved = localStorage.getItem(ACTIVE_TOKEN_KEY)
if (saved && tokens.some(t => t.id === saved)) return saved
} catch {
// ignore
}
// Fall back to default (rcs_uuid) entry
const defaultEntry = tokens.find(t => t.id === DEFAULT_ID)
if (defaultEntry) return defaultEntry.id
// Fall back to first entry
return tokens[0]?.id ?? DEFAULT_ID
}
export function useTokens() {
const [tokens, setTokens] = useState<TokenEntry[]>(loadTokens)
const [activeTokenId, setActiveTokenIdState] = useState<string>(() =>
loadActiveTokenId(loadTokens()),
)
const persistTokens = useCallback((next: TokenEntry[]) => {
setTokens(next)
try {
localStorage.setItem(TOKENS_KEY, JSON.stringify(next))
} catch {
// ignore
}
}, [])
const setActiveTokenId = useCallback((id: string) => {
setActiveTokenIdState(id)
try {
localStorage.setItem(ACTIVE_TOKEN_KEY, id)
location.reload() // Reload to ensure api client picks up new token from localStorage
} catch {
// ignore
}
}, [])
const addToken = useCallback(
(token: string, label: string): string | null => {
const trimmed = token.trim()
if (!trimmed) return 'Token is required'
const entry: TokenEntry = {
id: generateId(),
token: trimmed,
label: label.trim() || trimmed.slice(0, 8),
}
const next = [...tokens, entry]
persistTokens(next)
return null
},
[tokens, persistTokens],
)
const removeToken = useCallback(
(id: string) => {
if (id === DEFAULT_ID) return // Cannot remove default
const next = tokens.filter(t => t.id !== id)
persistTokens(next)
if (activeTokenId === id) {
setActiveTokenId(DEFAULT_ID)
}
},
[tokens, persistTokens, activeTokenId, setActiveTokenId],
)
const updateToken = useCallback(
(id: string, label: string) => {
const next = tokens.map(t => (t.id === id ? { ...t, label } : t))
persistTokens(next)
},
[tokens, persistTokens],
)
const activeToken =
tokens.find(t => t.id === activeTokenId) ?? tokens[0] ?? null
const activeLabel = activeToken?.label ?? 'Default'
const activeTokenValue = activeToken?.token ?? null
return {
tokens,
activeTokenId,
activeToken,
activeLabel,
activeTokenValue,
setActiveTokenId,
addToken,
removeToken,
updateToken,
}
}