mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
feat(desktop): frontend API client + zustand store for haha OAuth
- api/hahaOAuth.ts: start/status/logout 3 个方法,基于现有 api client - stores/hahaOAuthStore.ts: 状态管理 + 2s 间隔轮询 /status port 从 getBaseUrl() 动态 parse,避免 Tauri 每次启动端口变化时失效。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
669be1fbc6
commit
f10396e5bf
38
desktop/src/api/hahaOAuth.ts
Normal file
38
desktop/src/api/hahaOAuth.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// desktop/src/api/hahaOAuth.ts
|
||||||
|
|
||||||
|
import { api, getBaseUrl } from './client'
|
||||||
|
|
||||||
|
export type HahaOAuthStatus =
|
||||||
|
| { loggedIn: false }
|
||||||
|
| {
|
||||||
|
loggedIn: true
|
||||||
|
expiresAt: number | null
|
||||||
|
scopes: string[]
|
||||||
|
subscriptionType: 'pro' | 'max' | 'team' | 'enterprise' | null
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentServerPort(): number {
|
||||||
|
const port = new URL(getBaseUrl()).port
|
||||||
|
const parsed = Number.parseInt(port, 10)
|
||||||
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||||
|
throw new Error(`Cannot determine server port from baseUrl: ${getBaseUrl()}`)
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hahaOAuthApi = {
|
||||||
|
start() {
|
||||||
|
return api.post<{ authorizeUrl: string; state: string }>(
|
||||||
|
'/api/haha-oauth/start',
|
||||||
|
{ serverPort: currentServerPort() },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
status() {
|
||||||
|
return api.get<HahaOAuthStatus>('/api/haha-oauth')
|
||||||
|
},
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
return api.delete<{ ok: true }>('/api/haha-oauth')
|
||||||
|
},
|
||||||
|
}
|
||||||
89
desktop/src/stores/hahaOAuthStore.ts
Normal file
89
desktop/src/stores/hahaOAuthStore.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// desktop/src/stores/hahaOAuthStore.ts
|
||||||
|
|
||||||
|
import { create } from 'zustand'
|
||||||
|
import { hahaOAuthApi, type HahaOAuthStatus } from '../api/hahaOAuth'
|
||||||
|
|
||||||
|
const POLL_INTERVAL_MS = 2_000
|
||||||
|
|
||||||
|
type HahaOAuthState = {
|
||||||
|
status: HahaOAuthStatus | null
|
||||||
|
isPolling: boolean
|
||||||
|
isLoading: boolean
|
||||||
|
error: string | null
|
||||||
|
|
||||||
|
fetchStatus: () => Promise<void>
|
||||||
|
login: () => Promise<{ authorizeUrl: string }>
|
||||||
|
logout: () => Promise<void>
|
||||||
|
startPolling: () => void
|
||||||
|
stopPolling: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHahaOAuthStore = create<HahaOAuthState>((set, get) => {
|
||||||
|
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: null,
|
||||||
|
isPolling: false,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
|
fetchStatus: async () => {
|
||||||
|
try {
|
||||||
|
const status = await hahaOAuthApi.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 hahaOAuthApi.start()
|
||||||
|
set({ isLoading: false })
|
||||||
|
get().startPolling()
|
||||||
|
return { authorizeUrl: res.authorizeUrl }
|
||||||
|
} catch (err) {
|
||||||
|
set({
|
||||||
|
isLoading: false,
|
||||||
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
})
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
logout: async () => {
|
||||||
|
set({ isLoading: true })
|
||||||
|
try {
|
||||||
|
await hahaOAuthApi.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 })
|
||||||
|
pollTimer = setInterval(async () => {
|
||||||
|
await get().fetchStatus()
|
||||||
|
const cur = get().status
|
||||||
|
if (cur && cur.loggedIn) {
|
||||||
|
get().stopPolling()
|
||||||
|
}
|
||||||
|
}, POLL_INTERVAL_MS)
|
||||||
|
},
|
||||||
|
|
||||||
|
stopPolling: () => {
|
||||||
|
if (pollTimer) {
|
||||||
|
clearInterval(pollTimer)
|
||||||
|
pollTimer = null
|
||||||
|
}
|
||||||
|
set({ isPolling: false })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user