Files
frontend/src/stores/message/MessageManager.ts
lan a91637466c
Some checks failed
Frontend CI / build-android-apk (push) Has been cancelled
Frontend CI / build-and-push-web (push) Has been cancelled
Frontend CI / ota-android (push) Has been cancelled
Frontend CI / ota-android (pull_request) Has been skipped
Frontend CI / build-and-push-web (pull_request) Has been cancelled
Frontend CI / build-android-apk (pull_request) Has been cancelled
refactor(messaging): replace WebSocket with SSE for real-time message handling
Replace WebSocket-based real-time communication with Server-Sent Events (SSE) across the messaging infrastructure. This includes:

- Create new SSEClient datasource to manage SSE connections
- Create new SSEMessageHandler to process SSE events
- Update ProcessMessageUseCase to use SSEClient instead of WebSocketClient
- Update MessageManager and MessageStateManager to work with SSE handlers
- Rename connection state variables from `isWebSocketConnected` to `isSSEConnected`
- Update type definitions in dto.ts (WSEventType → SSEEventType, etc.)
- Delete obsolete WebSocketClient and WebSocketMessageHandler files
- Update comments and documentation to reflect SSE terminology

This refactoring aligns with the backend's SSE implementation for better compatibility with React Native's networking capabilities.
2026-03-19 00:56:41 +08:00

251 lines
7.3 KiB
TypeScript

/**
* MessageManager - 重构版
* 作为协调者,整合各个专注的服务模块
*/
import { messageStateManager, MessageStateManager } from './MessageStateManager';
import { sseMessageHandler, SSEMessageHandler } from './SSEMessageHandler';
import { messageSyncService, MessageSyncService } from './MessageSyncService';
import { readReceiptManager, ReadReceiptManager } from './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 './MessageStateManager';
export class MessageManager {
private stateManager: MessageStateManager;
private sseHandler: SSEMessageHandler;
private syncService: MessageSyncService;
private readManager: ReadReceiptManager;
private initialized: boolean = false;
private authUnsubscribe: (() => void) | null = null;
private sseUnsubscribe: (() => void) | null = null;
private currentUserId: string | null = null;
constructor() {
this.stateManager = messageStateManager;
this.sseHandler = sseMessageHandler;
this.syncService = messageSyncService;
this.readManager = readReceiptManager;
this.readManager.setStateManager(this.stateManager);
this.setupSSEHandlers();
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 setupSSEHandlers(): void {
this.sseUnsubscribe = this.sseHandler.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.sseHandler.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.sseHandler.isConnected();
}
reset(): void {
this.initialized = false;
this.stateManager.reset();
this.readManager.reset();
this.sseHandler.disconnect();
}
destroy(): void {
if (this.authUnsubscribe) {
this.authUnsubscribe();
this.authUnsubscribe = null;
}
if (this.sseUnsubscribe) {
this.sseUnsubscribe();
this.sseUnsubscribe = null;
}
this.reset();
}
}
export const messageManager = new MessageManager();
export default messageManager;