import { apiService } from './api' import { useAppStore } from '@/stores/app' import type { ExtendedMessage, UnsentMessage } from '@/types' export class SyncService { private getAppStore() { return useAppStore() } /** * Sync messages for a channel: merge server data with local data */ async syncChannelMessages(channelId: number): Promise { try { console.log(`Syncing messages for channel ${channelId}`) const appStore = this.getAppStore() // Get server messages const serverResponse = await apiService.getMessages(channelId) const serverMessages = serverResponse.messages // Get local messages const localMessages = appStore.messages[channelId] || [] console.log(`Server has ${serverMessages.length} messages, local has ${localMessages.length} messages`) // Merge messages using a simple strategy: // 1. Create a map of all messages by ID // 2. Server messages take precedence (they may have been updated) // 3. Keep local messages that don't exist on server (may be unsent) const messageMap = new Map() // Add local messages first localMessages.forEach(msg => { if (typeof msg.id === 'number') { messageMap.set(msg.id, msg) } }) // Add/update with server messages (server wins for conflicts) serverMessages.forEach((msg: any) => { // Transform server message format to match our types const transformedMsg: ExtendedMessage = { id: msg.id, channel_id: msg.channelId || msg.channel_id, content: msg.content, created_at: msg.createdAt || msg.created_at, file_id: msg.fileId || msg.file_id, // Map the flattened file fields from backend fileId: msg.fileId, filePath: msg.filePath, fileType: msg.fileType, fileSize: msg.fileSize, originalName: msg.originalName, fileCreatedAt: msg.fileCreatedAt } console.log(`Sync: Processing message ${msg.id}, has file:`, !!msg.fileId, `(${msg.originalName})`) messageMap.set(msg.id, transformedMsg) }) // Convert back to array, sorted by creation time const mergedMessages = Array.from(messageMap.values()) .sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) console.log(`Merged result: ${mergedMessages.length} messages`) // Update local storage appStore.setMessages(channelId, mergedMessages) await appStore.saveState() } catch (error) { console.warn(`Failed to sync messages for channel ${channelId}:`, error) throw error } } /** * Attempt to send all unsent messages */ async retryUnsentMessages(): Promise { const appStore = this.getAppStore() const unsentMessages = appStore.unsentMessages console.log(`Attempting to send ${unsentMessages.length} unsent messages`) for (const unsentMsg of [...unsentMessages]) { try { console.log(`Sending unsent message: ${unsentMsg.content}`) // Try to send the message const response = await apiService.createMessage(unsentMsg.channelId, unsentMsg.content) console.log(`Successfully sent unsent message, got ID: ${response.id}`) // Create the sent message const sentMessage: ExtendedMessage = { id: response.id, channel_id: unsentMsg.channelId, content: unsentMsg.content, created_at: new Date().toISOString() } // Add to messages and remove from unsent appStore.addMessage(sentMessage) appStore.removeUnsentMessage(unsentMsg.id) // Save state immediately after successful send to ensure UI updates await appStore.saveState() console.log(`Moved unsent message ${unsentMsg.id} to sent messages with ID ${response.id}`) console.log(`Unsent messages remaining: ${appStore.unsentMessages.length}`) } catch (error) { console.warn(`Failed to send unsent message ${unsentMsg.id}:`, error) // Increment retry count unsentMsg.retries = (unsentMsg.retries || 0) + 1 // Remove if too many retries (optional) if (unsentMsg.retries >= 5) { console.log(`Giving up on unsent message ${unsentMsg.id} after ${unsentMsg.retries} retries`) appStore.removeUnsentMessage(unsentMsg.id) } } } // Save state after processing await appStore.saveState() } /** * Full sync: channels and messages */ async fullSync(): Promise { try { console.log('Starting full sync...') const appStore = this.getAppStore() // 1. Sync channels const channelsResponse = await apiService.getChannels() appStore.setChannels(channelsResponse.channels) // 2. Retry unsent messages first await this.retryUnsentMessages() // 3. Sync messages for current channel if (appStore.currentChannelId) { await this.syncChannelMessages(appStore.currentChannelId) } // 4. Save everything await appStore.saveState() console.log('Full sync completed') } catch (error) { console.error('Full sync failed:', error) throw error } } /** * Optimistic message sending with automatic sync */ async sendMessage(channelId: number, content: string): Promise { try { console.log(`Optimistically sending message: ${content}`) // Try to send immediately const response = await apiService.createMessage(channelId, content) // Success - add to local messages const message: ExtendedMessage = { id: response.id, channel_id: channelId, content: content, created_at: new Date().toISOString() } const appStore = this.getAppStore() appStore.addMessage(message) console.log(`Message sent successfully with ID: ${response.id}`) } catch (error) { console.warn('Failed to send message immediately, queuing for later:', error) // Failed - add to unsent messages const unsentMessage: UnsentMessage = { id: `unsent_${Date.now()}_${Math.random()}`, channelId: channelId, content: content, timestamp: Date.now(), retries: 0 } const appStore = this.getAppStore() appStore.addUnsentMessage(unsentMessage) await appStore.saveState() throw error // Re-throw so caller knows it failed } } } export const syncService = new SyncService()