mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
The desktop app now has a dedicated OpenAI OAuth API client and store mirroring the existing Claude Official flow. The component uses the existing browser-open plus polling pattern and never exposes token material to the UI. Constraint: OAuth status responses must not return token bodies Rejected: Reuse Claude OAuth store | the status shape and endpoint differ Confidence: high Scope-risk: narrow Tested: cd desktop && bun run test -- src/stores/hahaOpenAIOAuthStore.test.ts Tested: git diff --check
102 lines
2.4 KiB
TypeScript
102 lines
2.4 KiB
TypeScript
// desktop/src/stores/hahaOpenAIOAuthStore.ts
|
|
|
|
import { create } from 'zustand'
|
|
import {
|
|
hahaOpenAIOAuthApi,
|
|
type HahaOpenAIOAuthStatus,
|
|
} from '../api/hahaOpenAIOAuth'
|
|
|
|
const POLL_INTERVAL_MS = 2_000
|
|
|
|
type HahaOpenAIOAuthState = {
|
|
status: HahaOpenAIOAuthStatus | null
|
|
isPolling: boolean
|
|
isLoading: boolean
|
|
error: string | null
|
|
|
|
fetchStatus: () => Promise<void>
|
|
login: () => Promise<{ authorizeUrl: string }>
|
|
logout: () => Promise<void>
|
|
startPolling: () => void
|
|
stopPolling: () => void
|
|
}
|
|
|
|
export const useHahaOpenAIOAuthStore = create<HahaOpenAIOAuthState>((set, get) => {
|
|
let pollTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
return {
|
|
status: null,
|
|
isPolling: false,
|
|
isLoading: false,
|
|
error: null,
|
|
|
|
fetchStatus: async () => {
|
|
try {
|
|
const status = await hahaOpenAIOAuthApi.status()
|
|
set({ status, error: null })
|
|
} catch (err) {
|
|
set({ error: err instanceof Error ? err.message : String(err) })
|
|
}
|
|
},
|
|
|
|
login: async () => {
|
|
set({ isLoading: true, error: null })
|
|
try {
|
|
const res = await hahaOpenAIOAuthApi.start()
|
|
set({ isLoading: false })
|
|
return { authorizeUrl: res.authorizeUrl }
|
|
} catch (err) {
|
|
set({
|
|
isLoading: false,
|
|
error: err instanceof Error ? err.message : String(err),
|
|
})
|
|
throw err
|
|
}
|
|
},
|
|
|
|
logout: async () => {
|
|
get().stopPolling()
|
|
set({ isLoading: true, error: null })
|
|
try {
|
|
await hahaOpenAIOAuthApi.logout()
|
|
set({ status: { loggedIn: false }, isLoading: false })
|
|
} catch (err) {
|
|
set({
|
|
isLoading: false,
|
|
error: err instanceof Error ? err.message : String(err),
|
|
})
|
|
throw err
|
|
}
|
|
},
|
|
|
|
startPolling: () => {
|
|
if (pollTimer) return
|
|
set({ isPolling: true })
|
|
|
|
const scheduleNext = () => {
|
|
pollTimer = setTimeout(async () => {
|
|
pollTimer = null
|
|
await get().fetchStatus()
|
|
const cur = get().status
|
|
if (cur && cur.loggedIn) {
|
|
get().stopPolling()
|
|
return
|
|
}
|
|
if (get().isPolling) {
|
|
scheduleNext()
|
|
}
|
|
}, POLL_INTERVAL_MS)
|
|
}
|
|
scheduleNext()
|
|
},
|
|
|
|
stopPolling: () => {
|
|
if (pollTimer) {
|
|
clearTimeout(pollTimer)
|
|
pollTimer = null
|
|
}
|
|
set({ isPolling: false })
|
|
},
|
|
}
|
|
})
|