| |
| |
| |
|
|
| const SYNC_DEBOUNCE_MS = 2500; |
| let _syncDebounceTimer = null; |
| let _isSyncing = false; |
|
|
| const SyncManager = { |
| init() { |
| window.addEventListener('online', () => { |
| this._updateUIState('online'); |
| this.flushChanges(); |
| }); |
| |
| window.addEventListener('offline', () => { |
| this._updateUIState('offline'); |
| }); |
|
|
| |
| if (navigator.onLine) { |
| setTimeout(() => this.flushChanges(), 1000); |
| } |
| }, |
|
|
| |
| |
| |
| |
| |
| queueChange(docId, content) { |
| if (!docId) return; |
|
|
| |
| if (typeof SyncQueue !== 'undefined') { |
| SyncQueue.enqueue(docId, content); |
| } |
|
|
| if (!navigator.onLine) { |
| this._updateUIState('saved_locally'); |
| return; |
| } |
|
|
| this._updateUIState('saving'); |
|
|
| |
| if (_syncDebounceTimer) clearTimeout(_syncDebounceTimer); |
| _syncDebounceTimer = setTimeout(() => { |
| this.flushChanges(); |
| }, SYNC_DEBOUNCE_MS); |
| }, |
|
|
| |
| |
| |
| async syncNow() { |
| if (_syncDebounceTimer) clearTimeout(_syncDebounceTimer); |
| await this.flushChanges(); |
| }, |
|
|
| |
| |
| |
| async flushChanges() { |
| if (!navigator.onLine || typeof SyncQueue === 'undefined' || typeof saveDocument === 'undefined') return; |
| if (_isSyncing) return; |
|
|
| const queue = SyncQueue.getAll(); |
| if (queue.length === 0) { |
| this._updateUIState('saved'); |
| return; |
| } |
|
|
| _isSyncing = true; |
| this._updateUIState('saving'); |
|
|
| let allSuccess = true; |
|
|
| for (const item of queue) { |
| try { |
| |
| |
| |
| |
| |
| const success = await saveDocument(item.docId, item.content); |
| if (success) { |
| SyncQueue.remove(item.id); |
| } else { |
| allSuccess = false; |
| SyncQueue.incrementRetry(item.id); |
| } |
| } catch (e) { |
| console.error('Sync error:', e); |
| allSuccess = false; |
| SyncQueue.incrementRetry(item.id); |
| } |
| } |
|
|
| _isSyncing = false; |
|
|
| if (allSuccess) { |
| this._updateUIState('saved'); |
| if (typeof markClean === 'function') markClean(); |
| } else { |
| this._updateUIState('error'); |
| } |
| }, |
|
|
| |
| |
| |
| |
| |
| async loadAndResolveDocument(docId) { |
| if (typeof loadDocument === 'undefined' || typeof SyncResolver === 'undefined') { |
| return null; |
| } |
|
|
| const serverDoc = await loadDocument(docId); |
| if (!serverDoc) return null; |
|
|
| |
| const queue = SyncQueue.getAll(); |
| const localDraft = queue.find(q => q.docId === docId); |
|
|
| if (localDraft) { |
| const winner = SyncResolver.resolveConflict(localDraft, serverDoc); |
| if (winner === 'local') { |
| |
| setTimeout(() => this.flushChanges(), 500); |
| return localDraft.content; |
| } else { |
| |
| SyncQueue.remove(localDraft.id); |
| return serverDoc.content; |
| } |
| } |
|
|
| return serverDoc.content; |
| }, |
|
|
| |
| |
| |
| |
| _updateUIState(state) { |
| window.dispatchEvent(new CustomEvent('bayan:syncstate', { detail: { state } })); |
| } |
| }; |
|
|
| if (typeof module !== 'undefined' && module.exports) { |
| module.exports = { SyncManager, SYNC_DEBOUNCE_MS }; |
| } |
|
|