import 'reflect-metadata'; import { configureDIContainer, resetDIContainer, getDIContainer, resolveSessionDataPath, resolveTemplatePath } from './di-config'; import { DI_TOKENS } from './di-tokens'; import { PlaywrightAutomationAdapter, FixtureServer } from '@gridpilot/automation/infrastructure/adapters/automation'; import { StartAutomationSessionUseCase } from '@gridpilot/automation/application/use-cases/StartAutomationSessionUseCase'; import { CheckAuthenticationUseCase } from '@gridpilot/automation/application/use-cases/CheckAuthenticationUseCase'; import { InitiateLoginUseCase } from '@gridpilot/automation/application/use-cases/InitiateLoginUseCase'; import { ClearSessionUseCase } from '@gridpilot/automation/application/use-cases/ClearSessionUseCase'; import { ConfirmCheckoutUseCase } from '@gridpilot/automation/application/use-cases/ConfirmCheckoutUseCase'; import { getAutomationMode, AutomationMode, BrowserModeConfigLoader } from '@gridpilot/automation/infrastructure/config'; import type { SessionRepositoryPort } from '@gridpilot/automation/application/ports/SessionRepositoryPort'; import type { IBrowserAutomation } from '@gridpilot/automation/application/ports/ScreenAutomationPort'; import type { AutomationEnginePort } from '@gridpilot/automation/application/ports/AutomationEnginePort'; import type { AuthenticationServicePort } from '@gridpilot/automation/application/ports/AuthenticationServicePort'; import type { CheckoutConfirmationPort } from '@gridpilot/automation/application/ports/CheckoutConfirmationPort'; import type { CheckoutServicePort } from '@gridpilot/automation/application/ports/CheckoutServicePort'; import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort'; import type { OverlaySyncPort } from '@gridpilot/automation/application/ports/OverlaySyncPort'; // Re-export for backward compatibility export { resolveSessionDataPath, resolveTemplatePath }; export interface BrowserConnectionResult { success: boolean; error?: string; } /** * Check if running in fixture hosted mode */ function isFixtureHostedMode(): boolean { return process.env.NODE_ENV === 'test' && process.env.COMPANION_FIXTURE_HOSTED === '1'; } /** * DIContainer - Facade over TSyringe container for backward compatibility */ export class DIContainer { private static instance: DIContainer; private initialized = false; private automationMode: AutomationMode; private fixtureServer: FixtureServer | null = null; private confirmCheckoutUseCase: ConfirmCheckoutUseCase | null = null; private constructor() { this.automationMode = getAutomationMode(); } /** * Lazily initialize the TSyringe container */ private ensureInitialized(): void { if (this.initialized) return; configureDIContainer(); const logger = getDIContainer().resolve(DI_TOKENS.Logger); logger.info('DIContainer initialized', { automationMode: this.automationMode, nodeEnv: process.env.NODE_ENV, }); const fixtureMode = isFixtureHostedMode(); if (fixtureMode) { try { this.fixtureServer = getDIContainer().resolve(DI_TOKENS.FixtureServer); } catch { // FixtureServer not registered in non-fixture mode } } this.initialized = true; } public static getInstance(): DIContainer { if (!DIContainer.instance) { DIContainer.instance = new DIContainer(); } return DIContainer.instance; } public getStartAutomationUseCase(): StartAutomationSessionUseCase { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.StartAutomationUseCase); } public getSessionRepository(): SessionRepositoryPort { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.SessionRepository); } public getAutomationEngine(): AutomationEnginePort { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.AutomationEngine); } public getAutomationMode(): AutomationMode { return this.automationMode; } public getBrowserAutomation(): IBrowserAutomation { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.BrowserAutomation); } public getLogger(): LoggerPort { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.Logger); } public getCheckAuthenticationUseCase(): CheckAuthenticationUseCase | null { this.ensureInitialized(); try { return getDIContainer().resolve(DI_TOKENS.CheckAuthenticationUseCase); } catch { return null; } } public getInitiateLoginUseCase(): InitiateLoginUseCase | null { this.ensureInitialized(); try { return getDIContainer().resolve(DI_TOKENS.InitiateLoginUseCase); } catch { return null; } } public getClearSessionUseCase(): ClearSessionUseCase | null { this.ensureInitialized(); try { return getDIContainer().resolve(DI_TOKENS.ClearSessionUseCase); } catch { return null; } } public getAuthenticationService(): AuthenticationServicePort | null { this.ensureInitialized(); try { return getDIContainer().resolve(DI_TOKENS.AuthenticationService); } catch { return null; } } public setConfirmCheckoutUseCase(checkoutConfirmationPort: CheckoutConfirmationPort): void { this.ensureInitialized(); const checkoutService = getDIContainer().resolve(DI_TOKENS.CheckoutService); this.confirmCheckoutUseCase = new ConfirmCheckoutUseCase( checkoutService, checkoutConfirmationPort ); } public getConfirmCheckoutUseCase(): ConfirmCheckoutUseCase | null { return this.confirmCheckoutUseCase; } public getBrowserModeConfigLoader(): BrowserModeConfigLoader { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.BrowserModeConfigLoader); } public getOverlaySyncPort(): OverlaySyncPort { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.OverlaySyncPort); } public async initializeBrowserConnection(): Promise { this.ensureInitialized(); const fixtureMode = isFixtureHostedMode(); const logger = this.getLogger(); logger.info('Initializing automation connection', { mode: this.automationMode, fixtureMode }); if (this.automationMode === 'production' || this.automationMode === 'development' || fixtureMode) { try { if (fixtureMode && this.fixtureServer && !this.fixtureServer.isRunning()) { await this.fixtureServer.start(); } const browserAutomation = this.getBrowserAutomation(); const playwrightAdapter = browserAutomation as PlaywrightAutomationAdapter; const result = await playwrightAdapter.connect(); if (!result.success) { logger.error( 'Automation connection failed', new Error(result.error || 'Unknown error'), { mode: this.automationMode } ); return { success: false, error: result.error ?? 'Unknown error' }; } const isConnected = playwrightAdapter.isConnected(); const page = playwrightAdapter.getPage(); if (!isConnected || !page) { const errorMsg = 'Browser not connected'; logger.error( 'Automation connection reported success but has no usable page', new Error(errorMsg), { mode: this.automationMode, isConnected, hasPage: !!page } ); return { success: false, error: errorMsg }; } logger.info('Automation connection established', { mode: this.automationMode, adapter: 'Playwright', }); return { success: true }; } catch (error) { const errorMsg = error instanceof Error ? error.message : 'Failed to initialize Playwright'; logger.error( 'Automation connection failed', error instanceof Error ? error : new Error(errorMsg), { mode: this.automationMode } ); return { success: false, error: errorMsg, }; } } logger.debug('Test mode - no automation connection needed'); return { success: true }; } public async shutdown(): Promise { this.ensureInitialized(); const logger = this.getLogger(); logger.info('DIContainer shutting down'); const browserAutomation = this.getBrowserAutomation(); if (browserAutomation && 'disconnect' in browserAutomation) { try { await (browserAutomation as PlaywrightAutomationAdapter).disconnect(); logger.info('Automation adapter disconnected'); } catch (error) { logger.error( 'Error disconnecting automation adapter', error instanceof Error ? error : new Error('Unknown error') ); } } if (this.fixtureServer && this.fixtureServer.isRunning()) { try { await this.fixtureServer.stop(); logger.info('FixtureServer stopped'); } catch (error) { logger.error('Error stopping FixtureServer', error instanceof Error ? error : new Error('Unknown error')); } finally { this.fixtureServer = null; } } logger.info('DIContainer shutdown complete'); } public refreshBrowserAutomation(): void { this.ensureInitialized(); // Reconfigure the entire container to pick up new browser mode settings resetDIContainer(); this.initialized = false; this.ensureInitialized(); const logger = this.getLogger(); const browserModeConfigLoader = this.getBrowserModeConfigLoader(); logger.info('Browser automation refreshed from updated BrowserModeConfigLoader', { browserMode: browserModeConfigLoader.load().mode, }); } public static resetInstance(): void { resetDIContainer(); DIContainer.instance = undefined as unknown as DIContainer; } }