This commit is contained in:
2025-12-04 15:15:24 +01:00
parent b7d5551ea7
commit c698a0b893
119 changed files with 1167 additions and 2652 deletions

View File

@@ -0,0 +1,4 @@
export interface AutomationEngineValidationResultDTO {
isValid: boolean;
error?: string;
}

View File

@@ -0,0 +1,5 @@
export interface AutomationResultDTO {
success: boolean;
error?: string;
metadata?: Record<string, unknown>;
}

View File

@@ -0,0 +1,13 @@
import { CheckoutPrice } from '../../domain/value-objects/CheckoutPrice';
import { CheckoutState } from '../../domain/value-objects/CheckoutState';
export interface CheckoutConfirmationRequestDTO {
price: CheckoutPrice;
state: CheckoutState;
sessionMetadata: {
sessionName: string;
trackId: string;
carIds: string[];
};
timeoutMs: number;
}

View File

@@ -0,0 +1,8 @@
import { CheckoutPrice } from '../../domain/value-objects/CheckoutPrice';
import { CheckoutState } from '../../domain/value-objects/CheckoutState';
export interface CheckoutInfoDTO {
price: CheckoutPrice | null;
state: CheckoutState;
buttonHtml: string;
}

View File

@@ -0,0 +1,5 @@
import type { AutomationResultDTO } from './AutomationResultDTO';
export interface ClickResultDTO extends AutomationResultDTO {
target: string;
}

View File

@@ -0,0 +1,6 @@
import type { AutomationResultDTO } from './AutomationResultDTO';
export interface FormFillResultDTO extends AutomationResultDTO {
fieldName: string;
valueSet: string;
}

View File

@@ -0,0 +1,6 @@
import type { AutomationResultDTO } from './AutomationResultDTO';
export interface ModalResultDTO extends AutomationResultDTO {
stepId: number;
action: string;
}

View File

@@ -0,0 +1,6 @@
import type { AutomationResultDTO } from './AutomationResultDTO';
export interface NavigationResultDTO extends AutomationResultDTO {
url: string;
loadTime: number;
}

View File

@@ -0,0 +1,11 @@
import type { HostedSessionConfig } from '../../domain/entities/HostedSessionConfig';
export interface SessionDTO {
sessionId: string;
state: string;
currentStep: number;
config: HostedSessionConfig;
startedAt?: Date;
completedAt?: Date;
errorMessage?: string;
}

View File

@@ -0,0 +1,7 @@
import type { AutomationResultDTO } from './AutomationResultDTO';
export interface WaitResultDTO extends AutomationResultDTO {
target: string;
waitedMs: number;
found: boolean;
}

View File

