mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 10:28:10 +01:00
292 lines
9.4 KiB
JavaScript
292 lines
9.4 KiB
JavaScript
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');
|