162 lines
4.7 KiB
TypeScript
162 lines
4.7 KiB
TypeScript
|
|
/**
|
||
|
|
* 消息同步服务
|
||
|
|
* 负责从服务器同步消息和会话
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { messageService } from '../../services/messageService';
|
||
|
|
import { messageRepository } from '../../data/repositories/MessageRepository';
|
||
|
|
import type { Message, Conversation } from '../../core/entities/Message';
|
||
|
|
import type { ConversationResponse, MessageResponse } from '../../types/dto';
|
||
|
|
|
||
|
|
export interface SyncOptions {
|
||
|
|
force?: boolean;
|
||
|
|
lastSeq?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface SyncResult {
|
||
|
|
conversations: Conversation[];
|
||
|
|
messages: Message[];
|
||
|
|
hasMore: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export class MessageSyncService {
|
||
|
|
private syncingConversations: boolean = false;
|
||
|
|
private syncingMessages: Map<string, boolean> = new Map();
|
||
|
|
|
||
|
|
async syncConversations(): Promise<Conversation[]> {
|
||
|
|
if (this.syncingConversations) {
|
||
|
|
console.log('[MessageSyncService] Already syncing conversations');
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
this.syncingConversations = true;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await messageService.getConversations();
|
||
|
|
const conversations = this.mapConversations(response);
|
||
|
|
return conversations;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('[MessageSyncService] Failed to sync conversations:', error);
|
||
|
|
throw error;
|
||
|
|
} finally {
|
||
|
|
this.syncingConversations = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async syncMessages(
|
||
|
|
conversationId: string,
|
||
|
|
options: SyncOptions = {}
|
||
|
|
): Promise<SyncResult> {
|
||
|
|
const key = conversationId;
|
||
|
|
|
||
|
|
if (this.syncingMessages.get(key)) {
|
||
|
|
console.log('[MessageSyncService] Already syncing messages for:', conversationId);
|
||
|
|
return { conversations: [], messages: [], hasMore: false };
|
||
|
|
}
|
||
|
|
|
||
|
|
this.syncingMessages.set(key, true);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const lastSeq = options.lastSeq ?? await messageRepository.getMaxSeq(conversationId);
|
||
|
|
|
||
|
|
const response = await messageService.getMessages(conversationId, {
|
||
|
|
after_seq: lastSeq,
|
||
|
|
limit: 50,
|
||
|
|
});
|
||
|
|
|
||
|
|
const messages = this.mapMessages(response.messages || []);
|
||
|
|
|
||
|
|
if (messages.length > 0) {
|
||
|
|
await messageRepository.saveMessages(messages, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
conversations: [],
|
||
|
|
messages,
|
||
|
|
hasMore: response.has_more || false,
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error('[MessageSyncService] Failed to sync messages:', error);
|
||
|
|
throw error;
|
||
|
|
} finally {
|
||
|
|
this.syncingMessages.delete(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async loadHistoryMessages(
|
||
|
|
conversationId: string,
|
||
|
|
beforeSeq: number,
|
||
|
|
limit: number = 20
|
||
|
|
): Promise<Message[]> {
|
||
|
|
try {
|
||
|
|
const response = await messageService.getMessages(conversationId, {
|
||
|
|
before_seq: beforeSeq,
|
||
|
|
limit,
|
||
|
|
});
|
||
|
|
|
||
|
|
const messages = this.mapMessages(response.messages || []);
|
||
|
|
|
||
|
|
if (messages.length > 0) {
|
||
|
|
await messageRepository.saveMessages(messages, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
return messages;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('[MessageSyncService] Failed to load history:', error);
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async loadLocalMessages(
|
||
|
|
conversationId: string,
|
||
|
|
limit: number = 50
|
||
|
|
): Promise<Message[]> {
|
||
|
|
return messageRepository.getMessagesByConversation(conversationId, limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
async loadHistoryFromLocal(
|
||
|
|
conversationId: string,
|
||
|
|
beforeSeq: number,
|
||
|
|
limit: number = 20
|
||
|
|
): Promise<Message[]> {
|
||
|
|
return messageRepository.getMessagesBeforeSeq(conversationId, beforeSeq, limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
private mapConversations(response: ConversationResponse[]): Conversation[] {
|
||
|
|
return response.map(conv => ({
|
||
|
|
id: conv.id,
|
||
|
|
type: conv.type || 'private',
|
||
|
|
isPinned: conv.isPinned || false,
|
||
|
|
lastSeq: conv.lastSeq || conv.last_seq || 0,
|
||
|
|
lastMessageAt: conv.lastMessageAt || conv.last_message_at || new Date().toISOString(),
|
||
|
|
unreadCount: conv.unreadCount || conv.unread_count || 0,
|
||
|
|
participants: conv.participants || [],
|
||
|
|
group: conv.group,
|
||
|
|
createdAt: conv.createdAt || conv.created_at || new Date().toISOString(),
|
||
|
|
updatedAt: conv.updatedAt || conv.updated_at || new Date().toISOString(),
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
private mapMessages(response: MessageResponse[]): Message[] {
|
||
|
|
return response.map(msg => ({
|
||
|
|
id: msg.id,
|
||
|
|
conversationId: msg.conversationId || msg.conversation_id || '',
|
||
|
|
senderId: msg.senderId || msg.sender_id || '',
|
||
|
|
seq: msg.seq || 0,
|
||
|
|
segments: msg.segments || [],
|
||
|
|
createdAt: msg.createdAt || msg.created_at || new Date().toISOString(),
|
||
|
|
status: msg.status || 'normal',
|
||
|
|
sender: msg.sender,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
isSyncingConversations(): boolean {
|
||
|
|
return this.syncingConversations;
|
||
|
|
}
|
||
|
|
|
||
|
|
isSyncingMessages(conversationId: string): boolean {
|
||
|
|
return this.syncingMessages.get(conversationId) || false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const messageSyncService = new MessageSyncService();
|