Files
gridpilot.gg/apps/companion/main/di-container.ts

281 lines
10 KiB
TypeScript

import 'reflect-metadata';
import { configureDIContainer, resetDIContainer, getDIContainer, resolveSessionDataPath, resolveTemplatePath } from './di-config';
import { DI_TOKENS } from './di-tokens';
import { PlaywrightAutomationAdapter, FixtureServer } from '@core/automation/infrastructure//automation';
import { StartAutomationSessionUseCase } from 'apps/companion/main/automation/application/use-cases/StartAutomationSessionUseCase';
import { CheckAuthenticationUseCase } from 'apps/companion/main/automation/application/use-cases/CheckAuthenticationUseCase';
import { InitiateLoginUseCase } from 'apps/companion/main/automation/application/use-cases/InitiateLoginUseCase';
import { ClearSessionUseCase } from 'apps/companion/main/automation/application/use-cases/ClearSessionUseCase';
import { ConfirmCheckoutUseCase } from 'apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase';
import { getAutomationMode, AutomationMode, BrowserModeConfigLoader } from '@core/automation/infrastructure/config';
import type { SessionRepositoryPort } from 'apps/companion/main/automation/application/ports/SessionRepositoryPort';
import type { IBrowserAutomation } from 'apps/companion/main/automation/application/ports/ScreenAutomationPort';
import type { AutomationEnginePort } from 'apps/companion/main/automation/application/ports/AutomationEnginePort';
import type { AuthenticationServicePort } from 'apps/companion/main/automation/application/ports/AuthenticationServicePort';
import type { CheckoutConfirmationPort } from 'apps/companion/main/automation/application/ports/CheckoutConfirmationPort';
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
import type { LoggerPort } from 'apps/companion/main/automation/application/ports/LoggerPort';
import type { OverlaySyncPort } from 'apps/companion/main/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<LoggerPort>(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<FixtureServer>(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<StartAutomationSessionUseCase>(DI_TOKENS.StartAutomationUseCase);
}
public getSessionRepository(): SessionRepositoryPort {
this.ensureInitialized();
return getDIContainer().resolve<SessionRepositoryPort>(DI_TOKENS.SessionRepository);
}
public getAutomationEngine(): AutomationEnginePort {
this.ensureInitialized();
return getDIContainer().resolve<AutomationEnginePort>(DI_TOKENS.AutomationEngine);
}
public getAutomationMode(): AutomationMode {
return this.automationMode;
}
public getBrowserAutomation(): IBrowserAutomation {
this.ensureInitialized();
return getDIContainer().resolve<IBrowserAutomation>(DI_TOKENS.BrowserAutomation);
}
public getLogger(): LoggerPort {
this.ensureInitialized();
return getDIContainer().resolve<LoggerPort>(DI_TOKENS.Logger);
}
public getCheckAuthenticationUseCase(): CheckAuthenticationUseCase | null {
this.ensureInitialized();
try {
return getDIContainer().resolve<CheckAuthenticationUseCase>(DI_TOKENS.CheckAuthenticationUseCase);
} catch {
return null;
}
}
public getInitiateLoginUseCase(): InitiateLoginUseCase | null {
this.ensureInitialized();
try {
return getDIContainer().resolve<InitiateLoginUseCase>(DI_TOKENS.InitiateLoginUseCase);
} catch {
return null;
}
}
public getClearSessionUseCase(): ClearSessionUseCase | null {
this.ensureInitialized();
try {
return getDIContainer().resolve<ClearSessionUseCase>(DI_TOKENS.ClearSessionUseCase);
} catch {
return null;
}
}
public getAuthenticationService(): AuthenticationServicePort | null {
this.ensureInitialized();
try {
return getDIContainer().resolve<AuthenticationServicePort>(DI_TOKENS.AuthenticationService);
} catch {
return null;
}
}
public setConfirmCheckoutUseCase(checkoutConfirmationPort: CheckoutConfirmationPort): void {
this.ensureInitialized();
const checkoutService = getDIContainer().resolve<CheckoutServicePort>(DI_TOKENS.CheckoutService);
this.confirmCheckoutUseCase = new ConfirmCheckoutUseCase(
checkoutService,
checkoutConfirmationPort
);
}
public getConfirmCheckoutUseCase(): ConfirmCheckoutUseCase | null {
return this.confirmCheckoutUseCase;
}
public getBrowserModeConfigLoader(): BrowserModeConfigLoader {
this.ensureInitialized();
return getDIContainer().resolve<BrowserModeConfigLoader>(DI_TOKENS.BrowserModeConfigLoader);
}
public getOverlaySyncPort(): OverlaySyncPort {
this.ensureInitialized();
return getDIContainer().resolve<OverlaySyncPort>(DI_TOKENS.OverlaySyncPort);
}
public async initializeBrowserConnection(): Promise<BrowserConnectionResult> {
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<void> {
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;
}
}