@@ -1,6 +1,6 @@
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
import { BrowserAuthenticationState } from '@gridpilot/automation/domain/value-objects/BrowserAuthenticationState';
import { Result } from '../../shared/result/Result';
import { AuthenticationState } from '../../domain/value-objects/AuthenticationState';
import { BrowserAuthenticationState } from '../../domain/value-objects/BrowserAuthenticationState';
import { Result } from '../../../shared/result/Result';
/**
* Port for authentication services implementing zero-knowledge login.
@@ -10,7 +10,7 @@ import { Result } from '../../shared/result/Result';
* the user logs in directly with iRacing. GridPilot only observes
* URL changes to detect successful authentication.
*/
export interface IAuthenticationService {
export interface AuthenticationServicePort {
/**
* Check if user has a valid session without prompting login.
* Navigates to a protected iRacing page and checks for login redirects.

View File

@@ -0,0 +1,9 @@
import { HostedSessionConfig } from '../../domain/entities/HostedSessionConfig';
import { StepId } from '../../domain/value-objects/StepId';
import type { AutomationEngineValidationResultDTO } from '../dto/AutomationEngineValidationResultDTO';
export interface AutomationEnginePort {
validateConfiguration(config: HostedSessionConfig): Promise<AutomationEngineValidationResultDTO>;
executeStep(stepId: StepId, config: HostedSessionConfig): Promise<void>;
stopAutomation(): void;
}

View File

@@ -5,6 +5,6 @@ export type AutomationEvent = {
payload?: any
}
export interface IAutomationEventPublisher {
export interface AutomationEventPublisherPort {
publish(event: AutomationEvent): Promise<void>
}

View File

@@ -1,30 +0,0 @@
export interface AutomationResult {
success: boolean;
error?: string;
metadata?: Record<string, unknown>;
}
export interface NavigationResult extends AutomationResult {
url: string;
loadTime: number;
}
export interface FormFillResult extends AutomationResult {
fieldName: string;
valueSet: string;
}
export interface ClickResult extends AutomationResult {
target: string;
}
export interface WaitResult extends AutomationResult {
target: string;
waitedMs: number;
found: boolean;
}
export interface ModalResult extends AutomationResult {
stepId: number;
action: string;
}

View File

@@ -0,0 +1,9 @@
import { Result } from '../../../shared/result/Result';
import { CheckoutConfirmation } from '../../domain/value-objects/CheckoutConfirmation';
import type { CheckoutConfirmationRequestDTO } from '../dto/CheckoutConfirmationRequestDTO';
export interface CheckoutConfirmationPort {
requestCheckoutConfirmation(
request: CheckoutConfirmationRequestDTO
): Promise<Result<CheckoutConfirmation>>;
}

View File

@@ -0,0 +1,7 @@
import { Result } from '../../../shared/result/Result';
import type { CheckoutInfoDTO } from '../dto/CheckoutInfoDTO';
export interface CheckoutServicePort {
extractCheckoutInfo(): Promise<Result<CheckoutInfoDTO>>;
proceedWithCheckout(): Promise<Result<void>>;
}

View File

@@ -1,13 +0,0 @@
import { HostedSessionConfig } from '@gridpilot/automation/domain/entities/HostedSessionConfig';
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
export interface ValidationResult {
isValid: boolean;
error?: string;
}
export interface IAutomationEngine {
validateConfiguration(config: HostedSessionConfig): Promise<ValidationResult>;
executeStep(stepId: StepId, config: HostedSessionConfig): Promise<void>;
stopAutomation(): void;
}

View File

@@ -1,21 +0,0 @@
import { Result } from '../../shared/result/Result';
import { CheckoutConfirmation } from '@gridpilot/automation/domain/value-objects/CheckoutConfirmation';
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
import { CheckoutState } from '@gridpilot/automation/domain/value-objects/CheckoutState';
export interface CheckoutConfirmationRequest {
price: CheckoutPrice;
state: CheckoutState;
sessionMetadata: {
sessionName: string;
trackId: string;
carIds: string[];
};
timeoutMs: number;
}
export interface ICheckoutConfirmationPort {
requestCheckoutConfirmation(
request: CheckoutConfirmationRequest
): Promise<Result<CheckoutConfirmation>>;
}

View File

@@ -1,14 +0,0 @@
import { Result } from '../../shared/result/Result';
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
import { CheckoutState } from '@gridpilot/automation/domain/value-objects/CheckoutState';
export interface CheckoutInfo {
price: CheckoutPrice | null;
state: CheckoutState;
buttonHtml: string;
}
export interface ICheckoutService {
extractCheckoutInfo(): Promise<Result<CheckoutInfo>>;
proceedWithCheckout(): Promise<Result<void>>;
}

View File

@@ -1,35 +0,0 @@
/**
* Log levels in order of severity (lowest to highest)
*/
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
/**
* Contextual metadata attached to log entries
*/
export interface LogContext {
/** Unique session identifier for correlation */
sessionId?: string;
/** Current automation step (1-18) */
stepId?: number;
/** Step name for human readability */
stepName?: string;
/** Adapter or component name */
adapter?: string;
/** Operation duration in milliseconds */
durationMs?: number;
/** Additional arbitrary metadata */
[key: string]: unknown;
}
/**
* ILogger - Port interface for application-layer logging.
*/
export interface ILogger {
debug(message: string, context?: LogContext): void;
info(message: string, context?: LogContext): void;
warn(message: string, context?: LogContext): void;
error(message: string, error?: Error, context?: LogContext): void;
fatal(message: string, error?: Error, context?: LogContext): void;
child(context: LogContext): ILogger;
flush(): Promise<void>;
}

View File

@@ -0,0 +1,17 @@
/**
* Contextual metadata attached to log entries
*/
export interface LogContext {
/** Unique session identifier for correlation */
sessionId?: string;
/** Current automation step (1-18) */
stepId?: number;
/** Step name for human readability */
stepName?: string;
/** Adapter or component name */
adapter?: string;
/** Operation duration in milliseconds */
durationMs?: number;
/** Additional arbitrary metadata */
[key: string]: unknown;
}

View File

@@ -0,0 +1,4 @@
/**
* Log levels in order of severity (lowest to highest)
*/
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';

View File

@@ -0,0 +1,15 @@
import type { LogLevel } from './LoggerLogLevel';
import type { LogContext } from './LoggerContext';
/**
* LoggerPort - Port interface for application-layer logging.
*/
export interface LoggerPort {
debug(message: string, context?: LogContext): void;
info(message: string, context?: LogContext): void;
warn(message: string, context?: LogContext): void;
error(message: string, error?: Error, context?: LogContext): void;
fatal(message: string, error?: Error, context?: LogContext): void;
child(context: LogContext): LoggerPort;
flush(): Promise<void>;
}

View File

@@ -1,7 +1,7 @@
export type OverlayAction = { id: string; label: string; meta?: Record<string, unknown>; timeoutMs?: number }
export type ActionAck = { id: string; status: 'confirmed' | 'tentative' | 'failed'; reason?: string }
export interface IOverlaySyncPort {
export interface OverlaySyncPort {
startAction(action: OverlayAction): Promise<ActionAck>
cancelAction(actionId: string): Promise<void>
}

View File

@@ -1,12 +1,10 @@
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
import {
NavigationResult,
FormFillResult,
ClickResult,
WaitResult,
ModalResult,
AutomationResult,
} from './AutomationResults';
import { StepId } from '../../domain/value-objects/StepId';
import type { NavigationResultDTO } from '../dto/NavigationResultDTO';
import type { ClickResultDTO } from '../dto/ClickResultDTO';
import type { WaitResultDTO } from '../dto/WaitResultDTO';
import type { ModalResultDTO } from '../dto/ModalResultDTO';
import type { AutomationResultDTO } from '../dto/AutomationResultDTO';
import type { FormFillResultDTO } from '../dto/FormFillResultDTO';
/**
* Browser automation interface for Playwright-based automation.
@@ -19,38 +17,38 @@ export interface IBrowserAutomation {
/**
* Navigate to a URL.
*/
navigateToPage(url: string): Promise<NavigationResult>;
navigateToPage(url: string): Promise<NavigationResultDTO>;
/**
* Fill a form field by name or selector.
*/
fillFormField(fieldName: string, value: string): Promise<FormFillResult>;
fillFormField(fieldName: string, value: string): Promise<FormFillResultDTO>;
/**
* Click an element by selector or action name.
*/
clickElement(target: string): Promise<ClickResult>;
clickElement(target: string): Promise<ClickResultDTO>;
/**
* Wait for an element to appear.
*/
waitForElement(target: string, maxWaitMs?: number): Promise<WaitResult>;
waitForElement(target: string, maxWaitMs?: number): Promise<WaitResultDTO>;
/**
* Handle modal dialogs.
*/
handleModal(stepId: StepId, action: string): Promise<ModalResult>;
handleModal(stepId: StepId, action: string): Promise<ModalResultDTO>;
/**
* Execute a complete workflow step.
*/
executeStep?(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResult>;
executeStep?(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResultDTO>;
/**
* Initialize the browser connection.
* Returns an AutomationResult indicating success or failure.
*/
connect?(): Promise<AutomationResult>;
connect?(): Promise<AutomationResultDTO>;
/**
* Clean up browser resources.
@@ -62,9 +60,3 @@ export interface IBrowserAutomation {
*/
isConnected?(): boolean;
}
/**
* @deprecated Use IBrowserAutomation directly. IScreenAutomation was for OS-level
* automation which has been removed in favor of browser-only automation.
*/
export type IScreenAutomation = IBrowserAutomation;

View File

@@ -1,7 +1,7 @@
import { AutomationSession } from '@gridpilot/automation/domain/entities/AutomationSession';
import { SessionStateValue } from '@gridpilot/automation/domain/value-objects/SessionState';
import { AutomationSession } from '../../domain/entities/AutomationSession';
import { SessionStateValue } from '../../domain/value-objects/SessionState';
export interface ISessionRepository {
export interface SessionRepositoryPort {
save(session: AutomationSession): Promise<void>;
findById(id: string): Promise<AutomationSession | null>;
update(session: AutomationSession): Promise<void>;

View File

@@ -0,0 +1,5 @@
import type { Result } from '../../../shared/result/Result';
export interface SessionValidatorPort {
validateSession(): Promise<Result<boolean>>;
}

View File

@@ -1,22 +1,22 @@
import { IOverlaySyncPort, OverlayAction, ActionAck } from '../ports/IOverlaySyncPort';
import { IAutomationEventPublisher, AutomationEvent } from '../ports/IAutomationEventPublisher';
import { OverlaySyncPort, OverlayAction, ActionAck } from '../ports/OverlaySyncPort';
import { AutomationEventPublisherPort, AutomationEvent } from '../ports/AutomationEventPublisherPort';
import { IAutomationLifecycleEmitter, LifecycleCallback } from '../../infrastructure/adapters/IAutomationLifecycleEmitter';
import { ILogger } from '../ports/ILogger';
import { LoggerPort } from '../ports/LoggerPort';
type ConstructorArgs = {
lifecycleEmitter: IAutomationLifecycleEmitter
publisher: IAutomationEventPublisher
logger: ILogger
publisher: AutomationEventPublisherPort
logger: LoggerPort
initialPanelWaitMs?: number
maxPanelRetries?: number
backoffFactor?: number
defaultTimeoutMs?: number
}
export class OverlaySyncService implements IOverlaySyncPort {
export class OverlaySyncService implements OverlaySyncPort {
private lifecycleEmitter: IAutomationLifecycleEmitter
private publisher: IAutomationEventPublisher
private logger: ILogger
private publisher: AutomationEventPublisherPort
private logger: LoggerPort
private initialPanelWaitMs: number
private maxPanelRetries: number
private backoffFactor: number

View File

@@ -1,14 +1,8 @@
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
import { Result } from '../../shared/result/Result';
import type { IAuthenticationService } from '../ports/IAuthenticationService';
import { SessionLifetime } from '@gridpilot/automation/domain/value-objects/SessionLifetime';
/**
* Port for optional server-side session validation.
*/
export interface ISessionValidator {
validateSession(): Promise<Result<boolean>>;
}
import { AuthenticationState } from '../../domain/value-objects/AuthenticationState';
import { Result } from '../../../shared/result/Result';
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
import { SessionLifetime } from '../../domain/value-objects/SessionLifetime';
import type { SessionValidatorPort } from '../ports/SessionValidatorPort';
/**
* Use case for checking if the user has a valid iRacing session.
@@ -22,8 +16,8 @@ export interface ISessionValidator {
*/
export class CheckAuthenticationUseCase {
constructor(
private readonly authService: IAuthenticationService,
private readonly sessionValidator?: ISessionValidator
private readonly authService: AuthenticationServicePort,
private readonly sessionValidator?: SessionValidatorPort
) {}
/**

View File

@@ -1,5 +1,5 @@
import { Result } from '../../shared/result/Result';
import type { IAuthenticationService } from '../ports/IAuthenticationService';
import { Result } from '../../../shared/result/Result';
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
/**
* Use case for clearing the user's session (logout).
@@ -8,7 +8,7 @@ import type { IAuthenticationService } from '../ports/IAuthenticationService';
* the user out. The next automation attempt will require re-authentication.
*/
export class ClearSessionUseCase {
constructor(private readonly authService: IAuthenticationService) {}
constructor(private readonly authService: AuthenticationServicePort) {}
/**
* Execute the session clearing.

View File

@@ -1,9 +1,9 @@
import { Result } from '../../shared/result/Result';
import { RaceCreationResult } from '@gridpilot/automation/domain/value-objects/RaceCreationResult';
import type { ICheckoutService } from '../ports/ICheckoutService';
import { Result } from '../../../shared/result/Result';
import { RaceCreationResult } from '../../domain/value-objects/RaceCreationResult';
import type { CheckoutServicePort } from '../ports/CheckoutServicePort';
export class CompleteRaceCreationUseCase {
constructor(private readonly checkoutService: ICheckoutService) {}
constructor(private readonly checkoutService: CheckoutServicePort) {}
async execute(sessionId: string): Promise<Result<RaceCreationResult>> {
if (!sessionId || sessionId.trim() === '') {

View File

@@ -1,7 +1,7 @@
import { Result } from '../../shared/result/Result';
import { ICheckoutService } from '../ports/ICheckoutService';
import { ICheckoutConfirmationPort } from '../ports/ICheckoutConfirmationPort';
import { CheckoutStateEnum } from '@gridpilot/automation/domain/value-objects/CheckoutState';
import { Result } from '../../../shared/result/Result';
import type { CheckoutServicePort } from '../ports/CheckoutServicePort';
import type { CheckoutConfirmationPort } from '../ports/CheckoutConfirmationPort';
import { CheckoutStateEnum } from '../../domain/value-objects/CheckoutState';
interface SessionMetadata {
sessionName: string;
@@ -13,8 +13,8 @@ export class ConfirmCheckoutUseCase {
private static readonly DEFAULT_TIMEOUT_MS = 30000;
constructor(
private readonly checkoutService: ICheckoutService,
private readonly confirmationPort: ICheckoutConfirmationPort
private readonly checkoutService: CheckoutServicePort,
private readonly confirmationPort: CheckoutConfirmationPort
) {}
async execute(sessionMetadata?: SessionMetadata): Promise<Result<void>> {

View File

@@ -1,5 +1,5 @@
import { Result } from '../../shared/result/Result';
import type { IAuthenticationService } from '../ports/IAuthenticationService';
import { Result } from '../../../shared/result/Result';
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
/**
* Use case for initiating the manual login flow.
@@ -9,7 +9,7 @@ import type { IAuthenticationService } from '../ports/IAuthenticationService';
* indicating successful login.
*/
export class InitiateLoginUseCase {
constructor(private readonly authService: IAuthenticationService) {}
constructor(private readonly authService: AuthenticationServicePort) {}
/**
* Execute the login flow.

View File

@@ -1,24 +1,15 @@
import { AutomationSession } from '@gridpilot/automation/domain/entities/AutomationSession';
import { HostedSessionConfig } from '@gridpilot/automation/domain/entities/HostedSessionConfig';
import { IAutomationEngine } from '../ports/IAutomationEngine';
import type { IBrowserAutomation } from '../ports/IScreenAutomation';
import { ISessionRepository } from '../ports/ISessionRepository';
export interface SessionDTO {
sessionId: string;
state: string;
currentStep: number;
config: HostedSessionConfig;
startedAt?: Date;
completedAt?: Date;
errorMessage?: string;
}
import { AutomationSession } from '../../domain/entities/AutomationSession';
import { HostedSessionConfig } from '../../domain/entities/HostedSessionConfig';
import { AutomationEnginePort } from '../ports/AutomationEnginePort';
import type { IBrowserAutomation } from '../ports/ScreenAutomationPort';
import { SessionRepositoryPort } from '../ports/SessionRepositoryPort';
import type { SessionDTO } from '../dto/SessionDTO';
export class StartAutomationSessionUseCase {
constructor(
private readonly automationEngine: IAutomationEngine,
private readonly automationEngine: AutomationEnginePort,
private readonly browserAutomation: IBrowserAutomation,
private readonly sessionRepository: ISessionRepository
private readonly sessionRepository: SessionRepositoryPort
) {}
async execute(config: HostedSessionConfig): Promise<SessionDTO> {

View File

@@ -1,6 +1,6 @@
import { IAuthenticationService } from '../ports/IAuthenticationService';
import { Result } from '../../shared/result/Result';
import { BrowserAuthenticationState } from '@gridpilot/automation/domain/value-objects/BrowserAuthenticationState';
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
import { Result } from '../../../shared/result/Result';
import { BrowserAuthenticationState } from '../../domain/value-objects/BrowserAuthenticationState';
/**
* Use case for verifying browser shows authenticated page state.
@@ -8,7 +8,7 @@ import { BrowserAuthenticationState } from '@gridpilot/automation/domain/value-o
*/
export class VerifyAuthenticatedPageUseCase {
constructor(
private readonly authService: IAuthenticationService
private readonly authService: AuthenticationServicePort
) {}
async execute(): Promise<Result<BrowserAuthenticationState>> {