wip
This commit is contained in:
@@ -11,12 +11,14 @@ import type { AutomationEnginePort } from '@gridpilot/automation/application/por
|
||||
import type { AuthenticationServicePort } from '@gridpilot/automation/application/ports/AuthenticationServicePort';
|
||||
import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
||||
import type { OverlaySyncPort } from '@gridpilot/automation/application/ports/OverlaySyncPort';
|
||||
import type { CheckoutServicePort } from '@gridpilot/automation/application/ports/CheckoutServicePort';
|
||||
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 { OverlaySyncService } from '@gridpilot/automation/application/services/OverlaySyncService';
|
||||
import type { IAutomationLifecycleEmitter } from '@gridpilot/automation/infrastructure/adapters/IAutomationLifecycleEmitter';
|
||||
|
||||
// Infrastructure
|
||||
import { InMemorySessionRepository } from '@gridpilot/automation/infrastructure/repositories/InMemorySessionRepository';
|
||||
@@ -187,13 +189,19 @@ export function configureDIContainer(): void {
|
||||
browserAutomation
|
||||
);
|
||||
|
||||
// Checkout Service (singleton, backed by browser automation)
|
||||
container.registerInstance<CheckoutServicePort>(
|
||||
DI_TOKENS.CheckoutService,
|
||||
browserAutomation as unknown as CheckoutServicePort
|
||||
);
|
||||
|
||||
// Automation Engine (singleton)
|
||||
const sessionRepository = container.resolve<SessionRepositoryPort>(DI_TOKENS.SessionRepository);
|
||||
let automationEngine: AutomationEnginePort;
|
||||
|
||||
if (fixtureMode) {
|
||||
automationEngine = new AutomationEngineAdapter(
|
||||
browserAutomation as any,
|
||||
browserAutomation,
|
||||
sessionRepository
|
||||
);
|
||||
} else {
|
||||
@@ -247,16 +255,16 @@ export function configureDIContainer(): void {
|
||||
}
|
||||
|
||||
// Overlay Sync Service - create singleton instance directly
|
||||
const lifecycleEmitter = browserAutomation as any;
|
||||
const lifecycleEmitter = browserAutomation as unknown as IAutomationLifecycleEmitter;
|
||||
const publisher = {
|
||||
publish: async (_event: any) => {
|
||||
publish: async (event: unknown) => {
|
||||
try {
|
||||
logger.debug?.('OverlaySyncPublisher.publish', _event);
|
||||
logger.debug?.('OverlaySyncPublisher.publish', { event });
|
||||
} catch {
|
||||
// swallow
|
||||
}
|
||||
},
|
||||
} as any;
|
||||
};
|
||||
const overlaySyncService = new OverlaySyncService({
|
||||
lifecycleEmitter,
|
||||
publisher,
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { IBrowserAutomation } from '@gridpilot/automation/application/ports
|
||||
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';
|
||||
|
||||
@@ -145,9 +146,9 @@ export class DIContainer {
|
||||
|
||||
public setConfirmCheckoutUseCase(checkoutConfirmationPort: CheckoutConfirmationPort): void {
|
||||
this.ensureInitialized();
|
||||
const browserAutomation = getDIContainer().resolve<IBrowserAutomation>(DI_TOKENS.BrowserAutomation);
|
||||
const checkoutService = getDIContainer().resolve<CheckoutServicePort>(DI_TOKENS.CheckoutService);
|
||||
this.confirmCheckoutUseCase = new ConfirmCheckoutUseCase(
|
||||
browserAutomation as any,
|
||||
checkoutService,
|
||||
checkoutConfirmationPort
|
||||
);
|
||||
}
|
||||
@@ -188,7 +189,7 @@ export class DIContainer {
|
||||
new Error(result.error || 'Unknown error'),
|
||||
{ mode: this.automationMode }
|
||||
);
|
||||
return { success: false, error: result.error };
|
||||
return { success: false, error: result.error ?? 'Unknown error' };
|
||||
}
|
||||
|
||||
const isConnected = playwrightAdapter.isConnected();
|
||||
|
||||
@@ -27,6 +27,7 @@ export const DI_TOKENS = {
|
||||
|
||||
// Services
|
||||
OverlaySyncPort: Symbol.for('OverlaySyncPort'),
|
||||
CheckoutService: Symbol.for('CheckoutServicePort'),
|
||||
|
||||
// Infrastructure
|
||||
FixtureServer: Symbol.for('FixtureServer'),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import type { BrowserWindow, IpcMainInvokeEvent } from 'electron';
|
||||
import { DIContainer } from './di-container';
|
||||
import type { HostedSessionConfig } from '@/packages/domain/entities/HostedSessionConfig';
|
||||
import { StepId } from '@/packages/domain/value-objects/StepId';
|
||||
import { AuthenticationState } from '@/packages/domain/value-objects/AuthenticationState';
|
||||
import { ElectronCheckoutConfirmationAdapter } from '@/packages/infrastructure/adapters/ipc/ElectronCheckoutConfirmationAdapter';
|
||||
import type { HostedSessionConfig } from 'packages/automation/domain/types/HostedSessionConfig';
|
||||
import { StepId } from 'packages/automation/domain/value-objects/StepId';
|
||||
import { AuthenticationState } from 'packages/automation/domain/value-objects/AuthenticationState';
|
||||
import { ElectronCheckoutConfirmationAdapter } from 'packages/automation/infrastructure/adapters/ipc/ElectronCheckoutConfirmationAdapter';
|
||||
import type { OverlayAction } from 'packages/automation/application/ports/OverlaySyncPort';
|
||||
import type { IAutomationLifecycleEmitter } from 'packages/automation/infrastructure/adapters/IAutomationLifecycleEmitter';
|
||||
|
||||
let progressMonitorInterval: NodeJS.Timeout | null = null;
|
||||
let lifecycleSubscribed = false;
|
||||
@@ -95,8 +97,8 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
|
||||
}
|
||||
|
||||
// Call confirmLoginComplete on the adapter if it exists
|
||||
if ('confirmLoginComplete' in authService) {
|
||||
const result = await (authService as any).confirmLoginComplete();
|
||||
if ('confirmLoginComplete' in authService && typeof authService.confirmLoginComplete === 'function') {
|
||||
const result = await authService.confirmLoginComplete();
|
||||
if (result.isErr()) {
|
||||
logger.error('Confirm login failed', result.unwrapErr());
|
||||
return { success: false, error: result.unwrapErr().message };
|
||||
@@ -334,7 +336,7 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
|
||||
// Ensure runtime automation wiring reflects the new browser mode
|
||||
if ('refreshBrowserAutomation' in container) {
|
||||
// Call method to refresh adapters/use-cases that depend on browser mode
|
||||
(container as any).refreshBrowserAutomation();
|
||||
container.refreshBrowserAutomation();
|
||||
}
|
||||
logger.info('Browser mode updated', { mode });
|
||||
return { success: true, mode };
|
||||
@@ -349,9 +351,9 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
|
||||
});
|
||||
|
||||
// Handle overlay action requests from renderer and forward to the OverlaySyncService
|
||||
ipcMain.handle('overlay-action-request', async (_event: IpcMainInvokeEvent, action: any) => {
|
||||
ipcMain.handle('overlay-action-request', async (_event: IpcMainInvokeEvent, action: OverlayAction) => {
|
||||
try {
|
||||
const overlayPort = (container as any).getOverlaySyncPort ? container.getOverlaySyncPort() : null;
|
||||
const overlayPort = 'getOverlaySyncPort' in container ? container.getOverlaySyncPort() : null;
|
||||
if (!overlayPort) {
|
||||
logger.warn('OverlaySyncPort not available');
|
||||
return { id: action?.id ?? 'unknown', status: 'failed', reason: 'OverlaySyncPort not available' };
|
||||
@@ -361,16 +363,20 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
|
||||
} catch (e) {
|
||||
const err = e instanceof Error ? e : new Error(String(e));
|
||||
logger.error('Overlay action request failed', err);
|
||||
return { id: action?.id ?? 'unknown', status: 'failed', reason: err.message };
|
||||
const id = typeof action === 'object' && action !== null && 'id' in action
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(action as { id?: string }).id ?? 'unknown'
|
||||
: 'unknown';
|
||||
return { id, status: 'failed', reason: err.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to automation adapter lifecycle events and relay to renderer
|
||||
try {
|
||||
if (!lifecycleSubscribed) {
|
||||
const browserAutomation = container.getBrowserAutomation() as any;
|
||||
if (browserAutomation && typeof browserAutomation.onLifecycle === 'function') {
|
||||
browserAutomation.onLifecycle((ev: any) => {
|
||||
const lifecycleEmitter = container.getBrowserAutomation() as unknown as IAutomationLifecycleEmitter;
|
||||
if (typeof lifecycleEmitter.onLifecycle === 'function') {
|
||||
lifecycleEmitter.onLifecycle((ev) => {
|
||||
try {
|
||||
if (mainWindow && mainWindow.webContents) {
|
||||
mainWindow.webContents.send('automation-event', ev);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
import type { HostedSessionConfig } from '../../../packages/automation/domain/types/HostedSessionConfig';
|
||||
import type { AuthenticationState } from '../../../packages/domain/value-objects/AuthenticationState';
|
||||
import type { AuthenticationState } from '../../../packages/automation/domain/value-objects/AuthenticationState';
|
||||
|
||||
export interface AuthStatusEvent {
|
||||
state: AuthenticationState;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { LoginPrompt } from './components/LoginPrompt';
|
||||
import { BrowserModeToggle } from './components/BrowserModeToggle';
|
||||
import { CheckoutConfirmationDialog } from './components/CheckoutConfirmationDialog';
|
||||
import { RaceCreationSuccessScreen } from './components/RaceCreationSuccessScreen';
|
||||
import type { HostedSessionConfig } from '../../../packages/domain/entities/HostedSessionConfig';
|
||||
import type { HostedSessionConfig } from '../../../packages/automation/domain/types/HostedSessionConfig';
|
||||
|
||||
interface SessionProgress {
|
||||
sessionId: string;
|
||||
@@ -138,7 +138,13 @@ export function App() {
|
||||
|
||||
const handleStartAutomation = async (config: HostedSessionConfig) => {
|
||||
setIsRunning(true);
|
||||
const result = await window.electronAPI.startAutomation(config);
|
||||
const result = await window.electronAPI.startAutomation(config) as {
|
||||
success: boolean;
|
||||
sessionId?: string;
|
||||
error?: string;
|
||||
authRequired?: boolean;
|
||||
authState?: AuthState;
|
||||
};
|
||||
|
||||
if (result.success && result.sessionId) {
|
||||
setSessionId(result.sessionId);
|
||||
@@ -147,8 +153,8 @@ export function App() {
|
||||
|
||||
setIsRunning(false);
|
||||
|
||||
if ((result as any).authRequired) {
|
||||
const nextAuthState = (result as any).authState as AuthState | undefined;
|
||||
if ('authRequired' in result && result.authRequired) {
|
||||
const nextAuthState = result.authState as AuthState | undefined;
|
||||
setAuthState(nextAuthState ?? 'EXPIRED');
|
||||
setAuthError(result.error ?? 'Authentication required before starting automation.');
|
||||
return;
|
||||
|
||||
@@ -4,7 +4,7 @@ type LoginStatus = 'idle' | 'waiting' | 'success' | 'error';
|
||||
|
||||
interface LoginPromptProps {
|
||||
authState: string;
|
||||
errorMessage?: string;
|
||||
errorMessage: string | undefined;
|
||||
onLogin: () => void;
|
||||
onRetry: () => void;
|
||||
loginStatus?: LoginStatus;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { HostedSessionConfig } from '../../../../packages/domain/entities/HostedSessionConfig';
|
||||
import type { HostedSessionConfig } from '../../../../packages/automation/domain/types/HostedSessionConfig';
|
||||
|
||||
interface SessionCreationFormProps {
|
||||
onSubmit: (config: HostedSessionConfig) => void;
|
||||
@@ -112,7 +112,14 @@ export function SessionCreationForm({ onSubmit, disabled }: SessionCreationFormP
|
||||
<label style={labelStyle}>Weather Type</label>
|
||||
<select
|
||||
value={config.weatherType}
|
||||
onChange={(e) => setConfig({ ...config, weatherType: e.target.value as any })}
|
||||
onChange={(e) =>
|
||||
setConfig(prev =>
|
||||
({
|
||||
...prev,
|
||||
weatherType: e.target.value as HostedSessionConfig['weatherType'],
|
||||
} as HostedSessionConfig)
|
||||
)
|
||||
}
|
||||
style={inputStyle}
|
||||
disabled={disabled}
|
||||
>
|
||||
@@ -125,7 +132,14 @@ export function SessionCreationForm({ onSubmit, disabled }: SessionCreationFormP
|
||||
<label style={labelStyle}>Time of Day</label>
|
||||
<select
|
||||
value={config.timeOfDay}
|
||||
onChange={(e) => setConfig({ ...config, timeOfDay: e.target.value as any })}
|
||||
onChange={(e) =>
|
||||
setConfig(prev =>
|
||||
({
|
||||
...prev,
|
||||
timeOfDay: e.target.value as HostedSessionConfig['timeOfDay'],
|
||||
} as HostedSessionConfig)
|
||||
)
|
||||
}
|
||||
style={inputStyle}
|
||||
disabled={disabled}
|
||||
>
|
||||
|
||||
@@ -73,8 +73,8 @@ export function SessionProgressMonitor({ sessionId, progress, isRunning }: Sessi
|
||||
(async () => {
|
||||
try {
|
||||
// Use electronAPI overlayActionRequest to obtain ack
|
||||
if ((window as any).electronAPI?.overlayActionRequest) {
|
||||
const ack = await (window as any).electronAPI.overlayActionRequest(action);
|
||||
if (window.electronAPI?.overlayActionRequest) {
|
||||
const ack = await window.electronAPI.overlayActionRequest(action);
|
||||
if (!mounted) return;
|
||||
setAckStatusByStep(prev => ({ ...prev, [currentStep]: ack.status }));
|
||||
} else {
|
||||
@@ -91,8 +91,8 @@ export function SessionProgressMonitor({ sessionId, progress, isRunning }: Sessi
|
||||
|
||||
// Subscribe to automation events for optional live updates
|
||||
useEffect(() => {
|
||||
if ((window as any).electronAPI?.onAutomationEvent) {
|
||||
const off = (window as any).electronAPI.onAutomationEvent((ev: any) => {
|
||||
if (window.electronAPI?.onAutomationEvent) {
|
||||
const off = window.electronAPI.onAutomationEvent((ev) => {
|
||||
if (ev && ev.payload && ev.payload.actionId && ev.type) {
|
||||
setAutomationEventMsg(`${ev.type} ${ev.payload.actionId}`);
|
||||
} else if (ev && ev.type) {
|
||||
|
||||
Reference in New Issue
Block a user