342 lines
9.2 KiB
TypeScript
342 lines
9.2 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState, useCallback } from 'react'
|
||
import { useSession } from '@/lib/auth-client'
|
||
import { userCache, UserData, SyncTrigger, SyncOptions, shouldSync } from '@/lib/user-cache'
|
||
import type { User } from '@/lib/auth'
|
||
|
||
// 全局防抖变量
|
||
const debounceTimeouts: Map<string, NodeJS.Timeout> = new Map()
|
||
const pendingPromises: Map<string, Promise<void>> = new Map()
|
||
|
||
interface AuthUserState {
|
||
user: User | null
|
||
userData: UserData | null
|
||
loading: boolean
|
||
isAdmin: boolean
|
||
}
|
||
|
||
// 全局状态管理,避免多个钩子实例重复调用
|
||
let globalAuthState: AuthUserState = {
|
||
user: null,
|
||
userData: null,
|
||
loading: true,
|
||
isAdmin: false,
|
||
}
|
||
const stateListeners: Set<(state: AuthUserState) => void> = new Set()
|
||
let isGlobalInitialized = false
|
||
let currentUserId: string | null = null
|
||
let lastSyncTime: number = 0
|
||
|
||
// 广播状态变化给所有监听器
|
||
const notifyStateChange = (newState: AuthUserState) => {
|
||
globalAuthState = { ...newState }
|
||
stateListeners.forEach(listener => listener(newState))
|
||
}
|
||
|
||
export function useBetterAuth() {
|
||
const [state, setState] = useState<AuthUserState>(globalAuthState)
|
||
const { data: session, isPending } = useSession()
|
||
|
||
// 注册状态监听器
|
||
useEffect(() => {
|
||
const listener = (newState: AuthUserState) => {
|
||
setState(newState)
|
||
}
|
||
stateListeners.add(listener)
|
||
|
||
return () => {
|
||
stateListeners.delete(listener)
|
||
}
|
||
}, [])
|
||
|
||
// 全局同步函数,防止重复调用
|
||
const globalSyncUserToDatabase = useCallback(async (userId: string, options: SyncOptions) => {
|
||
// 检查是否在短时间内已经同步过
|
||
const now = Date.now()
|
||
if (!options.force && now - lastSyncTime < 5000) { // 5秒内不重复同步
|
||
return
|
||
}
|
||
|
||
if (!shouldSync(userId, options)) {
|
||
return
|
||
}
|
||
|
||
return userCache.getSyncPromise(userId, async () => {
|
||
try {
|
||
const response = await fetch('/api/users/sync', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' }
|
||
})
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`Sync failed: ${response.status}`)
|
||
}
|
||
|
||
userCache.recordSync(userId)
|
||
lastSyncTime = Date.now()
|
||
console.log(`User synced to database: ${userId} (trigger: ${options.trigger})`)
|
||
} catch (error) {
|
||
console.error('Failed to sync user to database:', error)
|
||
throw error
|
||
}
|
||
})
|
||
}, [])
|
||
|
||
// 全局获取用户数据函数
|
||
const globalFetchUserData = useCallback(async (userId: string, options: SyncOptions) => {
|
||
// 先检查缓存
|
||
if (!options.skipCache) {
|
||
const cachedData = userCache.getUserData(userId)
|
||
if (cachedData) {
|
||
const newState = {
|
||
...globalAuthState,
|
||
userData: cachedData,
|
||
isAdmin: cachedData.isAdmin || false,
|
||
}
|
||
notifyStateChange(newState)
|
||
return cachedData
|
||
}
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/users/sync')
|
||
if (!response.ok) {
|
||
throw new Error(`Failed to fetch user data: ${response.status}`)
|
||
}
|
||
|
||
const data = await response.json()
|
||
const userData = data.user as UserData
|
||
|
||
// 更新缓存
|
||
userCache.setUserData(userId, userData)
|
||
|
||
// 更新全局状态
|
||
const newState = {
|
||
...globalAuthState,
|
||
userData,
|
||
isAdmin: userData.isAdmin || false,
|
||
}
|
||
notifyStateChange(newState)
|
||
|
||
return userData
|
||
} catch (error) {
|
||
console.error('Failed to fetch user data:', error)
|
||
throw error
|
||
}
|
||
}, [])
|
||
|
||
// 立即更新用户状态(不防抖)
|
||
const updateUserState = useCallback((user: User | null) => {
|
||
const newState = {
|
||
...globalAuthState,
|
||
user,
|
||
loading: false,
|
||
}
|
||
notifyStateChange(newState)
|
||
|
||
// 如果用户变化了,清除旧用户的缓存
|
||
if (currentUserId && currentUserId !== user?.id) {
|
||
userCache.clearUserCache(currentUserId)
|
||
lastSyncTime = 0 // 重置同步时间
|
||
}
|
||
currentUserId = user?.id || null
|
||
|
||
if (!user) {
|
||
const userLoggedOutState = {
|
||
...globalAuthState,
|
||
user: null,
|
||
userData: null,
|
||
isAdmin: false,
|
||
loading: false,
|
||
}
|
||
notifyStateChange(userLoggedOutState)
|
||
}
|
||
}, [])
|
||
|
||
// 全局处理用户数据同步(可以防抖)
|
||
const globalHandleUserDataSync = useCallback(async (
|
||
user: User | null,
|
||
trigger: SyncTrigger = SyncTrigger.INITIAL_LOAD
|
||
) => {
|
||
if (!user) return
|
||
|
||
// 避免重复处理相同用户
|
||
if (trigger === SyncTrigger.INITIAL_LOAD && isGlobalInitialized && user.id === currentUserId) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
const syncOptions: SyncOptions = { trigger }
|
||
|
||
// 并行执行同步和获取用户数据,但添加去重机制
|
||
await Promise.all([
|
||
globalSyncUserToDatabase(user.id, syncOptions),
|
||
globalFetchUserData(user.id, syncOptions),
|
||
])
|
||
|
||
console.log(`User data loaded: ${user.id} (trigger: ${trigger})`)
|
||
} catch (error) {
|
||
console.error('Failed to handle user change:', error)
|
||
}
|
||
}, [globalSyncUserToDatabase, globalFetchUserData])
|
||
|
||
// 创建防抖版本的用户数据同步处理
|
||
const debouncedUserDataSync = useCallback(async (
|
||
user: User | null,
|
||
trigger: SyncTrigger = SyncTrigger.INITIAL_LOAD
|
||
) => {
|
||
const key = `${user?.id || 'anonymous'}-${trigger}`
|
||
|
||
// 清除之前的超时
|
||
const existingTimeout = debounceTimeouts.get(key)
|
||
if (existingTimeout) {
|
||
clearTimeout(existingTimeout)
|
||
}
|
||
|
||
// 如果有正在执行的Promise,直接返回
|
||
const existingPromise = pendingPromises.get(key)
|
||
if (existingPromise) {
|
||
return existingPromise
|
||
}
|
||
|
||
// 创建新的防抖Promise
|
||
const debouncePromise = new Promise<void>((resolve) => {
|
||
const timeout = setTimeout(async () => {
|
||
debounceTimeouts.delete(key)
|
||
try {
|
||
await globalHandleUserDataSync(user, trigger)
|
||
} finally {
|
||
pendingPromises.delete(key)
|
||
resolve()
|
||
}
|
||
}, 300)
|
||
|
||
debounceTimeouts.set(key, timeout)
|
||
})
|
||
|
||
pendingPromises.set(key, debouncePromise)
|
||
return debouncePromise
|
||
}, [globalHandleUserDataSync])
|
||
|
||
// 监听 Better Auth 会话变化
|
||
useEffect(() => {
|
||
let mounted = true
|
||
|
||
const updateAuthState = async () => {
|
||
if (!mounted) return
|
||
|
||
const user = session?.user || null
|
||
const trigger = !isGlobalInitialized ? SyncTrigger.INITIAL_LOAD : SyncTrigger.SIGN_IN
|
||
|
||
// 立即更新用户状态(不延迟)
|
||
updateUserState(user)
|
||
|
||
// 异步进行数据同步(防抖延迟)
|
||
if (user) {
|
||
debouncedUserDataSync(user, trigger)
|
||
}
|
||
|
||
if (!isGlobalInitialized) {
|
||
isGlobalInitialized = true
|
||
}
|
||
}
|
||
|
||
if (!isPending) {
|
||
updateAuthState()
|
||
}
|
||
|
||
return () => {
|
||
mounted = false
|
||
}
|
||
}, [session, isPending, updateUserState, debouncedUserDataSync])
|
||
|
||
// 设置loading状态
|
||
useEffect(() => {
|
||
const newState = {
|
||
...globalAuthState,
|
||
loading: isPending
|
||
}
|
||
notifyStateChange(newState)
|
||
}, [isPending])
|
||
|
||
// 手动刷新用户数据
|
||
const refreshUserData = useCallback(async (force = false) => {
|
||
if (!globalAuthState.user) return
|
||
|
||
try {
|
||
notifyStateChange({ ...globalAuthState, loading: true })
|
||
|
||
const syncOptions: SyncOptions = {
|
||
trigger: SyncTrigger.MANUAL_REFRESH,
|
||
force,
|
||
skipCache: force,
|
||
}
|
||
|
||
await Promise.all([
|
||
globalSyncUserToDatabase(globalAuthState.user.id, syncOptions),
|
||
globalFetchUserData(globalAuthState.user.id, syncOptions),
|
||
])
|
||
} catch (error) {
|
||
console.error('Failed to refresh user data:', error)
|
||
} finally {
|
||
notifyStateChange({ ...globalAuthState, loading: false })
|
||
}
|
||
}, [globalSyncUserToDatabase, globalFetchUserData])
|
||
|
||
// 登出
|
||
const signOut = useCallback(async () => {
|
||
try {
|
||
const { signOut: betterSignOut } = await import('@/lib/auth-client')
|
||
await betterSignOut()
|
||
|
||
// 清除缓存
|
||
if (currentUserId) {
|
||
userCache.clearUserCache(currentUserId)
|
||
}
|
||
|
||
// 重置全局状态
|
||
isGlobalInitialized = false
|
||
currentUserId = null
|
||
lastSyncTime = 0
|
||
|
||
// 重定向到首页
|
||
window.location.href = '/'
|
||
} catch (error) {
|
||
console.error('Failed to sign out:', error)
|
||
}
|
||
}, [])
|
||
|
||
// 触发用户信息更新(用于个人资料更新后)
|
||
const triggerProfileUpdate = useCallback(async () => {
|
||
if (!globalAuthState.user) return
|
||
|
||
try {
|
||
await globalHandleUserDataSync(globalAuthState.user, SyncTrigger.PROFILE_UPDATE)
|
||
} catch (error) {
|
||
console.error('Failed to update profile:', error)
|
||
}
|
||
}, [globalHandleUserDataSync])
|
||
|
||
// 触发订阅状态更新
|
||
const triggerSubscriptionUpdate = useCallback(async () => {
|
||
if (!globalAuthState.user) return
|
||
|
||
try {
|
||
await globalHandleUserDataSync(globalAuthState.user, SyncTrigger.SUBSCRIPTION_CHANGE)
|
||
} catch (error) {
|
||
console.error('Failed to update subscription:', error)
|
||
}
|
||
}, [globalHandleUserDataSync])
|
||
|
||
return {
|
||
user: state.user,
|
||
userData: state.userData,
|
||
loading: state.loading,
|
||
isAdmin: state.isAdmin,
|
||
signOut,
|
||
refreshUserData,
|
||
triggerProfileUpdate,
|
||
triggerSubscriptionUpdate,
|
||
}
|
||
} |