This commit is contained in:
2025-12-11 21:06:25 +01:00
parent c49ea2598d
commit ec3ddc3a5c
227 changed files with 3496 additions and 2083 deletions

View File

@@ -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,

View File

@@ -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();

View File

@@ -27,6 +27,7 @@ export const DI_TOKENS = {
// Services
OverlaySyncPort: Symbol.for('OverlaySyncPort'),
CheckoutService: Symbol.for('CheckoutServicePort'),
// Infrastructure
FixtureServer: Symbol.for('FixtureServer'),

View File

@@ -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);

View File

@@ -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;