UNPKG

recoder-shared

Version:

Shared types, utilities, and configurations for Recoder

325 lines 12 kB
"use strict"; /** * Data Synchronization Client for Recoder.xyz * Handles cross-platform data sync with conflict resolution */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SyncClient = void 0; const tslib_1 = require("tslib"); const events_1 = require("events"); const axios_1 = tslib_1.__importDefault(require("axios")); class SyncClient extends events_1.EventEmitter { constructor(config) { super(); this.localVersions = new Map(); this.syncInProgress = new Set(); this.config = { baseURL: 'http://localhost:3001', syncInterval: 30000, // 30 seconds enableRealTimeSync: true, ...config }; this.authClient = config.authClient; this.webSocketClient = config.webSocketClient; this.api = axios_1.default.create({ baseURL: `${this.config.baseURL}/api`, timeout: 10000 }); this.setupAPIInterceptors(); this.setupWebSocketListeners(); this.loadLocalVersions(); } setupAPIInterceptors() { this.api.interceptors.request.use((config) => { const tokens = this.authClient.getTokens(); if (tokens?.accessToken) { config.headers.Authorization = `Bearer ${tokens.accessToken}`; } return config; }); } setupWebSocketListeners() { if (!this.webSocketClient) return; this.webSocketClient.on('syncRequested', (data) => { this.handleSyncRequest(data); }); this.webSocketClient.on('syncUpdated', (data) => { this.handleSyncUpdate(data); }); } // Main Sync Methods async startAutoSync() { if (this.syncInterval) return; // Initial sync await this.syncAll(); // Set up periodic sync this.syncInterval = setInterval(() => { this.syncAll().catch(error => { console.error('Auto sync failed:', error); this.emit('syncError', { error, dataType: 'all' }); }); }, this.config.syncInterval); this.emit('autoSyncStarted'); } stopAutoSync() { if (this.syncInterval) { clearInterval(this.syncInterval); this.syncInterval = undefined; this.emit('autoSyncStopped'); } } async syncAll() { const dataTypes = ['projects', 'settings', 'history', 'preferences']; const results = await Promise.allSettled(dataTypes.map(dataType => this.sync(dataType))); const failures = results.filter(result => result.status === 'rejected'); if (failures.length > 0) { console.error('Some syncs failed:', failures); this.emit('syncPartialFailure', failures); } } async sync(dataType) { if (this.syncInProgress.has(dataType)) { throw new Error(`Sync already in progress for ${dataType}`); } this.syncInProgress.add(dataType); try { const localVersion = this.localVersions.get(dataType) || 1; const deviceInfo = this.authClient.getDeviceInfo(); const result = await this.performSync(dataType, localVersion, deviceInfo?.deviceId); if (result.success && result.version) { this.localVersions.set(dataType, result.version); this.saveLocalVersions(); } this.emit('syncCompleted', { dataType, result }); return result; } finally { this.syncInProgress.delete(dataType); } } // Specific Data Type Sync Methods async syncProjects() { return this.sync('projects'); } async syncSettings() { return this.sync('settings'); } async updateProjects(projects) { const localVersion = this.localVersions.get('projects') || 1; const deviceInfo = this.authClient.getDeviceInfo(); try { const response = await this.api.post('/sync/projects', { deviceId: deviceInfo?.deviceId, localVersion: localVersion + 1, projects }); const result = response.data.data; if (result.version) { this.localVersions.set('projects', result.version); this.saveLocalVersions(); } // Notify via WebSocket if available if (this.webSocketClient?.connected) { await this.webSocketClient.updateSync('projects', result.version || localVersion + 1, projects); } this.emit('projectsUpdated', result); return result; } catch (error) { throw this.handleSyncError(error); } } async updateSettings(settings) { const localVersion = this.localVersions.get('settings') || 1; const deviceInfo = this.authClient.getDeviceInfo(); try { const response = await this.api.post('/sync/settings', { deviceId: deviceInfo?.deviceId, localVersion: localVersion + 1, settings }); const result = response.data.data; if (result.version) { this.localVersions.set('settings', result.version); this.saveLocalVersions(); } // Notify via WebSocket if available if (this.webSocketClient?.connected) { await this.webSocketClient.updateSync('settings', result.version || localVersion + 1, settings); } this.emit('settingsUpdated', result); return result; } catch (error) { throw this.handleSyncError(error); } } // Conflict Resolution async resolveConflict(dataType, resolution, resolvedData) { const deviceInfo = this.authClient.getDeviceInfo(); try { const response = await this.api.post('/sync/resolve-conflicts', { dataType, resolution, deviceId: deviceInfo?.deviceId, resolvedData }); const result = response.data.data; if (result.version) { this.localVersions.set(dataType, result.version); this.saveLocalVersions(); } this.emit('conflictResolved', { dataType, resolution, result }); return result; } catch (error) { throw this.handleSyncError(error); } } // Force Sync async forceSync() { const deviceInfo = this.authClient.getDeviceInfo(); try { const response = await this.api.post('/sync/force-sync', { deviceId: deviceInfo?.deviceId }); const result = response.data.data; // Update all local versions if (result.versions) { Object.entries(result.versions).forEach(([dataType, version]) => { this.localVersions.set(dataType, version); }); this.saveLocalVersions(); } this.emit('forceSyncCompleted', result); return result; } catch (error) { throw this.handleSyncError(error); } } // Sync Status async getSyncStatus() { try { const response = await this.api.get('/sync/status'); return response.data.data; } catch (error) { throw this.handleSyncError(error); } } // Private Methods async performSync(dataType, localVersion, deviceId) { try { const endpoint = `/sync/${dataType}`; const response = await this.api.post(endpoint, { deviceId, localVersion }); return response.data.data; } catch (error) { throw this.handleSyncError(error); } } handleSyncRequest(data) { this.emit('syncRequested', { dataType: data.dataType, version: data.version, requestedBy: data.requestedBy }); // Auto-sync if real-time sync is enabled if (this.config.enableRealTimeSync) { this.sync(data.dataType).catch(error => { console.error(`Failed to handle sync request for ${data.dataType}:`, error); }); } } handleSyncUpdate(data) { this.emit('syncUpdated', { dataType: data.dataType, version: data.version, changes: data.changes, updatedBy: data.updatedBy }); // Update local version if newer const currentVersion = this.localVersions.get(data.dataType) || 0; if (data.version > currentVersion) { this.localVersions.set(data.dataType, data.version); this.saveLocalVersions(); } } handleSyncError(error) { const message = error.response?.data?.error?.message || error.message || 'Sync failed'; return new Error(message); } loadLocalVersions() { try { if (typeof window !== 'undefined' && window && window.localStorage && typeof localStorage !== 'undefined') { const stored = localStorage.getItem('recoder-sync-versions'); if (stored) { const versions = JSON.parse(stored); Object.entries(versions).forEach(([dataType, version]) => { this.localVersions.set(dataType, version); }); } } else if (typeof process !== 'undefined' && process) { const fs = require('fs'); const path = require('path'); const os = require('os'); const syncFile = path.join(os.homedir(), '.recoder', 'sync-versions.json'); if (fs.existsSync(syncFile)) { const content = fs.readFileSync(syncFile, 'utf-8'); const versions = JSON.parse(content); Object.entries(versions).forEach(([dataType, version]) => { this.localVersions.set(dataType, version); }); } } } catch (error) { console.error('Failed to load sync versions:', error); } } saveLocalVersions() { try { const versions = Object.fromEntries(this.localVersions); if (typeof window !== 'undefined' && window && window.localStorage && typeof localStorage !== 'undefined') { localStorage.setItem('recoder-sync-versions', JSON.stringify(versions)); } else if (typeof process !== 'undefined' && process) { const fs = require('fs'); const path = require('path'); const os = require('os'); const configDir = path.join(os.homedir(), '.recoder'); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } const syncFile = path.join(configDir, 'sync-versions.json'); fs.writeFileSync(syncFile, JSON.stringify(versions, null, 2)); } } catch (error) { console.error('Failed to save sync versions:', error); } } // Public getters get isSyncing() { return this.syncInProgress.size > 0; } get autoSyncEnabled() { return !!this.syncInterval; } getLocalVersion(dataType) { return this.localVersions.get(dataType) || 1; } getSyncingDataTypes() { return Array.from(this.syncInProgress); } } exports.SyncClient = SyncClient; exports.default = SyncClient; //# sourceMappingURL=sync-client.js.map