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
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.
251 lines
7.3 KiB
TypeScript
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; |