281 lines
9.9 KiB
TypeScript
281 lines
9.9 KiB
TypeScript
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//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<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;
|
|
}
|
|
} |