wekan/models/lib/mongodbConnectionManager.js
Lauri Ojansivu bd8c565415 Fixes to make board showing correctly.
Thanks to xet7 !
2025-10-12 03:48:21 +03:00

291 lines
9.2 KiB
JavaScript

import { Meteor } from 'meteor/meteor';
import { mongodbDriverManager } from './mongodbDriverManager';
/**
* MongoDB Connection Manager
*
* This module handles MongoDB connections with automatic driver selection
* based on detected MongoDB server version and wire protocol compatibility.
*
* Features:
* - Automatic driver selection based on MongoDB version
* - Connection retry with different drivers on wire protocol errors
* - Fallback mechanism for unsupported versions
* - Connection pooling and management
*/
class MongoDBConnectionManager {
constructor() {
this.connections = new Map();
this.connectionConfigs = new Map();
this.retryAttempts = 3;
this.retryDelay = 1000; // 1 second
}
/**
* Create a MongoDB connection with automatic driver selection
* @param {string} connectionString - MongoDB connection string
* @param {Object} options - Connection options
* @returns {Promise<Object>} - MongoDB connection object
*/
async createConnection(connectionString, options = {}) {
const connectionId = this.generateConnectionId(connectionString);
// Check if we already have a working connection
if (this.connections.has(connectionId)) {
const existingConnection = this.connections.get(connectionId);
if (existingConnection.status === 'connected') {
return existingConnection;
}
}
// Try to connect with automatic driver selection
return await this.connectWithDriverSelection(connectionString, options, connectionId);
}
/**
* Connect with automatic driver selection and retry logic
* @param {string} connectionString - MongoDB connection string
* @param {Object} options - Connection options
* @param {string} connectionId - Connection identifier
* @returns {Promise<Object>} - MongoDB connection object
*/
async connectWithDriverSelection(connectionString, options, connectionId) {
let lastError = null;
let currentDriver = null;
// First, try with the default driver (if we have a detected version)
if (mongodbDriverManager.detectedVersion) {
currentDriver = mongodbDriverManager.getDriverForVersion(mongodbDriverManager.detectedVersion);
} else {
// Start with the most recent driver
currentDriver = 'mongodb8legacy';
}
// Try connection with different drivers
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
try {
console.log(`Attempting MongoDB connection with driver: ${currentDriver} (attempt ${attempt + 1})`);
const connection = await this.connectWithDriver(currentDriver, connectionString, options);
// Record successful connection
mongodbDriverManager.recordConnectionAttempt(
currentDriver,
mongodbDriverManager.detectedVersion || 'unknown',
true
);
// Store connection
this.connections.set(connectionId, {
connection,
driver: currentDriver,
version: mongodbDriverManager.detectedVersion || 'unknown',
status: 'connected',
connectionString,
options,
createdAt: new Date()
});
return connection;
} catch (error) {
lastError = error;
console.error(`Connection attempt ${attempt + 1} failed with driver ${currentDriver}:`, error.message);
// Try to detect MongoDB version from error
const detectedVersion = mongodbDriverManager.detectVersionFromError(error);
if (detectedVersion && detectedVersion !== 'unknown') {
mongodbDriverManager.detectedVersion = detectedVersion;
currentDriver = mongodbDriverManager.getDriverForVersion(detectedVersion);
console.log(`Detected MongoDB version ${detectedVersion}, switching to driver ${currentDriver}`);
} else {
// Try next fallback driver
const nextDriver = mongodbDriverManager.getNextFallbackDriver();
if (nextDriver) {
currentDriver = nextDriver;
console.log(`Trying fallback driver: ${currentDriver}`);
} else {
console.error('No more fallback drivers available');
break;
}
}
// Record failed attempt
mongodbDriverManager.recordConnectionAttempt(
currentDriver,
detectedVersion || 'unknown',
false,
error
);
// Wait before retry
if (attempt < this.retryAttempts - 1) {
await this.delay(this.retryDelay * (attempt + 1));
}
}
}
// All attempts failed
throw new Error(`Failed to connect to MongoDB after ${this.retryAttempts} attempts. Last error: ${lastError?.message}`);
}
/**
* Connect using a specific driver
* @param {string} driverName - Driver package name
* @param {string} connectionString - MongoDB connection string
* @param {Object} options - Connection options
* @returns {Promise<Object>} - MongoDB connection object
*/
async connectWithDriver(driverName, connectionString, options) {
try {
// Dynamically import the driver
const driver = await import(driverName);
const MongoClient = driver.MongoClient;
// Set default options
const defaultOptions = {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
...options
};
// Create connection
const client = new MongoClient(connectionString, defaultOptions);
await client.connect();
// Test the connection
await client.db('admin').admin().ping();
return client;
} catch (error) {
throw new Error(`Failed to connect with driver ${driverName}: ${error.message}`);
}
}
/**
* Get connection by ID
* @param {string} connectionId - Connection identifier
* @returns {Object|null} - Connection object or null
*/
getConnection(connectionId) {
return this.connections.get(connectionId) || null;
}
/**
* Close a connection
* @param {string} connectionId - Connection identifier
* @returns {Promise<boolean>} - Whether connection was closed successfully
*/
async closeConnection(connectionId) {
const connection = this.connections.get(connectionId);
if (connection && connection.connection) {
try {
await connection.connection.close();
this.connections.delete(connectionId);
console.log(`Closed MongoDB connection: ${connectionId}`);
return true;
} catch (error) {
console.error(`Error closing MongoDB connection ${connectionId}:`, error.message);
return false;
}
}
return false;
}
/**
* Close all connections
* @returns {Promise<number>} - Number of connections closed
*/
async closeAllConnections() {
let closedCount = 0;
const connectionIds = Array.from(this.connections.keys());
for (const connectionId of connectionIds) {
if (await this.closeConnection(connectionId)) {
closedCount++;
}
}
console.log(`Closed ${closedCount} MongoDB connections`);
return closedCount;
}
/**
* Get connection statistics
* @returns {Object} - Connection statistics
*/
getConnectionStats() {
const connections = Array.from(this.connections.values());
const connected = connections.filter(conn => conn.status === 'connected').length;
const disconnected = connections.length - connected;
return {
total: connections.length,
connected,
disconnected,
connections: connections.map(conn => ({
id: this.getConnectionIdFromConnection(conn),
driver: conn.driver,
version: conn.version,
status: conn.status,
createdAt: conn.createdAt
}))
};
}
/**
* Generate a unique connection ID
* @param {string} connectionString - MongoDB connection string
* @returns {string} - Unique connection ID
*/
generateConnectionId(connectionString) {
// Create a hash of the connection string for unique ID
let hash = 0;
for (let i = 0; i < connectionString.length; i++) {
const char = connectionString.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return `mongodb_${Math.abs(hash)}`;
}
/**
* Get connection ID from connection object
* @param {Object} connection - Connection object
* @returns {string} - Connection ID
*/
getConnectionIdFromConnection(connection) {
return this.generateConnectionId(connection.connectionString);
}
/**
* Utility function to delay execution
* @param {number} ms - Milliseconds to delay
* @returns {Promise} - Promise that resolves after delay
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Reset all connections and driver manager state
*/
reset() {
this.connections.clear();
this.connectionConfigs.clear();
mongodbDriverManager.reset();
}
}
// Create singleton instance
const mongodbConnectionManager = new MongoDBConnectionManager();
// Export for use in other modules
export { mongodbConnectionManager, MongoDBConnectionManager };
// MongoDB Connection Manager initialized (status available in Admin Panel)