251 lines
7.4 KiB
TypeScript
251 lines
7.4 KiB
TypeScript
|
|
/**
|
|||
|
|
* MessageManager - 重构版
|
|||
|
|
* 作为协调者,整合各个专注的服务模块
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { messageStateManager, MessageStateManager } from './message/MessageStateManager';
|
|||
|
|
import { webSocketMessageHandler, WebSocketMessageHandler } from './message/WebSocketMessageHandler';
|
|||
|
|
import { messageSyncService, MessageSyncService } from './message/MessageSyncService';
|
|||
|
|
import { readReceiptManager, ReadReceiptManager } from './message/ReadReceiptManager';
|
|||
|
|
import { messageRepository } from '../data/repositories/MessageRepository';
|
|||
|
|
import { useAuthStore } from './authStore';
|
|||
|
|
import type { Message, Conversation } from '../core/entities/Message';
|
|||
|
|
import type { ConversationResponse, MessageResponse } from '../types/dto';
|
|||
|
|
|
|||
|
|
export type {
|
|||
|
|
MessageEventType,
|
|||
|
|
MessageEvent,
|
|||
|
|
MessageSubscriber,
|
|||
|
|
} from './message/MessageStateManager';
|
|||
|
|
|
|||
|
|
export class MessageManager {
|
|||
|
|
private stateManager: MessageStateManager;
|
|||
|
|
private wsHandler: WebSocketMessageHandler;
|
|||
|
|
private syncService: MessageSyncService;
|
|||
|
|
private readManager: ReadReceiptManager;
|
|||
|
|
|
|||
|
|
private initialized: boolean = false;
|
|||
|
|
private authUnsubscribe: (() => void) | null = null;
|
|||
|
|
private wsUnsubscribe: (() => void) | null = null;
|
|||
|
|
private currentUserId: string | null = null;
|
|||
|
|
|
|||
|
|
constructor() {
|
|||
|
|
this.stateManager = messageStateManager;
|
|||
|
|
this.wsHandler = webSocketMessageHandler;
|
|||
|
|
this.syncService = messageSyncService;
|
|||
|
|
this.readManager = readReceiptManager;
|
|||
|
|
|
|||
|
|
this.readManager.setStateManager(this.stateManager);
|
|||
|
|
this.setupWebSocketHandlers();
|
|||
|
|
this.initAuthListener();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private initAuthListener(): void {
|
|||
|
|
const authState = useAuthStore.getState();
|
|||
|
|
this.currentUserId = authState.currentUser?.id || null;
|
|||
|
|
|
|||
|
|
if (this.currentUserId && !this.initialized) {
|
|||
|
|
this.initialize();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.authUnsubscribe = useAuthStore.subscribe((state) => {
|
|||
|
|
const nextUserId = state.currentUser?.id || null;
|
|||
|
|
this.currentUserId = nextUserId;
|
|||
|
|
|
|||
|
|
if (nextUserId && !this.initialized) {
|
|||
|
|
this.initialize();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private setupWebSocketHandlers(): void {
|
|||
|
|
this.wsUnsubscribe = this.wsHandler.subscribe((event) => {
|
|||
|
|
switch (event.type) {
|
|||
|
|
case 'chat_message':
|
|||
|
|
case 'group_message':
|
|||
|
|
this.handleNewMessage(event.payload);
|
|||
|
|
break;
|
|||
|
|
case 'read_receipt':
|
|||
|
|
case 'group_read_receipt':
|
|||
|
|
this.handleReadReceipt(event.payload);
|
|||
|
|
break;
|
|||
|
|
case 'message_recalled':
|
|||
|
|
case 'group_message_recalled':
|
|||
|
|
this.handleMessageRecall(event.payload);
|
|||
|
|
break;
|
|||
|
|
case 'typing':
|
|||
|
|
this.handleTypingStatus(event.payload);
|
|||
|
|
break;
|
|||
|
|
case 'group_notice':
|
|||
|
|
this.handleGroupNotice(event.payload);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async initialize(): Promise<void> {
|
|||
|
|
if (this.initialized) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
console.log('[MessageManager] Initializing...');
|
|||
|
|
|
|||
|
|
this.wsHandler.connect();
|
|||
|
|
|
|||
|
|
const conversations = await this.syncService.syncConversations();
|
|||
|
|
this.stateManager.setConversations(conversations);
|
|||
|
|
|
|||
|
|
this.initialized = true;
|
|||
|
|
console.log('[MessageManager] Initialized successfully');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[MessageManager] Initialization failed:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async activateConversation(conversationId: string): Promise<void> {
|
|||
|
|
this.stateManager.setCurrentConversation(conversationId);
|
|||
|
|
|
|||
|
|
const existing = this.stateManager.getMessages(conversationId);
|
|||
|
|
if (existing.length === 0) {
|
|||
|
|
await this.loadMessages(conversationId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const conversation = this.stateManager.getConversation(conversationId);
|
|||
|
|
if (conversation && conversation.unreadCount > 0) {
|
|||
|
|
await this.readManager.markAsRead(conversationId, conversation.lastSeq);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
deactivateConversation(): void {
|
|||
|
|
this.stateManager.setCurrentConversation(null);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async loadMessages(conversationId: string): Promise<Message[]> {
|
|||
|
|
const localMessages = await this.syncService.loadLocalMessages(conversationId);
|
|||
|
|
|
|||
|
|
if (localMessages.length > 0) {
|
|||
|
|
this.stateManager.setMessages(conversationId, localMessages);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const syncResult = await this.syncService.syncMessages(conversationId);
|
|||
|
|
|
|||
|
|
if (syncResult.messages.length > 0) {
|
|||
|
|
this.stateManager.appendMessages(conversationId, syncResult.messages);
|
|||
|
|
return [...localMessages, ...syncResult.messages];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return localMessages;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async loadHistory(conversationId: string): Promise<Message[]> {
|
|||
|
|
const messages = this.stateManager.getMessages(conversationId);
|
|||
|
|
const oldestSeq = messages.length > 0 ? Math.min(...messages.map(m => m.seq)) : 0;
|
|||
|
|
|
|||
|
|
const history = await this.syncService.loadHistoryMessages(
|
|||
|
|
conversationId,
|
|||
|
|
oldestSeq,
|
|||
|
|
20
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (history.length > 0) {
|
|||
|
|
this.stateManager.prependMessages(conversationId, history);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return history;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async sendMessage(
|
|||
|
|
conversationId: string,
|
|||
|
|
content: string,
|
|||
|
|
type: 'private' | 'group' = 'private'
|
|||
|
|
): Promise<Message> {
|
|||
|
|
throw new Error('sendMessage not implemented - use existing implementation');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async handleNewMessage(message: Message): Promise<void> {
|
|||
|
|
await messageRepository.saveMessage(message, true);
|
|||
|
|
|
|||
|
|
this.stateManager.addMessage(message.conversationId, message);
|
|||
|
|
|
|||
|
|
const currentConvId = this.stateManager.getCurrentConversationId();
|
|||
|
|
if (currentConvId !== message.conversationId) {
|
|||
|
|
const conv = this.stateManager.getConversation(message.conversationId);
|
|||
|
|
if (conv) {
|
|||
|
|
this.stateManager.updateConversation(message.conversationId, {
|
|||
|
|
unreadCount: (conv.unreadCount || 0) + 1,
|
|||
|
|
lastSeq: message.seq,
|
|||
|
|
lastMessageAt: message.createdAt,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private handleReadReceipt(payload: any): void {
|
|||
|
|
const { conversationId, lastReadSeq } = payload;
|
|||
|
|
|
|||
|
|
this.stateManager.updateConversation(conversationId, {
|
|||
|
|
unreadCount: 0,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async handleMessageRecall(payload: any): Promise<void> {
|
|||
|
|
const { messageId, conversationId } = payload;
|
|||
|
|
|
|||
|
|
await messageRepository.updateMessageStatus(messageId, 'recalled', true);
|
|||
|
|
|
|||
|
|
this.stateManager.updateMessage(conversationId, messageId, {
|
|||
|
|
status: 'recalled',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private handleTypingStatus(payload: any): void {
|
|||
|
|
const { groupId, userIds } = payload;
|
|||
|
|
this.stateManager.setTypingUsers(groupId, userIds);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private handleGroupNotice(notice: any): void {
|
|||
|
|
console.log('[MessageManager] Group notice:', notice);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
subscribe(callback: any): () => void {
|
|||
|
|
return this.stateManager.subscribe(callback);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getConversations(): Conversation[] {
|
|||
|
|
return this.stateManager.getConversations();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getMessages(conversationId: string): Message[] {
|
|||
|
|
return this.stateManager.getMessages(conversationId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getUnreadCount(): { total: number; system: number } {
|
|||
|
|
return this.stateManager.getUnreadCount();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
isConnected(): boolean {
|
|||
|
|
return this.wsHandler.isConnected();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
reset(): void {
|
|||
|
|
this.initialized = false;
|
|||
|
|
this.stateManager.reset();
|
|||
|
|
this.readManager.reset();
|
|||
|
|
this.wsHandler.disconnect();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
destroy(): void {
|
|||
|
|
if (this.authUnsubscribe) {
|
|||
|
|
this.authUnsubscribe();
|
|||
|
|
this.authUnsubscribe = null;
|
|||
|
|
}
|
|||
|
|
if (this.wsUnsubscribe) {
|
|||
|
|
this.wsUnsubscribe();
|
|||
|
|
this.wsUnsubscribe = null;
|
|||
|
|
}
|
|||
|
|
this.reset();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const messageManager = new MessageManager();
|
|||
|
|
export default messageManager;
|