Files
frontend/src/stores/message/MessageManager.ts

501 lines
16 KiB
TypeScript
Raw Normal View History

/**
* MessageManager -
*
*
* API
*/
import type { ConversationResponse, MessageResponse, MessageSegment, UserDTO } from '../../types/dto';
import { messageService } from '../../services/messageService';
import {
wsService,
WSChatMessage,
WSGroupChatMessage,
WSReadMessage,
WSGroupReadMessage,
WSRecallMessage,
WSGroupRecallMessage,
WSGroupTypingMessage,
WSGroupNoticeMessage,
GroupNoticeType,
} from '../../services/wsService';
import {
saveMessage,
saveMessagesBatch,
getMessagesByConversation,
getMaxSeq,
getMinSeq,
getMessagesBeforeSeq,
markConversationAsRead,
updateConversationCacheUnreadCount,
CachedMessage,
getUserCache,
saveUserCache,
updateMessageStatus,
deleteConversation as deleteConversationFromDb,
saveConversationsWithRelatedCache,
} from '../../services/database';
import { api } from '../../services/api';
import { vibrateOnMessage } from '../../services/messageVibrationService';
import { useAuthStore } from '../authStore';
import {
type IConversationListPagedSource,
SqliteConversationListPagedSource,
createRemoteConversationListSource,
CONVERSATION_LIST_PAGE_SIZE,
} from '../conversationListSources';
// 导入新模块
import { MessageStateManager } from './MessageStateManager';
import { MessageDeduplication } from './MessageDeduplication';
import { UserCacheService } from './UserCacheService';
import { ReadReceiptManager } from './ReadReceiptManager';
import { ConversationOperations } from './ConversationOperations';
import { MessageSendService } from './MessageSendService';
import { MessageSyncService } from './MessageSyncService';
import { WSMessageHandler } from './WSMessageHandler';
// 重新导出类型,保持兼容性
export type {
MessageEventType,
MessageEvent,
MessageSubscriber,
MessageManagerState,
ReadStateRecord,
MessageManagerConversationListDeps,
} from './types';
// ==================== MessageManager 类 ====================
class MessageManager {
// 子模块
private stateManager: MessageStateManager;
private deduplication: MessageDeduplication;
private userCacheService: UserCacheService;
private readReceiptManager: ReadReceiptManager;
private conversationOps: ConversationOperations;
private sendService: MessageSendService;
private syncService: MessageSyncService;
private wsHandler: WSMessageHandler;
// 本地状态
private currentUserId: string | null = null;
private authUnsubscribe: (() => void) | null = null;
private initializePromise: Promise<void> | null = null;
private activatingConversationTasks: Map<string, Promise<void>> = new Map();
constructor(deps?: import('./types').MessageManagerConversationListDeps) {
// 初始化子模块
this.stateManager = new MessageStateManager();
this.deduplication = new MessageDeduplication();
this.userCacheService = new UserCacheService();
this.readReceiptManager = new ReadReceiptManager(this.stateManager);
this.conversationOps = new ConversationOperations(this.stateManager);
this.sendService = new MessageSendService(this.stateManager, () => this.getCurrentUserId());
this.syncService = new MessageSyncService(
this.stateManager,
() => this.getCurrentUserId(),
deps
);
this.wsHandler = new WSMessageHandler(
this.stateManager,
this.deduplication,
this.userCacheService,
() => this.getCurrentUserId(),
{
markAsRead: (id, seq) => this.markAsRead(id, seq),
fetchConversations: (force, source) => this.fetchConversations(force, source),
fetchUnreadCount: () => this.fetchUnreadCount(),
fetchMessages: (id) => this.fetchMessages(id),
}
);
// 监听认证状态变化
this.initAuthListener();
}
// ==================== 私有工具方法 ====================
private initAuthListener() {
const authState = useAuthStore.getState();
this.currentUserId = authState.currentUser?.id || null;
if (this.currentUserId && !this.stateManager.getState().isInitialized) {
this.initialize();
}
if (!this.authUnsubscribe) {
this.authUnsubscribe = useAuthStore.subscribe((state) => {
const nextUserId = state.currentUser?.id || null;
const prevUserId = this.currentUserId;
this.currentUserId = nextUserId;
if (nextUserId && !this.stateManager.getState().isInitialized) {
this.initialize();
}
if (nextUserId && this.stateManager.getActiveConversation()) {
this.activateConversation(this.stateManager.getActiveConversation()!, { forceSync: true }).catch(error => {
console.error('[MessageManager] 登录态就绪后重激活会话失败:', error);
});
}
if (!nextUserId && prevUserId && this.stateManager.getState().isInitialized) {
this.destroy();
}
});
}
}
private getCurrentUserId(): string | null {
if (!this.currentUserId) {
this.currentUserId = useAuthStore.getState().currentUser?.id || null;
}
return this.currentUserId;
}
private normalizeConversationId(conversationId: string | number | null | undefined): string {
return conversationId == null ? '' : String(conversationId);
}
// ==================== 初始化与销毁 ====================
async initialize(): Promise<void> {
if (this.stateManager.getState().isInitialized) {
return;
}
if (this.initializePromise) {
await this.initializePromise;
return;
}
this.initializePromise = (async () => {
try {
this.currentUserId = this.getCurrentUserId();
if (!this.currentUserId) {
console.warn('[MessageManager] 用户未登录,延迟初始化');
return;
}
this.wsHandler.setBootstrapping(true);
// 初始化SSE监听
this.wsHandler.connect();
// 加载会话列表
await this.fetchConversations(false, 'initialize');
// 加载未读数
await this.fetchUnreadCount();
this.stateManager.setInitialized(true);
// 处理缓冲的 SSE 事件
await this.wsHandler.flushBufferedSSEEvents();
this.wsHandler.setBootstrapping(false);
this.stateManager.notifySubscribers({
type: 'conversations_updated',
payload: { conversations: this.stateManager.getConversations() },
timestamp: Date.now(),
});
if (this.stateManager.getActiveConversation()) {
await this.fetchMessages(this.stateManager.getActiveConversation()!);
}
} catch (error) {
console.error('[MessageManager] 初始化失败:', error);
this.wsHandler.setBootstrapping(false);
this.stateManager.notifySubscribers({
type: 'error',
payload: { error, context: 'initialize' },
timestamp: Date.now(),
});
}
})();
try {
await this.initializePromise;
} finally {
this.initializePromise = null;
}
}
destroy(): void {
this.wsHandler.disconnect();
this.stateManager.reset();
this.syncService.restartSources();
this.readReceiptManager.reset();
this.deduplication.reset();
this.userCacheService.reset();
this.activatingConversationTasks.clear();
this.initializePromise = null;
}
// ==================== 数据获取方法(代理到 syncService====================
async fetchConversations(forceRefresh = false, source: string = 'unknown'): Promise<void> {
return this.syncService.fetchConversations(forceRefresh, source);
}
async loadMoreConversations(): Promise<void> {
return this.syncService.loadMoreConversations();
}
async fetchConversationDetail(conversationId: string): Promise<ConversationResponse | null> {
return this.syncService.fetchConversationDetail(conversationId);
}
async fetchMessages(conversationId: string, afterSeq?: number): Promise<void> {
return this.syncService.fetchMessages(conversationId, afterSeq);
}
async loadMoreMessages(conversationId: string, beforeSeq: number, limit = 20): Promise<MessageResponse[]> {
return this.syncService.loadMoreMessages(conversationId, beforeSeq, limit);
}
async fetchUnreadCount(): Promise<void> {
return this.syncService.fetchUnreadCount();
}
// ==================== 数据操作方法(代理到各服务)====================
async sendMessage(
conversationId: string,
segments: MessageSegment[],
options?: { replyToId?: string }
): Promise<MessageResponse | null> {
return this.sendService.sendMessage(conversationId, segments, options);
}
async markAsRead(conversationId: string, seq: number): Promise<void> {
return this.readReceiptManager.markAsRead(conversationId, seq);
}
async markAllAsRead(): Promise<void> {
return this.readReceiptManager.markAllAsRead();
}
async createConversation(userId: string): Promise<ConversationResponse | null> {
return this.conversationOps.createConversation(userId);
}
updateConversation(conversationId: string, updates: Partial<ConversationResponse>): void {
return this.conversationOps.updateConversation(conversationId, updates);
}
removeConversation(conversationId: string): void {
return this.conversationOps.removeConversation(conversationId);
}
clearConversations(): void {
return this.conversationOps.clearConversations();
}
updateUnreadCount(conversationId: string, count: number): void {
return this.conversationOps.updateUnreadCount(conversationId, count);
}
setSystemUnreadCount(count: number): void {
return this.conversationOps.setSystemUnreadCount(count);
}
incrementSystemUnreadCount(): void {
return this.conversationOps.incrementSystemUnreadCount();
}
decrementSystemUnreadCount(count?: number): void {
return this.conversationOps.decrementSystemUnreadCount(count);
}
// ==================== 状态查询方法(代理到 stateManager====================
getConversations(): ConversationResponse[] {
return this.stateManager.getConversations();
}
getConversation(conversationId: string): ConversationResponse | null {
return this.stateManager.getConversation(conversationId);
}
getMessages(conversationId: string): MessageResponse[] {
return this.stateManager.getMessages(conversationId);
}
getUnreadCount(): { total: number; system: number } {
return this.stateManager.getUnreadCount();
}
isConnected(): boolean {
return this.stateManager.isConnected();
}
isLoading(): boolean {
return this.stateManager.isLoading();
}
isLoadingMessages(conversationId: string): boolean {
return this.stateManager.isLoadingMessages(conversationId);
}
getTypingUsers(groupId: string): string[] {
return this.stateManager.getTypingUsers(groupId);
}
isMuted(groupId: string): boolean {
return this.stateManager.isMuted(groupId);
}
setMutedStatus(groupId: string, isMuted: boolean): void {
return this.stateManager.setMutedStatus(groupId, isMuted);
}
// ==================== 活动会话管理 ====================
setActiveConversation(conversationId: string | null): void {
const normalizedId = conversationId ? this.normalizeConversationId(conversationId) : null;
this.stateManager.setCurrentConversation(normalizedId);
}
getActiveConversation(): string | null {
return this.stateManager.getActiveConversation();
}
async activateConversation(conversationId: string, options?: { forceSync?: boolean }): Promise<void> {
const normalizedId = this.normalizeConversationId(conversationId);
if (!normalizedId) return;
await this.initialize();
this.setActiveConversation(normalizedId);
// 先下发当前内存快照
const snapshot = this.getMessages(normalizedId);
this.stateManager.notifySubscribers({
type: 'messages_updated',
payload: {
conversationId: normalizedId,
messages: [...snapshot],
source: 'activation_snapshot',
},
timestamp: Date.now(),
});
const existingTask = this.activatingConversationTasks.get(normalizedId);
if (existingTask && !options?.forceSync) {
await existingTask;
return;
}
const task = (async () => {
await this.fetchMessages(normalizedId);
})();
this.activatingConversationTasks.set(normalizedId, task);
try {
await task;
} finally {
if (this.activatingConversationTasks.get(normalizedId) === task) {
this.activatingConversationTasks.delete(normalizedId);
}
}
}
// ==================== 订阅机制 ====================
subscribe(subscriber: import('./types').MessageSubscriber): () => void {
const unsubscribe = this.stateManager.subscribe(subscriber);
// 立即发送当前状态给新订阅者(与原始版本保持一致)
subscriber({
type: 'conversations_updated',
payload: { conversations: this.stateManager.getConversations() },
timestamp: Date.now(),
});
const unreadCount = this.stateManager.getUnreadCount();
subscriber({
type: 'unread_count_updated',
payload: {
totalUnreadCount: unreadCount.total,
systemUnreadCount: unreadCount.system,
},
timestamp: Date.now(),
});
subscriber({
type: 'connection_changed',
payload: { connected: this.stateManager.isConnected() },
timestamp: Date.now(),
});
// 如果当前有活动会话,立即发送该会话的消息给新订阅者
const activeConversationId = this.stateManager.getActiveConversation();
if (activeConversationId) {
const currentMessages = this.stateManager.getMessages(activeConversationId);
if (currentMessages.length > 0) {
subscriber({
type: 'messages_updated',
payload: {
conversationId: activeConversationId,
messages: [...currentMessages],
source: 'initial_sync',
},
timestamp: Date.now(),
});
}
}
return unsubscribe;
}
subscribeToMessages(conversationId: string, callback: (messages: MessageResponse[]) => void): () => void {
return this.stateManager.subscribe((event) => {
if (event.type === 'messages_updated' && event.payload.conversationId === conversationId) {
callback(event.payload.messages);
}
});
}
// ==================== 其他方法 ====================
canLoadMoreConversations(): boolean {
return this.syncService.canLoadMoreConversations();
}
invalidateCache(_type?: 'all' | 'list' | 'unread'): void {
// 兼容性方法,新架构不需要外部缓存
}
async requestConversationListRefresh(
source: string,
options?: { force?: boolean; allowDefer?: boolean }
): Promise<void> {
const forceRefresh = options?.force ?? true;
const allowDefer = options?.allowDefer ?? true;
if (allowDefer && this.shouldDeferConversationRefresh(source, forceRefresh)) {
// 延迟刷新逻辑在 syncService 中处理
return;
}
await this.fetchConversations(forceRefresh, source);
}
private shouldDeferConversationRefresh(source: string, forceRefresh: boolean): boolean {
if (!this.stateManager.getActiveConversation()) return false;
if (!forceRefresh) return false;
return source === 'sse-reconnect' || source === 'prefetch' || source === 'hooks-initial-refresh';
}
}
// ==================== 单例导出 ====================
export const messageManager = new MessageManager();
// 导出类以支持类型检查和测试
export { MessageManager };
export default messageManager;