163 lines
4.2 KiB
TypeScript
163 lines
4.2 KiB
TypeScript
import { AppState, type AppStateStatus } from 'react-native'
|
||
import { create } from 'zustand'
|
||
|
||
import { subscription } from '@/lib/auth'
|
||
import type { ApiError } from '@/lib/types'
|
||
|
||
interface UserBalanceState {
|
||
balance: number
|
||
loading: boolean
|
||
error: ApiError | null
|
||
lastLoadTime: number
|
||
pollingEnabled: boolean
|
||
}
|
||
|
||
interface UserBalanceActions {
|
||
load: (force?: boolean) => Promise<void>
|
||
setBalance: (balance: number) => void
|
||
deductBalance: (amount: number) => void
|
||
startPolling: () => void
|
||
stopPolling: () => void
|
||
restartPolling: () => void
|
||
reset: () => void
|
||
}
|
||
|
||
type UserBalanceStore = UserBalanceState & UserBalanceActions
|
||
|
||
const POLLING_INTERVAL = 60e3 // 60秒轮询一次
|
||
const DEBOUNCE_TIME = 5e3 // 5秒防抖
|
||
|
||
let pollingInterval: ReturnType<typeof setInterval> | null = null
|
||
let loadingPromise: Promise<void> | null = null
|
||
let appStateSubscription: { remove: () => void } | null = null
|
||
|
||
export const useUserBalanceStore = create<UserBalanceStore>((set, get) => ({
|
||
// State
|
||
balance: 0,
|
||
loading: false,
|
||
error: null,
|
||
lastLoadTime: 0,
|
||
pollingEnabled: false,
|
||
|
||
// Actions
|
||
load: async (force = false) => {
|
||
const state = get()
|
||
const now = Date.now()
|
||
const timeSinceLastLoad = now - state.lastLoadTime
|
||
|
||
// 如果不是强制刷新,且距离上次调用少于5秒,则跳过
|
||
if (!force && timeSinceLastLoad < DEBOUNCE_TIME) {
|
||
console.log('跳过余额加载,距离上次调用时间过短:', timeSinceLastLoad + 'ms')
|
||
return
|
||
}
|
||
|
||
// 如果已经在加载中且不是强制刷新,直接返回
|
||
if (state.loading && !force) {
|
||
console.log('余额加载中,跳过重复请求')
|
||
return loadingPromise || undefined
|
||
}
|
||
|
||
set({ lastLoadTime: now, loading: true })
|
||
|
||
loadingPromise = (async () => {
|
||
try {
|
||
const { data, error } = await subscription.list()
|
||
|
||
if (error) {
|
||
set({ error, loading: false })
|
||
return
|
||
}
|
||
|
||
const meteredSubscriptions = data?.filter((sub) => sub.type === 'metered') || []
|
||
const creditBalance = meteredSubscriptions[0]?.creditBalance?.remainingTokenBalance || 0
|
||
set({ balance: creditBalance, error: null, loading: false })
|
||
} catch (e) {
|
||
console.error('加载余额失败:', e)
|
||
set({ loading: false })
|
||
} finally {
|
||
loadingPromise = null
|
||
}
|
||
})()
|
||
|
||
return loadingPromise
|
||
},
|
||
|
||
setBalance: (balance: number) => {
|
||
set({ balance })
|
||
},
|
||
|
||
deductBalance: (amount: number) => {
|
||
set((state) => ({ balance: Math.max(0, state.balance - amount) }))
|
||
},
|
||
|
||
startPolling: () => {
|
||
const { stopPolling, load } = get()
|
||
|
||
// 先清理可能存在的旧轮询状态
|
||
stopPolling()
|
||
|
||
console.log('开始余额轮询,间隔:', POLLING_INTERVAL / 1000, '秒')
|
||
set({ pollingEnabled: true })
|
||
|
||
// 立即执行一次
|
||
load(false)
|
||
|
||
// 设置定时器
|
||
pollingInterval = setInterval(() => {
|
||
const state = get()
|
||
if (state.pollingEnabled) {
|
||
load(false)
|
||
}
|
||
}, POLLING_INTERVAL)
|
||
|
||
// 设置应用状态监听
|
||
if (!appStateSubscription) {
|
||
appStateSubscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
|
||
const state = get()
|
||
if (nextAppState === 'active' && state.pollingEnabled) {
|
||
console.log('应用回到前台,重启余额轮询')
|
||
get().restartPolling()
|
||
} else if (nextAppState === 'background' || nextAppState === 'inactive') {
|
||
console.log('应用进入后台,暂停余额轮询')
|
||
get().stopPolling()
|
||
}
|
||
})
|
||
}
|
||
},
|
||
|
||
stopPolling: () => {
|
||
console.log('停止余额轮询')
|
||
set({ pollingEnabled: false })
|
||
|
||
if (pollingInterval) {
|
||
clearInterval(pollingInterval)
|
||
pollingInterval = null
|
||
}
|
||
},
|
||
|
||
restartPolling: () => {
|
||
const { stopPolling, startPolling } = get()
|
||
stopPolling()
|
||
setTimeout(() => startPolling(), 1000) // 延迟1秒重启
|
||
},
|
||
|
||
reset: () => {
|
||
const { stopPolling } = get()
|
||
stopPolling()
|
||
|
||
set({
|
||
balance: 0,
|
||
loading: false,
|
||
error: null,
|
||
lastLoadTime: 0,
|
||
pollingEnabled: false,
|
||
})
|
||
|
||
// 清理应用状态监听器
|
||
if (appStateSubscription) {
|
||
appStateSubscription.remove()
|
||
appStateSubscription = null
|
||
}
|
||
},
|
||
}))
|