Some ignored filles was missing

This commit is contained in:
Janus C. H. Knudsen 2026-02-03 00:02:25 +01:00
parent 7db22245e2
commit fd5ab6bc0d
268 changed files with 31970 additions and 4 deletions

78
wwwroot/js/workers/SyncManager.d.ts vendored Normal file
View file

@ -0,0 +1,78 @@
import { IEventBus } from '../types/CalendarTypes';
import { OperationQueue } from '../storage/OperationQueue';
import { IndexedDBService } from '../storage/IndexedDBService';
import { ApiEventRepository } from '../repositories/ApiEventRepository';
/**
* SyncManager - Background sync worker
* Processes operation queue and syncs with API when online
*
* Features:
* - Monitors online/offline status
* - Processes queue with FIFO order
* - Exponential backoff retry logic
* - Updates syncStatus in IndexedDB after successful sync
* - Emits sync events for UI feedback
*/
export declare class SyncManager {
private eventBus;
private queue;
private indexedDB;
private apiRepository;
private isOnline;
private isSyncing;
private syncInterval;
private maxRetries;
private intervalId;
constructor(eventBus: IEventBus, queue: OperationQueue, indexedDB: IndexedDBService, apiRepository: ApiEventRepository);
/**
* Setup online/offline event listeners
*/
private setupNetworkListeners;
/**
* Start background sync worker
*/
startSync(): void;
/**
* Stop background sync worker
*/
stopSync(): void;
/**
* Process operation queue
* Sends pending operations to API
*/
private processQueue;
/**
* Process a single operation
*/
private processOperation;
/**
* Mark event as synced in IndexedDB
*/
private markEventAsSynced;
/**
* Mark event as error in IndexedDB
*/
private markEventAsError;
/**
* Calculate exponential backoff delay
* @param retryCount Current retry count
* @returns Delay in milliseconds
*/
private calculateBackoff;
/**
* Manually trigger sync (for testing or manual sync button)
*/
triggerManualSync(): Promise<void>;
/**
* Get current sync status
*/
getSyncStatus(): {
isOnline: boolean;
isSyncing: boolean;
isRunning: boolean;
};
/**
* Cleanup - stop sync and remove listeners
*/
destroy(): void;
}

View file

