refactor(core): introduce EventBus and refactor store infrastructure
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m46s
Frontend CI / ota-android (push) Successful in 10m34s
Frontend CI / build-android-apk (push) Successful in 1h15m8s

- Add EventBus for decoupled event-driven communication between services
- Add EventSubscriber component for centralized event handling
- Add requestDedupe utility for shared request deduplication
- Refactor api/wsService to use eventBus instead of direct router navigation
- Extract MessageMapper.toCachedMessages for consistent message mapping
- Remove deprecated BaseManager and CacheBus classes
- Improve @mention rendering with memberMap support in segment rendering
- Update hooks to use useRef instead of useState for fetch tracking
This commit is contained in:
lafay
2026-04-12 18:14:29 +08:00
parent 4b5ce1ba21
commit 6610d2f173
28 changed files with 378 additions and 512 deletions

View File

@@ -6,11 +6,10 @@
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import { CommonActions } from '@react-navigation/native';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
import { hrefVerificationGuide } from '../navigation/hrefs';
import { eventBus } from '@/core/events/EventBus';
// 生产地址 https://bbs.littlelan.cn
const getBaseUrl = () => {
@@ -74,37 +73,27 @@ interface JwtPayload {
// API 客户端类
class ApiClient {
private baseUrl: string;
private navigation: any = null;
private expoRouter: any = null;
private verificationModalShown = false;
// 刷新锁:防止并发刷新
private refreshLock: Promise<boolean> | null = null;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
setExpoRouter(router: any) {
this.expoRouter = router;
}
private handleVerificationRequired() {
if (this.verificationModalShown) return;
this.verificationModalShown = true;
if (this.expoRouter) {
this.expoRouter.push(hrefVerificationGuide());
}
eventBus.emit({ type: 'SHOW_VERIFICATION_MODAL' });
setTimeout(() => {
this.verificationModalShown = false;
}, 3000);
}
// 设置导航对象用于处理401跳转
setNavigation(navigation: any) {
this.navigation = navigation;
private navigateToLogin() {
eventBus.emit({ type: 'NAVIGATE', payload: { path: '/(auth)/login' } });
eventBus.emit({ type: 'AUTH_LOGOUT' });
}
// 解析 JWT token 获取 payload
private parseJwt(token: string): JwtPayload | null {
try {
const parts = token.split('.');
@@ -208,19 +197,10 @@ class ApiClient {
if (this.isTokenExpiringSoon(token)) {
const refreshed = await this.refreshToken();
if (!refreshed) {
// 刷新失败,清除 token 并跳转登录
await this.clearToken();
if (this.navigation) {
this.navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'Login' }],
})
);
}
this.navigateToLogin();
throw new ApiError(401, '登录已过期,请重新登录');
}
// 刷新成功,使用新 token
const newToken = await this.getToken();
if (newToken) {
headers['Authorization'] = `Bearer ${newToken}`;
@@ -246,23 +226,13 @@ class ApiClient {
// 处理 401 未授权
if (response.status === 401) {
// 尝试刷新 token
const refreshed = await this.refreshToken();
if (!refreshed) {
// 刷新失败,清除 token 并跳转登录
await this.clearToken();
if (this.navigation) {
this.navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'Login' }],
})
);
}
this.navigateToLogin();
throw new ApiError(401, '登录已过期,请重新登录');
}
// 刷新成功后重试请求
return this.request(method, path, params, body);
}

View File

@@ -38,8 +38,8 @@ class WebRTCManager {
// ICE restart 相关状态
private reconnectAttempts = 0;
private readonly MAX_RECONNECT_ATTEMPTS = 3;
private reconnectTimer: NodeJS.Timeout | null = null;
private disconnectTimer: NodeJS.Timeout | null = null;
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
private disconnectTimer: ReturnType<typeof setTimeout> | null = null;
async initialize(iceServers: ICEServer[] = []): Promise<void> {
if (this.peerConnection) {

View File

@@ -1,7 +1,7 @@
import { AppState, AppStateStatus } from 'react-native';
import { api, WS_URL } from './api';
import { hrefVerificationGuide } from '../navigation/hrefs';
import { eventBus } from '@/core/events/EventBus';
import { MessageCategory, SystemMessageType, SystemMessageExtraData, MessageSegment } from '../types/dto';
import { systemNotificationService } from './systemNotificationService';
@@ -300,8 +300,8 @@ class WebSocketService {
private reconnectAttempts = 0;
private maxReconnectAttempts = 20;
private reconnectDelay = 3000;
private reconnectTimer: NodeJS.Timeout | null = null;
private heartbeatTimer: NodeJS.Timeout | null = null;
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
private heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
private heartbeatInterval = 30000; // 30秒心跳
private messageHandlers: Map<WSMessageType, MessageHandler[]> = new Map();
private connectionHandlers: ConnectionHandler[] = [];
@@ -318,7 +318,7 @@ class WebSocketService {
// 降级状态
private isFallbackMode = false;
private fallbackCheckTimer: NodeJS.Timeout | null = null;
private fallbackCheckTimer: ReturnType<typeof setTimeout> | null = null;
private getWSUrl(token: string | null): string {
return `${WS_URL}?token=${encodeURIComponent(token || '')}`;
@@ -1122,20 +1122,12 @@ class WebSocketService {
private handleVerificationRequired(): void {
if (this.verificationModalShown) return;
this.verificationModalShown = true;
if (this.routerRef) {
this.routerRef.push(hrefVerificationGuide());
}
eventBus.emit({ type: 'SHOW_VERIFICATION_MODAL' });
setTimeout(() => {
this.verificationModalShown = false;
}, 3000);
}
private routerRef: any = null;
setRouter(router: any): void {
this.routerRef = router;
}
private startHeartbeat(): void {
this.stopHeartbeat();
this.heartbeatTimer = setInterval(() => {