/** * 消息状态管理器 * 只负责管理状态,不包含业务逻辑 */ import type { Message, Conversation, UnreadCount } from '../../core/entities/Message'; export type MessageEventType = | 'conversations_updated' | 'messages_updated' | 'unread_count_updated' | 'connection_changed' | 'message_sent' | 'message_read' | 'message_received' | 'message_recalled' | 'typing_status' | 'group_notice' | 'error'; export interface MessageEvent { type: MessageEventType; payload: any; timestamp: number; } export type MessageSubscriber = (event: MessageEvent) => void; export interface MessageState { conversations: Map; conversationList: Conversation[]; messagesMap: Map; unreadCount: UnreadCount; isWebSocketConnected: boolean; currentConversationId: string | null; isLoading: boolean; typingUsersMap: Map; mutedStatusMap: Map; } export class MessageStateManager { private state: MessageState; private subscribers: Set; private readStateVersion: number = 0; private pendingReadMap: Map = new Map(); constructor() { this.state = { conversations: new Map(), conversationList: [], messagesMap: new Map(), unreadCount: { total: 0, system: 0 }, isWebSocketConnected: false, currentConversationId: null, isLoading: false, typingUsersMap: new Map(), mutedStatusMap: new Map(), }; this.subscribers = new Set(); } // ==================== 状态获取 ==================== getState(): MessageState { return this.state; } getConversations(): Conversation[] { return this.state.conversationList; } getConversation(id: string): Conversation | undefined { return this.state.conversations.get(id); } getMessages(conversationId: string): Message[] { return this.state.messagesMap.get(conversationId) || []; } getUnreadCount(): UnreadCount { return this.state.unreadCount; } getCurrentConversationId(): string | null { return this.state.currentConversationId; } isConnected(): boolean { return this.state.isWebSocketConnected; } getTypingUsers(groupId: string): string[] { return this.state.typingUsersMap.get(groupId) || []; } getMutedStatus(groupId: string): boolean { return this.state.mutedStatusMap.get(groupId) || false; } // ==================== 状态更新 ==================== setConversations(conversations: Conversation[]): void { this.state.conversations.clear(); this.state.conversationList = conversations; for (const conv of conversations) { this.state.conversations.set(conv.id, conv); } this.updateUnreadCount(); this.notify({ type: 'conversations_updated', payload: conversations }); } addConversation(conversation: Conversation): void { this.state.conversations.set(conversation.id, conversation); this.state.conversationList = Array.from(this.state.conversations.values()); this.updateUnreadCount(); this.notify({ type: 'conversations_updated', payload: this.state.conversationList }); } updateConversation(id: string, updates: Partial): void { const existing = this.state.conversations.get(id); if (existing) { const updated = { ...existing, ...updates }; this.state.conversations.set(id, updated); this.state.conversationList = Array.from(this.state.conversations.values()); this.updateUnreadCount(); this.notify({ type: 'conversations_updated', payload: this.state.conversationList }); } } removeConversation(id: string): void { this.state.conversations.delete(id); this.state.messagesMap.delete(id); this.state.conversationList = Array.from(this.state.conversations.values()); this.updateUnreadCount(); this.notify({ type: 'conversations_updated', payload: this.state.conversationList }); } setMessages(conversationId: string, messages: Message[]): void { this.state.messagesMap.set(conversationId, messages); this.notify({ type: 'messages_updated', payload: { conversationId, messages } }); } addMessage(conversationId: string, message: Message): void { const messages = this.state.messagesMap.get(conversationId) || []; const existingIndex = messages.findIndex(m => m.id === message.id); if (existingIndex >= 0) { messages[existingIndex] = message; } else { messages.push(message); } this.state.messagesMap.set(conversationId, messages); this.notify({ type: 'messages_updated', payload: { conversationId, messages } }); } prependMessages(conversationId: string, newMessages: Message[]): void { const existing = this.state.messagesMap.get(conversationId) || []; const merged = [...newMessages, ...existing]; const unique = this.deduplicateMessages(merged); this.state.messagesMap.set(conversationId, unique); this.notify({ type: 'messages_updated', payload: { conversationId, messages: unique } }); } appendMessages(conversationId: string, newMessages: Message[]): void { const existing = this.state.messagesMap.get(conversationId) || []; const merged = [...existing, ...newMessages]; const unique = this.deduplicateMessages(merged); this.state.messagesMap.set(conversationId, unique); this.notify({ type: 'messages_updated', payload: { conversationId, messages: unique } }); } updateMessage(conversationId: string, messageId: string, updates: Partial): void { const messages = this.state.messagesMap.get(conversationId); if (!messages) return; const index = messages.findIndex(m => m.id === messageId); if (index >= 0) { messages[index] = { ...messages[index], ...updates }; this.notify({ type: 'messages_updated', payload: { conversationId, messages } }); } } setUnreadCount(count: UnreadCount): void { this.state.unreadCount = count; this.notify({ type: 'unread_count_updated', payload: count }); } setWebSocketConnected(connected: boolean): void { this.state.isWebSocketConnected = connected; this.notify({ type: 'connection_changed', payload: connected }); } setCurrentConversation(id: string | null): void { this.state.currentConversationId = id; } setLoading(loading: boolean): void { this.state.isLoading = loading; } setTypingUsers(groupId: string, userIds: string[]): void { this.state.typingUsersMap.set(groupId, userIds); this.notify({ type: 'typing_status', payload: { groupId, userIds } }); } setMutedStatus(groupId: string, muted: boolean): void { this.state.mutedStatusMap.set(groupId, muted); } // ==================== 已读状态保护 ==================== beginReadOperation(conversationId: string): number { this.readStateVersion++; this.pendingReadMap.set(conversationId, { timestamp: Date.now(), version: this.readStateVersion, }); return this.readStateVersion; } endReadOperation(conversationId: string): void { setTimeout(() => { this.pendingReadMap.delete(conversationId); }, 5000); } isReadOperationPending(conversationId: string): boolean { const record = this.pendingReadMap.get(conversationId); if (!record) return false; return Date.now() - record.timestamp < 5000; } getReadStateVersion(): number { return this.readStateVersion; } // ==================== 订阅机制 ==================== subscribe(callback: MessageSubscriber): () => void { this.subscribers.add(callback); return () => this.subscribers.delete(callback); } private notify(event: Omit): void { const fullEvent: MessageEvent = { ...event, timestamp: Date.now(), }; this.subscribers.forEach(callback => { try { callback(fullEvent); } catch (error) { console.error('[MessageStateManager] Subscriber error:', error); } }); } // ==================== 工具方法 ==================== private updateUnreadCount(): void { let total = 0; let system = 0; for (const conv of this.state.conversations.values()) { total += conv.unreadCount || 0; } this.state.unreadCount = { total, system }; this.notify({ type: 'unread_count_updated', payload: this.state.unreadCount }); } private deduplicateMessages(messages: Message[]): Message[] { const seen = new Set(); return messages.filter(msg => { if (seen.has(msg.id)) return false; seen.add(msg.id); return true; }); } reset(): void { this.state = { conversations: new Map(), conversationList: [], messagesMap: new Map(), unreadCount: { total: 0, system: 0 }, isWebSocketConnected: false, currentConversationId: null, isLoading: false, typingUsersMap: new Map(), mutedStatusMap: new Map(), }; this.pendingReadMap.clear(); this.readStateVersion = 0; } } export const messageStateManager = new MessageStateManager();