2026-03-18 12:11:49 +08:00
|
|
|
/**
|
|
|
|
|
* 消息同步服务
|
|
|
|
|
* 负责从服务器同步消息和会话
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { messageService } from '../../services/messageService';
|
|
|
|
|
import { messageRepository } from '../../data/repositories/MessageRepository';
|
|
|
|
|
import type { Message, Conversation } from '../../core/entities/Message';
|
2026-03-19 10:53:09 +08:00
|
|
|
import type { ConversationListResponse, MessageListResponse } from '../../types/dto';
|
2026-03-18 12:11:49 +08:00
|
|
|
|
|
|
|
|
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 {
|
2026-03-19 10:53:09 +08:00
|
|
|
const response: ConversationListResponse = await messageService.getConversations();
|
|
|
|
|
const conversations = this.mapConversations(response.list || []);
|
2026-03-18 12:11:49 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-03-19 10:53:09 +08:00
|
|
|
const response: MessageListResponse = await messageService.getMessages(
|
|
|
|
|
conversationId,
|
|
|
|
|
lastSeq,
|
|
|
|
|
undefined,
|
|
|
|
|
50
|
|
|
|
|
);
|
2026-03-18 12:11:49 +08:00
|
|
|
|
|
|
|
|
const messages = this.mapMessages(response.messages || []);
|
|
|
|
|
|
|
|
|
|
if (messages.length > 0) {
|
|
|
|
|
await messageRepository.saveMessages(messages, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
conversations: [],
|
|
|
|
|
messages,
|
2026-03-19 10:53:09 +08:00
|
|
|
hasMore: messages.length >= 50,
|
2026-03-18 12:11:49 +08:00
|
|
|
};
|
|
|
|
|
} 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 {
|
2026-03-19 10:53:09 +08:00
|
|
|
const response: MessageListResponse = await messageService.getMessages(
|
|
|
|
|
conversationId,
|
|
|
|
|
undefined,
|
|
|
|
|
beforeSeq,
|
|
|
|
|
limit
|
|
|
|
|
);
|
2026-03-18 12:11:49 +08:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 10:53:09 +08:00
|
|
|
private mapConversations(list: ConversationListResponse['list']): Conversation[] {
|
|
|
|
|
return list.map(conv => ({
|
2026-03-18 12:11:49 +08:00
|
|
|
id: conv.id,
|
|
|
|
|
type: conv.type || 'private',
|
2026-03-19 10:53:09 +08:00
|
|
|
isPinned: conv.is_pinned || false,
|
|
|
|
|
lastSeq: conv.last_seq || 0,
|
|
|
|
|
lastMessageAt: conv.last_message_at || new Date().toISOString(),
|
|
|
|
|
unreadCount: conv.unread_count || 0,
|
|
|
|
|
participants: (conv.participants || []).map(p => ({
|
|
|
|
|
id: p.id,
|
|
|
|
|
username: p.username,
|
|
|
|
|
avatar: p.avatar || undefined,
|
|
|
|
|
nickname: p.nickname,
|
|
|
|
|
})),
|
|
|
|
|
group: conv.group ? {
|
|
|
|
|
id: String(conv.group.id),
|
|
|
|
|
name: conv.group.name,
|
|
|
|
|
avatar: conv.group.avatar,
|
|
|
|
|
} : undefined,
|
|
|
|
|
createdAt: conv.created_at || new Date().toISOString(),
|
|
|
|
|
updatedAt: conv.updated_at || new Date().toISOString(),
|
2026-03-18 12:11:49 +08:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 10:53:09 +08:00
|
|
|
private mapMessages(messages: MessageListResponse['messages']): Message[] {
|
|
|
|
|
return messages.map(msg => ({
|
2026-03-18 12:11:49 +08:00
|
|
|
id: msg.id,
|
2026-03-19 10:53:09 +08:00
|
|
|
conversationId: msg.conversation_id,
|
|
|
|
|
senderId: msg.sender_id,
|
|
|
|
|
seq: msg.seq,
|
2026-03-18 12:11:49 +08:00
|
|
|
segments: msg.segments || [],
|
2026-03-19 10:53:09 +08:00
|
|
|
createdAt: msg.created_at,
|
2026-03-18 12:11:49 +08:00
|
|
|
status: msg.status || 'normal',
|
2026-03-19 10:53:09 +08:00
|
|
|
sender: msg.sender ? {
|
|
|
|
|
id: msg.sender.id,
|
|
|
|
|
username: msg.sender.username,
|
|
|
|
|
avatar: msg.sender.avatar || undefined,
|
|
|
|
|
nickname: msg.sender.nickname,
|
|
|
|
|
} : undefined,
|
2026-03-18 12:11:49 +08:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isSyncingConversations(): boolean {
|
|
|
|
|
return this.syncingConversations;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isSyncingMessages(conversationId: string): boolean {
|
|
|
|
|
return this.syncingMessages.get(conversationId) || false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const messageSyncService = new MessageSyncService();
|