connection_connection-manager.js

/**
 * ConnectionManager - Manages and prioritizes different connection methods for GNSS devices
 */
export class ConnectionManager {
  constructor(eventEmitter, options = {}) {
    this.eventEmitter = eventEmitter;
    this.connectionMethods = []; // List of registered connection handlers
    this.activeConnection = null;
    this.isConnected = false;
    this.isConnecting = false;
    this.debugSettings = options.debug || { 
      info: false, 
      debug: false,
      errors: true 
    };
    
    // Logger for debugging
    this.logger = {
      info: (...args) => {
        if (this.debugSettings.info) {
          console.info('[CONN-MGR]', ...args);
        }
      },
      debug: (...args) => {
        if (this.debugSettings.debug) {
          console.debug('[CONN-MGR]', ...args);
        }
      },
      warn: (...args) => {
        // Warnings are typically shown regardless of debug settings
        console.warn('[CONN-MGR]', ...args);
      },
      error: (...args) => {
        if (this.debugSettings.errors) {
          console.error('[CONN-MGR]', ...args);
        }
      }
    };
  }
  
  /**
   * Register a connection handler
   * @param {ConnectionHandler} handler - The connection handler to register
   */
  registerConnectionMethod(handler) {
    this.connectionMethods.push(handler);
    this.logger.info(`Registered connection method: ${handler.name}`);
  }
  
  /**
   * Get available connection methods
   * @param {Object} options - Connection options that may affect availability
   * @returns {Array} - List of available connection handlers
   */
  getAvailableMethods(options = {}) {
    const available = this.connectionMethods
      .filter(handler => handler.isAvailable())
      .sort((a, b) => b.getPriority(options) - a.getPriority(options));
    
    this.logger.debug(`Available connection methods: ${available.map(h => h.name).join(', ')}`);
    return available;
  }
  
  /**
   * Connect to a GNSS device using the most appropriate method
   * @param {Object} options - Connection options
   * @returns {Promise<boolean>} - Whether connection was successful
   */
  async connect(options = {}) {
    if (this.isConnected) {
      this.logger.info('Already connected');
      return true;
    }
    
    if (this.isConnecting) {
      this.logger.info('Connection already in progress');
      return false;
    }
    
    this.isConnecting = true;
    this.eventEmitter.emit('connection:connecting', {});
    
    try {
      // Get available connection methods sorted by priority
      const availableMethods = this.getAvailableMethods(options);
      
      if (availableMethods.length === 0) {
        throw new Error('No connection methods available');
      }
      
      // Try specific method if requested
      if (options.method && options.method !== 'auto') {
        const specificMethod = this.connectionMethods
          .find(handler => handler.name === options.method && handler.isAvailable());
          
        if (specificMethod) {
          this.logger.info(`Trying specifically requested method: ${options.method}`);
          const success = await specificMethod.connect(options);
          
          if (success) {
            this.activeConnection = specificMethod;
            this.isConnected = true;
            this.isConnecting = false;
            this.eventEmitter.emit('connection:connected', {
              method: specificMethod.name,
              deviceInfo: specificMethod.getDeviceInfo()
            });
            return true;
          }
          
          // Do not fall back to auto if a specific method was requested
          this.logger.info(`Requested method ${options.method} failed`);
          this.isConnecting = false;
          this.eventEmitter.emit('connection:error', {
            message: `Failed to connect with ${options.method} method`
          });
          return false;
        } else {
          this.logger.warn(`Requested method ${options.method} not available`);
          this.isConnecting = false;
          this.eventEmitter.emit('connection:error', {
            message: `Requested connection method '${options.method}' is not available`
          });
          return false;
        }
      }
      
      // Try each method in priority order
      for (const handler of availableMethods) {
        this.logger.info(`Trying connection method: ${handler.name}`);
        this.eventEmitter.emit('connection:connecting', { method: handler.name });
        
        try {
          const success = await handler.connect(options);
          
          if (success) {
            this.activeConnection = handler;
            this.isConnected = true;
            this.isConnecting = false;
            this.eventEmitter.emit('connection:connected', {
              method: handler.name,
              deviceInfo: handler.getDeviceInfo()
            });
            return true;
          }
        } catch (err) {
          this.logger.error(`Error with ${handler.name}:`, err);
          // Continue with next method
        }
        
        this.logger.info(`Connection with ${handler.name} failed, trying next method`);
      }
      
      // If we get here, all methods failed
      this.logger.error('All connection methods failed');
      this.isConnecting = false;
      this.eventEmitter.emit('connection:error', {
        message: 'Failed to connect with any available method'
      });
      return false;
    } catch (error) {
      this.logger.error('Connection error:', error);
      this.isConnecting = false;
      this.eventEmitter.emit('connection:error', {
        message: error.message,
        error
      });
      return false;
    }
  }
  
  /**
   * Disconnect from the device
   * @returns {Promise<void>}
   */
  async disconnect() {
    if (!this.isConnected || !this.activeConnection) {
      return;
    }
    
    try {
      await this.activeConnection.disconnect();
      this.activeConnection = null;
      this.isConnected = false;
      this.eventEmitter.emit('connection:disconnected', {});
    } catch (error) {
      this.logger.error('Error during disconnect:', error);
      this.eventEmitter.emit('connection:error', {
        message: 'Failed to disconnect properly',
        error
      });
    }
  }
  
  /**
   * Send data to the connected device
   * @param {string|ArrayBuffer} data - Data to send
   * @returns {Promise<boolean>} - Whether data was sent successfully
   */
  async sendData(data) {
    if (!this.isConnected || !this.activeConnection) {
      this.logger.error('Cannot send data: no active connection');
      return false;
    }
    
    try {
      return await this.activeConnection.sendData(data);
    } catch (error) {
      this.logger.error('Error sending data:', error);
      this.eventEmitter.emit('connection:warning', {
        message: `Failed to send data: ${error.message}`,
        error
      });
      return false;
    }
  }
  
  /**
   * Check if device is connected
   * @returns {boolean} - Whether device is connected
   */
  isDeviceConnected() {
    return this.isConnected && this.activeConnection !== null;
  }
  
  /**
   * Get connection status information
   * @returns {Object} - Connection status
   */
  getConnectionInfo() {
    if (this.activeConnection) {
      return {
        connected: this.isConnected,
        connecting: this.isConnecting,
        method: this.activeConnection.name,
        deviceInfo: this.activeConnection.getDeviceInfo()
      };
    }
    
    return {
      connected: false,
      connecting: this.isConnecting,
      method: null,
      deviceInfo: null
    };
  }
}

export default ConnectionManager;