feat(companion): add Electron presentation layer with React UI, IPC handlers, and automation POC

This commit is contained in:
2025-11-21 16:49:14 +01:00
parent 098bfc2c11
commit bd60bd6771
18 changed files with 3888 additions and 171 deletions

View File

@@ -0,0 +1,96 @@
import { IAutomationEngine, ValidationResult } from '../../../packages/application/ports/IAutomationEngine';
import { HostedSessionConfig } from '../../../packages/domain/entities/HostedSessionConfig';
import { StepId } from '../../../packages/domain/value-objects/StepId';
import { IBrowserAutomation } from '../../../packages/application/ports/IBrowserAutomation';
import { ISessionRepository } from '../../../packages/application/ports/ISessionRepository';
export class MockAutomationEngineAdapter implements IAutomationEngine {
private automationInterval: NodeJS.Timeout | 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 {
this.automationInterval = setInterval(async () => {
try {
const sessions = await this.sessionRepository.findAll();
const session = sessions[0];
if (!session || !session.state.isInProgress()) {
if (this.automationInterval) {
clearInterval(this.automationInterval);
this.automationInterval = null;
}
return;
}
const currentStep = session.currentStep;
// Execute current step (simulate browser automation)
if (typeof (this.browserAutomation as any).executeStep === 'function') {
await (this.browserAutomation as any).executeStep(currentStep, config);
} else {
// Fallback to basic operations
await this.browserAutomation.navigateToPage(`step-${currentStep.value}`);
}
// Transition to next step if not final
if (!currentStep.isFinalStep()) {
session.transitionToStep(currentStep.next());
await this.sessionRepository.update(session);
} else {
// Stop at step 18
if (this.automationInterval) {
clearInterval(this.automationInterval);
this.automationInterval = null;
}
}
} catch (error) {
console.error('Automation error:', error);
if (this.automationInterval) {
clearInterval(this.automationInterval);
this.automationInterval = null;
}
}
}, 500); // Execute each step every 500ms
}
public stopAutomation(): void {
if (this.automationInterval) {
clearInterval(this.automationInterval);
this.automationInterval = null;
}
}
}