const NetworkManager = (function() { const state = { isOnline: navigator.onLine, reconnectAttempts: 0, reconnectTimer: null, lastNetworkError: null, eventHandlers: {}, connectionTestInProgress: false }; const config = { maxReconnectAttempts: 5, reconnectDelay: 5000, reconnectBackoff: 1.5, connectionTestEndpoint: '/json', connectionTestTimeout: 3000, debug: false }; function log(message, ...args) { if (config.debug) { console.log(`[NetworkManager] ${message}`, ...args); } } function generateHandlerId() { return `handler_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; } const publicAPI = { initialize: function(options = {}) { Object.assign(config, options); window.addEventListener('online', this.handleOnlineStatus.bind(this)); window.addEventListener('offline', this.handleOfflineStatus.bind(this)); state.isOnline = navigator.onLine; log(`Network status initialized: ${state.isOnline ? 'online' : 'offline'}`); if (window.CleanupManager) { window.CleanupManager.registerResource('networkManager', this, (mgr) => mgr.dispose()); } return this; }, isOnline: function() { return state.isOnline; }, getReconnectAttempts: function() { return state.reconnectAttempts; }, resetReconnectAttempts: function() { state.reconnectAttempts = 0; return this; }, handleOnlineStatus: function() { log('Browser reports online status'); state.isOnline = true; this.notifyHandlers('online'); if (state.reconnectTimer) { this.scheduleReconnectRefresh(); } }, handleOfflineStatus: function() { log('Browser reports offline status'); state.isOnline = false; this.notifyHandlers('offline'); }, handleNetworkError: function(error) { if (error && ( (error.name === 'TypeError' && error.message.includes('NetworkError')) || (error.name === 'AbortError') || (error.message && error.message.includes('network')) || (error.message && error.message.includes('timeout')) )) { log('Network error detected:', error.message); if (state.isOnline) { state.isOnline = false; state.lastNetworkError = error; this.notifyHandlers('error', error); } if (!state.reconnectTimer) { this.scheduleReconnectRefresh(); } return true; } return false; }, scheduleReconnectRefresh: function() { if (state.reconnectTimer) { clearTimeout(state.reconnectTimer); state.reconnectTimer = null; } const delay = config.reconnectDelay * Math.pow(config.reconnectBackoff, Math.min(state.reconnectAttempts, 5)); log(`Scheduling reconnection attempt in ${delay/1000} seconds`); state.reconnectTimer = CleanupManager.setTimeout(() => { state.reconnectTimer = null; this.attemptReconnect(); }, delay); return this; }, attemptReconnect: function() { if (!navigator.onLine) { log('Browser still reports offline, delaying reconnection attempt'); this.scheduleReconnectRefresh(); return; } if (state.connectionTestInProgress) { log('Connection test already in progress'); return; } state.reconnectAttempts++; state.connectionTestInProgress = true; log(`Attempting reconnect #${state.reconnectAttempts}`); this.testBackendConnection() .then(isAvailable => { state.connectionTestInProgress = false; if (isAvailable) { log('Backend connection confirmed'); state.isOnline = true; state.reconnectAttempts = 0; state.lastNetworkError = null; this.notifyHandlers('reconnected'); } else { log('Backend still unavailable'); if (state.reconnectAttempts < config.maxReconnectAttempts) { this.scheduleReconnectRefresh(); } else { log('Maximum reconnect attempts reached'); this.notifyHandlers('maxAttemptsReached'); } } }) .catch(error => { state.connectionTestInProgress = false; log('Error during connection test:', error); if (state.reconnectAttempts < config.maxReconnectAttempts) { this.scheduleReconnectRefresh(); } else { log('Maximum reconnect attempts reached'); this.notifyHandlers('maxAttemptsReached'); } }); }, testBackendConnection: async function() { if (window.ApiManager) { try { await window.ApiManager.makeRequest(config.connectionTestEndpoint, 'HEAD', { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }); return true; } catch (error) { log('Backend connection test failed:', error.message); return false; } } return fetch(config.connectionTestEndpoint, { method: 'HEAD', headers: { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }, timeout: config.connectionTestTimeout, signal: AbortSignal.timeout(config.connectionTestTimeout) }) .then(response => { return response.ok; }) .catch(error => { log('Backend connection test failed:', error.message); return false; }); }, manualReconnect: function() { log('Manual reconnection requested'); state.isOnline = navigator.onLine; state.reconnectAttempts = 0; this.notifyHandlers('manualReconnect'); if (state.isOnline) { return this.attemptReconnect(); } else { log('Cannot attempt manual reconnect while browser reports offline'); this.notifyHandlers('offlineWarning'); return false; } }, addHandler: function(event, handler) { if (!state.eventHandlers[event]) { state.eventHandlers[event] = {}; } const handlerId = generateHandlerId(); state.eventHandlers[event][handlerId] = handler; return handlerId; }, removeHandler: function(event, handlerId) { if (state.eventHandlers[event] && state.eventHandlers[event][handlerId]) { delete state.eventHandlers[event][handlerId]; return true; } return false; }, notifyHandlers: function(event, data) { if (state.eventHandlers[event]) { Object.values(state.eventHandlers[event]).forEach(handler => { try { handler(data); } catch (error) { log(`Error in ${event} handler:`, error); } }); } }, setDebugMode: function(enabled) { config.debug = Boolean(enabled); return `Debug mode ${config.debug ? 'enabled' : 'disabled'}`; }, getState: function() { return { isOnline: state.isOnline, reconnectAttempts: state.reconnectAttempts, hasReconnectTimer: Boolean(state.reconnectTimer), connectionTestInProgress: state.connectionTestInProgress }; }, dispose: function() { if (state.reconnectTimer) { clearTimeout(state.reconnectTimer); state.reconnectTimer = null; } window.removeEventListener('online', this.handleOnlineStatus); window.removeEventListener('offline', this.handleOfflineStatus); state.eventHandlers = {}; log('NetworkManager disposed'); } }; return publicAPI; })(); window.NetworkManager = NetworkManager; document.addEventListener('DOMContentLoaded', function() { if (!window.networkManagerInitialized) { NetworkManager.initialize(); window.networkManagerInitialized = true; } }); console.log('NetworkManager initialized');