206 lines
6.7 KiB
TypeScript
206 lines
6.7 KiB
TypeScript
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<void> {
|
|
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<number, ExtendedMessage>()
|
|
|
|
// 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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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() |