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