Files
frontend/src/stores/authStore.ts
lafay b19a2ced6f
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 5m2s
Frontend CI / ota-android (push) Successful in 11m28s
Frontend CI / build-android-apk (push) Successful in 1h0m58s
feat(CallFeature): implement call functionality and integrate WebSocket signaling
- Updated app.json to include microphone permissions and background audio support.
- Added call-related dependencies in package.json and package-lock.json.
- Enhanced ChatScreen to initiate calls and handle call actions.
- Introduced call components in the layout for incoming call handling.
- Expanded WebSocket service to manage call signaling messages and events.
- Refactored authStore to initialize call state on user authentication.
2026-03-27 17:12:19 +08:00

312 lines
8.5 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.
/**
* 认证状态管理
*
* 链路设计:
* authService = 纯 HTTP 层Token 管理),不碰 SQLite
* authStore = 编排层负责API → DB初始化 → 写缓存 → 更新状态
*
* 冷启动问题解法:
* 将 userId 持久化到 AsyncStorage这样 DB 可在 API 调用前提前初始化,
* 彻底打破"需要用户ID才能初始化DB但用户ID来自API"的循环依赖。
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import { create } from 'zustand';
import { User } from '../types';
import { authService, resolveAuthApiError, LoginRequest, RegisterRequest } from '../services';
import { wsService } from '../services/wsService';
import { callStore } from './callStore';
import {
initDatabase,
closeDatabase,
saveUserCache,
saveCurrentUserCache,
clearCurrentUserCache,
} from '../services/database';
// AsyncStorage 中保存已登录用户 ID 的 key
const USER_ID_KEY = 'auth_user_id';
// ── userId 持久化辅助函数 ──
async function saveUserId(userId: string): Promise<void> {
try {
await AsyncStorage.setItem(USER_ID_KEY, userId);
} catch (err) {
console.warn('[AuthStore] 保存 userId 失败:', err);
}
}
async function loadUserId(): Promise<string | null> {
try {
return await AsyncStorage.getItem(USER_ID_KEY);
} catch {
return null;
}
}
async function clearUserId(): Promise<void> {
try {
await AsyncStorage.removeItem(USER_ID_KEY);
} catch {}
}
// ── 写用户缓存DB 已就绪后调用)──
async function cacheUser(user: User): Promise<void> {
try {
await saveUserCache(user);
await saveCurrentUserCache(user);
} catch (err) {
console.warn('[AuthStore] 写用户缓存失败:', err);
}
}
// ── 将 API 错误转换为用户可读的中文提示 ──
function resolveLoginError(error: any): string {
const code: number = error?.code ?? 0;
const msg: string = error?.message ?? '';
// 网络层错误(无法连接服务器)
if (
msg.includes('Network request failed') ||
msg.includes('网络请求失败') ||
msg.includes('Failed to fetch') ||
code === 500
) {
return '无法连接到服务器,请检查网络连接后重试';
}
// 账号或密码错误
if (code === 401 || msg.includes('密码') || msg.includes('用户名') || msg.includes('incorrect')) {
return '用户名或密码错误,请重新输入';
}
// 账号被禁用/封禁
if (code === 403 || msg.includes('禁止') || msg.includes('封禁') || msg.includes('forbidden')) {
return '账号已被禁用,请联系管理员';
}
// 服务端明确返回了 message直接用
if (msg && msg !== '登录失败') {
return msg;
}
return '登录失败,请稍后重试';
}
// ── 启动 SSE 实时服务 ──
async function startRealtime(): Promise<void> {
try {
await wsService.start();
callStore.getState().initCall();
} catch (error) {
console.error('[AuthStore] 启动 SSE 服务失败:', error);
}
}
interface AuthState {
isAuthenticated: boolean;
currentUser: User | null;
isLoading: boolean;
error: string | null;
login: (credentials: LoginRequest) => Promise<boolean>;
register: (data: RegisterRequest) => Promise<boolean>;
logout: () => Promise<void>;
updateUser: (user: Partial<User>) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
fetchCurrentUser: () => Promise<void>;
}
export const useAuthStore = create<AuthState>((set) => ({
isAuthenticated: false,
currentUser: null,
isLoading: false,
error: null,
// ── 登录 ──
// 流程API → 保存Token → 初始化DB → 持久化userId → 写缓存 → 设置状态
login: async (credentials: LoginRequest) => {
set({ isLoading: true, error: null });
try {
// 1. API 登录authService 只负责 HTTP + Token
const response = await authService.login(credentials);
const user = response.user;
if (!user?.id) {
throw new Error('登录响应中缺少用户信息');
}
const userId = String(user.id);
// 2. 初始化用户专属数据库
await initDatabase(userId);
// 3. 持久化 userId供下次冷启动提前初始化 DB 用)
await saveUserId(userId);
// 4. 写用户缓存DB 已就绪)
await cacheUser(user);
set({
isAuthenticated: true,
currentUser: user,
isLoading: false,
error: null,
});
// 5. 启动 SSE
await startRealtime();
return true;
} catch (error: any) {
set({
isLoading: false,
error: resolveLoginError(error),
});
return false;
}
},
// ── 注册 ──
register: async (data: RegisterRequest) => {
set({ isLoading: true, error: null });
try {
const response = await authService.register(data);
const user = response.user;
if (!user?.id) {
throw new Error('注册响应中缺少用户信息');
}
const userId = String(user.id);
await initDatabase(userId);
await saveUserId(userId);
await cacheUser(user);
set({
isAuthenticated: true,
currentUser: user,
isLoading: false,
error: null,
});
await startRealtime();
return true;
} catch (error: any) {
set({
isLoading: false,
error: resolveAuthApiError(error, '注册失败,请稍后重试'),
});
return false;
}
},
// ── 登出 ──
logout: async () => {
set({ isLoading: true });
try {
// 1. 通知服务端Token 清理在 authService 内部完成)
await authService.logout();
// 2. 停止 SSE
wsService.stop();
// 3. 清除 DB 中的用户缓存DB 此时一定已初始化)
await clearCurrentUserCache().catch(() => {});
// 4. 关闭数据库连接
await closeDatabase();
// 5. 清除持久化的 userId
await clearUserId();
} catch (error) {
console.error('[AuthStore] 登出失败:', error);
} finally {
set({
isAuthenticated: false,
currentUser: null,
isLoading: false,
error: null,
});
}
},
// ── 冷启动校验 ──
// 流程读取已保存的userId → 提前初始化DB → 纯API验证Token → 更新缓存 → 设置状态
fetchCurrentUser: async () => {
set({ isLoading: true });
try {
// 1. 从 AsyncStorage 读取上次登录保存的 userId
// 这是关键:让 DB 在 API 调用前就能初始化,打破循环依赖
const savedUserId = await loadUserId();
if (savedUserId) {
// 2. 用已知的 userId 提前初始化 DB
try {
await initDatabase(savedUserId);
} catch (dbErr) {
console.warn('[AuthStore] DB 预初始化失败:', dbErr);
}
}
// 3. 纯 API 调用验证 Token 有效性fetchCurrentUserFromAPI 完全不碰 DB
const user = await authService.fetchCurrentUserFromAPI();
if (user) {
const userId = String(user.id);
// 4. 若 userId 发生变化(极少数情况),重新初始化 DB 并更新持久化
if (!savedUserId || savedUserId !== userId) {
await initDatabase(userId);
await saveUserId(userId);
}
// 5. DB 已就绪,更新用户缓存
await cacheUser(user);
set({
isAuthenticated: true,
currentUser: user,
isLoading: false,
});
// 6. 启动 SSE
await startRealtime();
} else {
// Token 已失效或不存在
await clearUserId();
set({
isAuthenticated: false,
currentUser: null,
isLoading: false,
});
}
} catch (error) {
console.error('[AuthStore] fetchCurrentUser 异常:', error);
set({
isAuthenticated: false,
currentUser: null,
isLoading: false,
error: '获取用户信息失败',
});
}
},
updateUser: (userData: Partial<User>) =>
set((state) => ({
currentUser: state.currentUser ? { ...state.currentUser, ...userData } : null,
})),
setLoading: (loading: boolean) => set({ isLoading: loading }),
setError: (error: string | null) => set({ error }),
}));
// 导出 selector hooks 以优化性能
export const useCurrentUser = () => useAuthStore((state) => state.currentUser);
export const useIsAuthenticated = () => useAuthStore((state) => state.isAuthenticated);
export const useAuthLoading = () => useAuthStore((state) => state.isLoading);