Files
36-character-flower/src/lib/auth/auth-session.ts
JiaJun 901ad1c30b refactor(constants): 提取常量并优化国际化配置
- 创建API相关常量文件,包括响应码、HTTP状态码、请求头等
- 将认证相关常量从auth模块提取到独立的常量文件
- 在API客户端中使用新定义的常量替换硬编码值
- 更新认证API和服务中对常量的引用
- 在国际化配置中创建统一的文案常量以减少重复
- 将认证表单验证规则改为使用常量配置
2026-06-02 12:03:29 +08:00

222 lines
5.3 KiB
TypeScript

import { LOGIN_PROMPT_DEDUP_MS } from '@/constants'
import i18n from '@/i18n'
import { notify } from '@/lib/notify'
import { queryClient } from '@/lib/query/query-client'
import type { AuthSessionInput, AuthUser } from '@/store/auth'
import { useAuthStore } from '@/store/auth'
import { useModalStore } from '@/store/modal'
export type CurrentUserInitializer = () => Promise<AuthUser | null>
export type RefreshSessionHandler = (
refreshToken: string,
) => Promise<AuthSessionInput | null>
let currentUserInitializer: CurrentUserInitializer | null = null
let refreshSessionHandler: RefreshSessionHandler | null = null
let authInitializationPromise: Promise<void> | null = null
let refreshSessionPromise: Promise<boolean> | null = null
let lastLoginPromptAt = 0
interface ClearAuthenticatedSessionOptions {
clearBrowserStorage?: boolean
clearQueryCache?: boolean
}
interface UnauthorizedSessionOptions extends ClearAuthenticatedSessionOptions {
openLoginModal?: boolean
showLoginRequiredToast?: boolean
}
function clearBrowserStorageData() {
if (typeof localStorage !== 'undefined') {
localStorage.clear()
}
if (typeof sessionStorage !== 'undefined') {
sessionStorage.clear()
}
}
function hasClearableSessionState() {
const snapshot = useAuthStore.getState()
return Boolean(
snapshot.status !== 'anonymous' ||
snapshot.accessToken ||
snapshot.refreshToken ||
snapshot.currentUser ||
snapshot.apiAuthToken ||
snapshot.apiAuthTokenExpiresAt ||
snapshot.apiAuthServerTime,
)
}
function hasRecordedUnauthorizedSession() {
const snapshot = useAuthStore.getState()
return snapshot.status === 'anonymous' && Boolean(snapshot.lastUnauthorizedAt)
}
export function registerCurrentUserInitializer(
initializer: CurrentUserInitializer | null,
) {
currentUserInitializer = initializer
}
export function registerRefreshSessionHandler(
handler: RefreshSessionHandler | null,
) {
refreshSessionHandler = handler
}
export function isAuthenticated() {
const snapshot = useAuthStore.getState()
return snapshot.status === 'authenticated' && Boolean(snapshot.accessToken)
}
export function clearAuthenticatedSession({
clearBrowserStorage = true,
clearQueryCache = true,
}: ClearAuthenticatedSessionOptions = {}) {
const alreadyUnauthorized = hasRecordedUnauthorizedSession()
if (!alreadyUnauthorized) {
useAuthStore.getState().markUnauthorized()
}
if (clearQueryCache && !alreadyUnauthorized) {
queryClient.clear()
}
if (clearBrowserStorage && !alreadyUnauthorized) {
clearBrowserStorageData()
}
}
export function handleUnauthorizedSession({
clearBrowserStorage = false,
openLoginModal = false,
showLoginRequiredToast = false,
}: UnauthorizedSessionOptions = {}) {
clearAuthenticatedSession({
clearBrowserStorage,
clearQueryCache: hasClearableSessionState(),
})
if (!openLoginModal && !showLoginRequiredToast) {
return
}
const modalStore = openLoginModal ? useModalStore.getState() : null
if (modalStore?.modals.desktopLogin) {
return
}
const now = Date.now()
const shouldPrompt = now - lastLoginPromptAt > LOGIN_PROMPT_DEDUP_MS
if (!shouldPrompt) {
return
}
lastLoginPromptAt = now
if (showLoginRequiredToast) {
notify.warning(i18n.t('commonUi.toast.loginRequired'))
}
if (openLoginModal) {
modalStore?.openExclusiveModal('desktopLogin')
}
}
export function handleInvalidTokenSession() {
handleUnauthorizedSession({
clearBrowserStorage: true,
openLoginModal: true,
showLoginRequiredToast: true,
})
}
export async function initializeAuthSession() {
if (authInitializationPromise) {
return authInitializationPromise
}
authInitializationPromise = (async () => {
await useAuthStore.persist.rehydrate()
const snapshot = useAuthStore.getState()
if (
!snapshot.accessToken ||
snapshot.currentUser ||
!currentUserInitializer
) {
return
}
const currentUser = await currentUserInitializer()
useAuthStore.getState().setCurrentUser(currentUser)
})().finally(() => {
authInitializationPromise = null
})
return authInitializationPromise
}
export async function hydrateCurrentUser(initializer: CurrentUserInitializer) {
const currentUser = await initializer()
useAuthStore.getState().setCurrentUser(currentUser)
return currentUser
}
export async function tryRefreshAuthSession() {
if (refreshSessionPromise) {
return refreshSessionPromise
}
const snapshot = useAuthStore.getState()
if (!snapshot.refreshToken || !refreshSessionHandler) {
return false
}
const refreshToken = snapshot.refreshToken
refreshSessionPromise = (async () => {
try {
const nextSession = await refreshSessionHandler(refreshToken)
if (!nextSession?.accessToken) {
handleUnauthorizedSession()
return false
}
useAuthStore.getState().startSession({
accessToken: nextSession.accessToken,
accessTokenExpiresAt:
nextSession.accessTokenExpiresAt ?? snapshot.accessTokenExpiresAt,
currentUser: nextSession.currentUser ?? snapshot.currentUser,
refreshToken: nextSession.refreshToken ?? snapshot.refreshToken,
})
return true
} catch {
handleUnauthorizedSession()
return false
} finally {
refreshSessionPromise = null
}
})()
return refreshSessionPromise
}