@ -0,0 +1,229 @@
import { CoreEvents } from '../constants/CoreEvents';
/**
* SyncManager - Background sync worker
* Processes operation queue and syncs with API when online
*
* Features:
* - Monitors online/offline status
* - Processes queue with FIFO order
* - Exponential backoff retry logic
* - Updates syncStatus in IndexedDB after successful sync
* - Emits sync events for UI feedback
*/
export class SyncManager {
constructor(eventBus, queue, indexedDB, apiRepository) {
this.isOnline = navigator.onLine;
this.isSyncing = false;
this.syncInterval = 5000; // 5 seconds
this.maxRetries = 5;
this.intervalId = null;
this.eventBus = eventBus;
this.queue = queue;
this.indexedDB = indexedDB;
this.apiRepository = apiRepository;
this.setupNetworkListeners();
this.startSync();
console.log('SyncManager initialized and started');
}
/**
* Setup online/offline event listeners
*/
setupNetworkListeners() {
window.addEventListener('online', () => {
this.isOnline = true;
this.eventBus.emit(CoreEvents.OFFLINE_MODE_CHANGED, {
isOnline: true
});
console.log('SyncManager: Network online - starting sync');
this.startSync();
});
window.addEventListener('offline', () => {
this.isOnline = false;
this.eventBus.emit(CoreEvents.OFFLINE_MODE_CHANGED, {
isOnline: false
});
console.log('SyncManager: Network offline - pausing sync');
this.stopSync();
});
}
/**
* Start background sync worker
*/
startSync() {
if (this.intervalId) {
return; // Already running
}
console.log('SyncManager: Starting background sync');
// Process immediately
this.processQueue();
// Then poll every syncInterval
this.intervalId = window.setInterval(() => {
this.processQueue();
}, this.syncInterval);
}
/**
* Stop background sync worker
*/
stopSync() {
if (this.intervalId) {
window.clearInterval(this.intervalId);
this.intervalId = null;
console.log('SyncManager: Stopped background sync');
}
}
/**
* Process operation queue
* Sends pending operations to API
*/
async processQueue() {
// Don't sync if offline
if (!this.isOnline) {
return;
}
// Don't start new sync if already syncing
if (this.isSyncing) {
return;
}
// Check if queue is empty
if (await this.queue.isEmpty()) {
return;
}
this.isSyncing = true;
try {
const operations = await this.queue.getAll();
this.eventBus.emit(CoreEvents.SYNC_STARTED, {
operationCount: operations.length
});
// Process operations one by one (FIFO)
for (const operation of operations) {
await this.processOperation(operation);
}
this.eventBus.emit(CoreEvents.SYNC_COMPLETED, {
operationCount: operations.length
});
}
catch (error) {
console.error('SyncManager: Queue processing error:', error);
this.eventBus.emit(CoreEvents.SYNC_FAILED, {
error: error instanceof Error ? error.message : 'Unknown error'
});
}
finally {
this.isSyncing = false;
}
}
/**
* Process a single operation
*/
async processOperation(operation) {
// Check if max retries exceeded
if (operation.retryCount >= this.maxRetries) {
console.error(`SyncManager: Max retries exceeded for operation ${operation.id}`, operation);
await this.queue.remove(operation.id);
await this.markEventAsError(operation.eventId);
return;
}
try {
// Send to API based on operation type
switch (operation.type) {
case 'create':
await this.apiRepository.sendCreate(operation.data);
break;
case 'update':
await this.apiRepository.sendUpdate(operation.eventId, operation.data);
break;
case 'delete':
await this.apiRepository.sendDelete(operation.eventId);
break;
default:
console.error(`SyncManager: Unknown operation type ${operation.type}`);
await this.queue.remove(operation.id);
return;
}
// Success - remove from queue and mark as synced
await this.queue.remove(operation.id);
await this.markEventAsSynced(operation.eventId);
console.log(`SyncManager: Successfully synced operation ${operation.id}`);
}
catch (error) {
console.error(`SyncManager: Failed to sync operation ${operation.id}:`, error);
// Increment retry count
await this.queue.incrementRetryCount(operation.id);
// Calculate backoff delay
const backoffDelay = this.calculateBackoff(operation.retryCount + 1);
this.eventBus.emit(CoreEvents.SYNC_RETRY, {
operationId: operation.id,
retryCount: operation.retryCount + 1,
nextRetryIn: backoffDelay
});
}
}
/**
* Mark event as synced in IndexedDB
*/
async markEventAsSynced(eventId) {
try {
const event = await this.indexedDB.getEvent(eventId);
if (event) {
event.syncStatus = 'synced';
await this.indexedDB.saveEvent(event);
}
}
catch (error) {
console.error(`SyncManager: Failed to mark event ${eventId} as synced:`, error);
}
}
/**
* Mark event as error in IndexedDB
*/
async markEventAsError(eventId) {
try {
const event = await this.indexedDB.getEvent(eventId);
if (event) {
event.syncStatus = 'error';
await this.indexedDB.saveEvent(event);
}
}
catch (error) {
console.error(`SyncManager: Failed to mark event ${eventId} as error:`, error);
}
}
/**
* Calculate exponential backoff delay
* @param retryCount Current retry count
* @returns Delay in milliseconds
*/
calculateBackoff(retryCount) {
// Exponential backoff: 2^retryCount * 1000ms
// Retry 1: 2s, Retry 2: 4s, Retry 3: 8s, Retry 4: 16s, Retry 5: 32s
const baseDelay = 1000;
const exponentialDelay = Math.pow(2, retryCount) * baseDelay;
const maxDelay = 60000; // Max 1 minute
return Math.min(exponentialDelay, maxDelay);
}
/**
* Manually trigger sync (for testing or manual sync button)
*/
async triggerManualSync() {
console.log('SyncManager: Manual sync triggered');
await this.processQueue();
}
/**
* Get current sync status
*/
getSyncStatus() {
return {
isOnline: this.isOnline,
isSyncing: this.isSyncing,
isRunning: this.intervalId !== null
};
}
/**
* Cleanup - stop sync and remove listeners
*/
destroy() {
this.stopSync();
// Note: We don't remove window event listeners as they're global
}
}
//# sourceMappingURL=SyncManager.js.map

File diff suppressed because one or more lines are too long