Files
frontend/src/stores/user/UserManager.ts
lafay 705b365536
Some checks failed
Frontend CI / ota (android) (push) Successful in 3m11s
Frontend CI / ota (ios) (push) Successful in 3m31s
Frontend CI / build-and-push-web (push) Successful in 4m38s
Frontend CI / build-android-apk (push) Has been cancelled
refactor(core): distinguish network errors from auth failures and refactor real-time messaging pipeline
- Add isNetworkError() helper and FetchCurrentUserResult discriminated union to prevent logout on network failures
- Refactor authService to return { kind: 'user' } | { kind: 'auth_failed' } | { kind: 'network_error' }
- Update authStore to preserve login state on network errors, only logout on auth failures
- Replace WSMessageHandler with RealtimeIngestionPipeline for improved real-time message handling
- Remove MessageDeduplication service; add store-level idempotent addOrReplaceMessage for message dedup
- Add atomic systemUnreadCount operations to prevent race conditions
- Add SearchHeader component for consistent search UI across screens
- Add 'home' TabBar variant with underline style matching HomeScreen
- Add Jest test infrastructure and exclude tests from tsconfig
- Fix ChatScreen history lock being stuck when scrolling to latest messages
- Remove unused call_participant_joined/left WS event types
2026-06-21 17:17:15 +08:00

141 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* UserManager - 用户管理核心模块(重构版)
*
* 使用 Zustand store 进行状态管理
* 保持与旧 API 的向后兼容
*/
import type { UserDTO } from '../../types/dto';
import { authService } from '@/services/auth';
import { userCacheRepository } from '@/database';
import { useUserManagerStore } from './userStore';
import { createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
import { createDedupe } from '../utils/requestDedupe';
const dedupe = createDedupe(() => useUserManagerStore);
// 将 fetchCurrentUserFromAPI 的判别结果转为 User | null。
// 网络故障/认证失败时返回 null由上层缓存兜底避免空数据覆盖缓存
// 注意UserManager 只负责缓存,不触发登出,登出由 authStore 统一处理。
async function fetchCurrentUserOrNull(): Promise<UserDTO | null> {
const result = await authService.fetchCurrentUserFromAPI();
return result.kind === 'user' ? result.user : null;
}
// ==================== UserManager 类 ====================
class UserManager {
async getCurrentUser(forceRefresh = false): Promise<UserDTO | null> {
const store = useUserManagerStore.getState();
const entry = store.currentUserEntry;
if (!forceRefresh && entry && !isCacheExpired(entry)) {
return entry.data;
}
if (!forceRefresh && entry && isCacheExpired(entry)) {
this.refreshCurrentUserInBackground();
return entry.data;
}
if (!forceRefresh) {
const cachedFromDb = await userCacheRepository.getCurrent();
if (cachedFromDb) {
const newEntry = createCacheEntry(cachedFromDb, DEFAULT_TTL.USER);
store.setCurrentUserEntry(newEntry);
this.refreshCurrentUserInBackground();
return cachedFromDb;
}
}
return dedupe('users:current', async () => {
const user = await fetchCurrentUserOrNull();
// 仅在拿到真实用户时刷新缓存;网络/认证失败时保留旧缓存,避免误清空
if (user) {
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setCurrentUserEntry(newEntry);
userCacheRepository.saveCurrent(user).catch(() => {});
userCacheRepository.save(user).catch(() => {});
}
return user;
});
}
private refreshCurrentUserInBackground(): void {
dedupe('users:current:bg', async () => {
const store = useUserManagerStore.getState();
const user = await fetchCurrentUserOrNull();
if (user) {
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setCurrentUserEntry(newEntry);
userCacheRepository.saveCurrent(user).catch(() => {});
userCacheRepository.save(user).catch(() => {});
}
return user;
}).catch((error) => {
console.error('[UserManager] refreshCurrentUserInBackground error:', error);
});
}
async getUserById(userId: string, forceRefresh = false): Promise<UserDTO | null> {
const store = useUserManagerStore.getState();
const entry = store.usersMap.get(userId);
if (!forceRefresh && entry && !isCacheExpired(entry)) {
return entry.data;
}
if (!forceRefresh && entry && isCacheExpired(entry)) {
this.refreshUserByIdInBackground(userId);
return entry.data;
}
if (!forceRefresh) {
const cachedFromDb = await userCacheRepository.get(userId);
if (cachedFromDb) {
const newEntry = createCacheEntry(cachedFromDb, DEFAULT_TTL.USER);
store.setUser(userId, cachedFromDb);
this.refreshUserByIdInBackground(userId);
return cachedFromDb;
}
}
return dedupe(`users:detail:${userId}`, async () => {
const store = useUserManagerStore.getState();
const user = await authService.fetchUserByIdFromAPI(userId);
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setUser(userId, user);
if (user) {
userCacheRepository.save(user).catch(() => {});
}
return user;
});
}
private refreshUserByIdInBackground(userId: string): void {
dedupe(`users:detail:bg:${userId}`, async () => {
const store = useUserManagerStore.getState();
const user = await authService.fetchUserByIdFromAPI(userId);
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setUser(userId, user);
if (user) {
userCacheRepository.save(user).catch(() => {});
}
return user;
}).catch((error) => {
console.error('[UserManager] refreshUserByIdInBackground error:', error);
});
}
invalidateUsers(): void {
useUserManagerStore.getState().invalidateAll();
}
}
export const userManager = new UserManager();
export { UserManager };
export default userManager;