/** * Unified Refresh Manager * * Centralizes all refresh logic across widgets and notifications. * Prevents duplicate refreshes, manages intervals, and provides * a single source of truth for refresh coordination. */ export type RefreshableResource = | 'notifications' | 'notifications-count' | 'calendar' | 'news' | 'email' | 'parole' | 'duties' | 'navbar-time'; export interface RefreshConfig { resource: RefreshableResource; interval: number; // milliseconds enabled: boolean; priority: 'high' | 'medium' | 'low'; onRefresh: () => Promise; } class RefreshManager { private intervals: Map = new Map(); private configs: Map = new Map(); private pendingRequests: Map> = new Map(); private lastRefresh: Map = new Map(); private isActive = false; /** * Register a refreshable resource */ register(config: RefreshConfig): void { console.log(`[RefreshManager] Registering resource: ${config.resource} (interval: ${config.interval}ms)`); this.configs.set(config.resource, config); if (config.enabled && this.isActive) { this.startRefresh(config.resource); } } /** * Unregister a resource */ unregister(resource: RefreshableResource): void { console.log(`[RefreshManager] Unregistering resource: ${resource}`); this.stopRefresh(resource); this.configs.delete(resource); this.lastRefresh.delete(resource); // Clean up pending request const pendingKey = `${resource}-pending`; this.pendingRequests.delete(pendingKey); } /** * Start all refresh intervals */ start(): void { if (this.isActive) { console.log('[RefreshManager] Already active'); return; } console.log('[RefreshManager] Starting refresh manager'); this.isActive = true; // Start all enabled resources this.configs.forEach((config, resource) => { if (config.enabled) { this.startRefresh(resource); } }); } /** * Stop all refresh intervals */ stop(): void { if (!this.isActive) { console.log('[RefreshManager] Already stopped'); return; } console.log('[RefreshManager] Stopping refresh manager'); this.isActive = false; // Clear all intervals this.intervals.forEach((interval, resource) => { console.log(`[RefreshManager] Stopping refresh for: ${resource}`); clearInterval(interval); }); this.intervals.clear(); // Clear pending requests this.pendingRequests.clear(); } /** * Start refresh for a specific resource */ private startRefresh(resource: RefreshableResource): void { // Stop existing interval if any this.stopRefresh(resource); const config = this.configs.get(resource); if (!config || !config.enabled) { console.log(`[RefreshManager] Cannot start refresh for ${resource}: not configured or disabled`); return; } console.log(`[RefreshManager] Starting refresh for ${resource} (interval: ${config.interval}ms)`); // Initial refresh this.executeRefresh(resource); // Set up interval const interval = setInterval(() => { this.executeRefresh(resource); }, config.interval); this.intervals.set(resource, interval); } /** * Stop refresh for a specific resource */ private stopRefresh(resource: RefreshableResource): void { const interval = this.intervals.get(resource); if (interval) { clearInterval(interval); this.intervals.delete(resource); console.log(`[RefreshManager] Stopped refresh for: ${resource}`); } } /** * Execute refresh with deduplication */ private async executeRefresh(resource: RefreshableResource): Promise { const config = this.configs.get(resource); if (!config) { console.warn(`[RefreshManager] No config found for resource: ${resource}`); return; } const now = Date.now(); const lastRefreshTime = this.lastRefresh.get(resource) || 0; // Prevent too frequent refreshes (minimum 1 second between same resource) if (now - lastRefreshTime < 1000) { console.log(`[RefreshManager] Skipping ${resource} - too soon (${now - lastRefreshTime}ms ago)`); return; } // Check if there's already a pending request for this resource const pendingKey = `${resource}-pending`; if (this.pendingRequests.has(pendingKey)) { console.log(`[RefreshManager] Deduplicating ${resource} request - already pending`); return; } // Create and track the request console.log(`[RefreshManager] Executing refresh for: ${resource}`); const refreshPromise = config.onRefresh() .then(() => { this.lastRefresh.set(resource, Date.now()); console.log(`[RefreshManager] Successfully refreshed: ${resource}`); }) .catch((error) => { console.error(`[RefreshManager] Error refreshing ${resource}:`, error); // Don't update lastRefresh on error to allow retry }) .finally(() => { this.pendingRequests.delete(pendingKey); }); this.pendingRequests.set(pendingKey, refreshPromise); try { await refreshPromise; } catch (error) { // Error already logged above } } /** * Manually trigger refresh for a resource */ async refresh(resource: RefreshableResource, force = false): Promise { const config = this.configs.get(resource); if (!config) { throw new Error(`Resource ${resource} not registered`); } console.log(`[RefreshManager] Manual refresh requested for: ${resource} (force: ${force})`); if (force) { // Force refresh: clear last refresh time and pending request this.lastRefresh.delete(resource); const pendingKey = `${resource}-pending`; this.pendingRequests.delete(pendingKey); } await this.executeRefresh(resource); } /** * Get refresh status */ getStatus(): { active: boolean; resources: Array<{ resource: RefreshableResource; enabled: boolean; lastRefresh: number | null; interval: number; isRunning: boolean; }>; } { const resources = Array.from(this.configs.entries()).map(([resource, config]) => ({ resource, enabled: config.enabled, lastRefresh: this.lastRefresh.get(resource) || null, interval: config.interval, isRunning: this.intervals.has(resource), })); return { active: this.isActive, resources, }; } /** * Pause all refreshes (temporary stop) */ pause(): void { console.log('[RefreshManager] Pausing all refreshes'); this.stop(); } /** * Resume all refreshes */ resume(): void { console.log('[RefreshManager] Resuming all refreshes'); this.start(); } } // Singleton instance export const refreshManager = new RefreshManager();