123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- 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)
|