import { InMemorySessionRepository } from '@/packages/infrastructure/repositories/InMemorySessionRepository'; import { MockBrowserAutomationAdapter } from '@/packages/infrastructure/adapters/automation/MockBrowserAutomationAdapter'; import { BrowserDevToolsAdapter } from '@/packages/infrastructure/adapters/automation/BrowserDevToolsAdapter'; import { NutJsAutomationAdapter } from '@/packages/infrastructure/adapters/automation/NutJsAutomationAdapter'; import { MockAutomationEngineAdapter } from '@/packages/infrastructure/adapters/automation/MockAutomationEngineAdapter'; import { StartAutomationSessionUseCase } from '@/packages/application/use-cases/StartAutomationSessionUseCase'; import { loadAutomationConfig, AutomationMode } from '@/packages/infrastructure/config'; import { PinoLogAdapter } from '@/packages/infrastructure/adapters/logging/PinoLogAdapter'; import { NoOpLogAdapter } from '@/packages/infrastructure/adapters/logging/NoOpLogAdapter'; import { loadLoggingConfig } from '@/packages/infrastructure/config/LoggingConfig'; import type { ISessionRepository } from '@/packages/application/ports/ISessionRepository'; import type { IBrowserAutomation } from '@/packages/application/ports/IBrowserAutomation'; import type { IAutomationEngine } from '@/packages/application/ports/IAutomationEngine'; import type { ILogger } from '@/packages/application/ports/ILogger'; export interface BrowserConnectionResult { success: boolean; error?: string; } /** * Create logger based on environment configuration. * In test environment, returns NoOpLogAdapter for silent logging. */ function createLogger(): ILogger { const config = loadLoggingConfig(); if (process.env.NODE_ENV === 'test') { return new NoOpLogAdapter(); } return new PinoLogAdapter(config); } /** * Create browser automation adapter based on configuration mode. * * @param mode - The automation mode from configuration * @param logger - Logger instance for the adapter * @returns IBrowserAutomation adapter instance */ function createBrowserAutomationAdapter(mode: AutomationMode, logger: ILogger): IBrowserAutomation { const config = loadAutomationConfig(); switch (mode) { case 'dev': return new BrowserDevToolsAdapter({ debuggingPort: config.devTools?.debuggingPort, browserWSEndpoint: config.devTools?.browserWSEndpoint, defaultTimeout: config.defaultTimeout, }, logger.child({ adapter: 'BrowserDevTools' })); case 'production': return new NutJsAutomationAdapter({ mouseSpeed: config.nutJs?.mouseSpeed, keyboardDelay: config.nutJs?.keyboardDelay, defaultTimeout: config.defaultTimeout, }, logger.child({ adapter: 'NutJs' })); case 'mock': default: return new MockBrowserAutomationAdapter(); } } export class DIContainer { private static instance: DIContainer; private logger: ILogger; private sessionRepository: ISessionRepository; private browserAutomation: IBrowserAutomation; private automationEngine: IAutomationEngine; private startAutomationUseCase: StartAutomationSessionUseCase; private automationMode: AutomationMode; private constructor() { // Initialize logger first - it's needed by other components this.logger = createLogger(); this.logger.info('DIContainer initializing', { automationMode: process.env.AUTOMATION_MODE }); const config = loadAutomationConfig(); this.automationMode = config.mode; this.sessionRepository = new InMemorySessionRepository(); this.browserAutomation = createBrowserAutomationAdapter(config.mode, this.logger); this.automationEngine = new MockAutomationEngineAdapter( this.browserAutomation, this.sessionRepository ); this.startAutomationUseCase = new StartAutomationSessionUseCase( this.automationEngine, this.browserAutomation, this.sessionRepository ); this.logger.info('DIContainer initialized', { automationMode: config.mode, sessionRepositoryType: 'InMemorySessionRepository', browserAutomationType: this.getBrowserAutomationType(config.mode) }); } private getBrowserAutomationType(mode: AutomationMode): string { switch (mode) { case 'dev': return 'BrowserDevToolsAdapter'; case 'production': return 'NutJsAutomationAdapter'; default: return 'MockBrowserAutomationAdapter'; } } public static getInstance(): DIContainer { if (!DIContainer.instance) { DIContainer.instance = new DIContainer(); } return DIContainer.instance; } public getStartAutomationUseCase(): StartAutomationSessionUseCase { return this.startAutomationUseCase; } public getSessionRepository(): ISessionRepository { return this.sessionRepository; } public getAutomationEngine(): IAutomationEngine { return this.automationEngine; } public getAutomationMode(): AutomationMode { return this.automationMode; } public getBrowserAutomation(): IBrowserAutomation { return this.browserAutomation; } public getLogger(): ILogger { return this.logger; } /** * Initialize browser connection for dev mode. * In dev mode, connects to the browser via Chrome DevTools Protocol. * In mock mode, returns success immediately (no connection needed). */ public async initializeBrowserConnection(): Promise { this.logger.info('Initializing browser connection', { mode: this.automationMode }); if (this.automationMode === 'dev') { try { const devToolsAdapter = this.browserAutomation as BrowserDevToolsAdapter; await devToolsAdapter.connect(); this.logger.info('Browser connection established', { mode: 'dev', adapter: 'BrowserDevTools' }); return { success: true }; } catch (error) { const errorMsg = error instanceof Error ? error.message : 'Failed to connect to browser'; this.logger.error('Browser connection failed', error instanceof Error ? error : new Error(errorMsg), { mode: 'dev' }); return { success: false, error: errorMsg }; } } if (this.automationMode === 'production') { try { const nutJsAdapter = this.browserAutomation as NutJsAutomationAdapter; const result = await nutJsAdapter.connect(); if (!result.success) { this.logger.error('Browser connection failed', new Error(result.error || 'Unknown error'), { mode: 'production' }); return { success: false, error: result.error }; } this.logger.info('Browser connection established', { mode: 'production', adapter: 'NutJs' }); return { success: true }; } catch (error) { const errorMsg = error instanceof Error ? error.message : 'Failed to initialize nut.js'; this.logger.error('Browser connection failed', error instanceof Error ? error : new Error(errorMsg), { mode: 'production' }); return { success: false, error: errorMsg }; } } this.logger.debug('Mock mode - no browser connection needed'); return { success: true }; // Mock mode doesn't need connection } /** * Reset the singleton instance (useful for testing with different configurations). */ public static resetInstance(): void { DIContainer.instance = undefined as unknown as DIContainer; } }