Files
gridpilot.gg/packages/infrastructure/adapters/automation/engine/AutomationEngineAdapter.ts
2025-12-01 17:27:56 +01:00

152 lines
5.6 KiB
TypeScript

import { IAutomationEngine, ValidationResult } from '../../../../application/ports/IAutomationEngine';
import { HostedSessionConfig } from '../../../../domain/entities/HostedSessionConfig';
import { StepId } from '../../../../domain/value-objects/StepId';
import type { IBrowserAutomation } from '../../../../application/ports/IScreenAutomation';
import { ISessionRepository } from '../../../../application/ports/ISessionRepository';
import { StepTransitionValidator } from '../../../../domain/services/StepTransitionValidator';
/**
* Real Automation Engine Adapter.
*
* Orchestrates the automation workflow by:
* 1. Validating session configuration
* 2. Executing each step using real browser automation
* 3. Managing session state transitions
*
* This is a REAL implementation that uses actual automation,
* not a mock. Historically delegated to legacy native screen
* automation adapters, but those are no longer part of the
* supported stack.
*
* @deprecated This adapter should be updated to use Playwright
* browser automation when available. See docs/ARCHITECTURE.md
* for the updated automation strategy.
*/
export class AutomationEngineAdapter implements IAutomationEngine {
private isRunning = false;
private automationPromise: Promise<void> | null = null;
constructor(
private readonly browserAutomation: IBrowserAutomation,
private readonly sessionRepository: ISessionRepository
) {}
async validateConfiguration(config: HostedSessionConfig): Promise<ValidationResult> {
if (!config.sessionName || config.sessionName.trim() === '') {
return { isValid: false, error: 'Session name is required' };
}
if (!config.trackId || config.trackId.trim() === '') {
return { isValid: false, error: 'Track ID is required' };
}
if (!config.carIds || config.carIds.length === 0) {
return { isValid: false, error: 'At least one car must be selected' };
}
return { isValid: true };
}
async executeStep(stepId: StepId, config: HostedSessionConfig): Promise<void> {
const sessions = await this.sessionRepository.findAll();
const session = sessions[0];
if (!session) {
throw new Error('No active session found');
}
// Start session if it's at step 1 and pending
if (session.state.isPending() && stepId.value === 1) {
session.start();
await this.sessionRepository.update(session);
// Start automated progression
this.startAutomation(config);
}
}
private startAutomation(config: HostedSessionConfig): void {
if (this.isRunning) {
return;
}
this.isRunning = true;
this.automationPromise = this.runAutomationLoop(config);
}
private async runAutomationLoop(config: HostedSessionConfig): Promise<void> {
while (this.isRunning) {
try {
const sessions = await this.sessionRepository.findAll();
const session = sessions[0];
if (!session || !session.state.isInProgress()) {
this.isRunning = false;
return;
}
const currentStep = session.currentStep;
// Execute current step using the browser automation
if (this.browserAutomation.executeStep) {
const result = await this.browserAutomation.executeStep(currentStep, config as unknown as Record<string, unknown>);
if (!result.success) {
const stepDescription = StepTransitionValidator.getStepDescription(currentStep);
const errorMessage = `Step ${currentStep.value} (${stepDescription}) failed: ${result.error}`;
console.error(errorMessage);
// Stop automation and mark session as failed
this.isRunning = false;
session.fail(errorMessage);
await this.sessionRepository.update(session);
return;
}
} else {
// Fallback for adapters without executeStep
await this.browserAutomation.navigateToPage(`step-${currentStep.value}`);
}
// Transition to next step
if (!currentStep.isFinalStep()) {
session.transitionToStep(currentStep.next());
await this.sessionRepository.update(session);
// If we just transitioned to the final step, execute it before stopping
const nextStep = session.currentStep;
if (nextStep.isFinalStep()) {
// Execute final step handler
if (this.browserAutomation.executeStep) {
const result = await this.browserAutomation.executeStep(nextStep, config as unknown as Record<string, unknown>);
if (!result.success) {
const stepDescription = StepTransitionValidator.getStepDescription(nextStep);
const errorMessage = `Step ${nextStep.value} (${stepDescription}) failed: ${result.error}`;
console.error(errorMessage);
// Don't try to fail terminal session - just log the error
// Session is already in STOPPED_AT_STEP_18 state after transitionToStep()
}
}
// Stop after final step
this.isRunning = false;
return;
}
} else {
// Current step is already final - stop
this.isRunning = false;
return;
}
// Wait before next iteration
await this.delay(500);
} catch (error) {
console.error('Automation error:', error);
this.isRunning = false;
return;
}
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
public stopAutomation(): void {
this.isRunning = false;
this.automationPromise = null;
}
}