Files
gridpilot.gg/apps/companion/main/ipc-handlers.ts

304 lines
11 KiB
TypeScript

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';
let progressMonitorInterval: NodeJS.Timeout | null = null;
export function setupIpcHandlers(mainWindow: BrowserWindow): void {
const container = DIContainer.getInstance();
const startAutomationUseCase = container.getStartAutomationUseCase();
const sessionRepository = container.getSessionRepository();
const automationEngine = container.getAutomationEngine();
const logger = container.getLogger();
// Authentication handlers
ipcMain.handle('auth:check', async () => {
try {
logger.info('Checking authentication status');
const checkAuthUseCase = container.getCheckAuthenticationUseCase();
if (!checkAuthUseCase) {
logger.warn('Authentication not available in mock mode');
return {
success: true,
state: AuthenticationState.AUTHENTICATED,
message: 'Mock mode - authentication bypassed'
};
}
// NO browser connection needed - cookie check reads JSON file directly
const result = await checkAuthUseCase.execute();
if (result.isErr()) {
logger.error('Auth check failed', result.unwrapErr());
return { success: false, error: result.unwrapErr().message };
}
const state = result.unwrap();
logger.info('Authentication check complete', { state });
return { success: true, state };
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
logger.error('Auth check failed', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('auth:login', async () => {
try {
logger.info('Starting iRacing login flow (will wait for completion)');
const authService = container.getAuthenticationService();
if (!authService) {
// Mock mode - no actual login needed
logger.warn('Auth service not available in mock mode');
return { success: true, message: 'Mock mode - login bypassed' };
}
// Use the Playwright browser for login (same browser used for automation)
// This now waits for login to complete, auto-detects success, and closes browser
const initiateLoginUseCase = container.getInitiateLoginUseCase();
if (!initiateLoginUseCase) {
logger.warn('Initiate login use case not available');
return { success: false, error: 'Login not available' };
}
// This call now blocks until login is complete or times out
const result = await initiateLoginUseCase.execute();
if (result.isErr()) {
logger.error('Login failed or timed out', result.unwrapErr());
return { success: false, error: result.unwrapErr().message };
}
logger.info('Login completed successfully');
return { success: true, message: 'Login completed successfully' };
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
logger.error('Login flow failed', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('auth:confirmLogin', async () => {
try {
logger.info('User confirmed login completion');
const authService = container.getAuthenticationService();
if (!authService) {
logger.warn('Auth service not available in mock mode');
return { success: true, state: AuthenticationState.AUTHENTICATED };
}
// Call confirmLoginComplete on the adapter if it exists
if ('confirmLoginComplete' in authService) {
const result = await (authService as any).confirmLoginComplete();
if (result.isErr()) {
logger.error('Confirm login failed', result.unwrapErr());
return { success: false, error: result.unwrapErr().message };
}
}
logger.info('Login confirmation recorded');
return { success: true, state: AuthenticationState.AUTHENTICATED };
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
logger.error('Failed to confirm login', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('auth:logout', async () => {
try {
logger.info('Clearing session (logout)');
const clearSessionUseCase = container.getClearSessionUseCase();
if (!clearSessionUseCase) {
logger.warn('Logout not available in mock mode');
return { success: true, message: 'Mock mode - logout bypassed' };
}
const result = await clearSessionUseCase.execute();
if (result.isErr()) {
logger.error('Logout failed', result.unwrapErr());
return { success: false, error: result.unwrapErr().message };
}
logger.info('Session cleared successfully');
return { success: true };
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
logger.error('Logout failed', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('auth:getState', async () => {
try {
const authService = container.getAuthenticationService();
if (!authService) {
return { success: true, state: AuthenticationState.AUTHENTICATED };
}
return { success: true, state: authService.getState() };
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
logger.error('Failed to get auth state', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('start-automation', async (_event: IpcMainInvokeEvent, config: HostedSessionConfig) => {
try {
logger.info('Starting automation', { sessionName: config.sessionName });
// Clear any existing progress interval
if (progressMonitorInterval) {
clearInterval(progressMonitorInterval);
progressMonitorInterval = null;
}
// Connect to browser first (required for dev mode)
const connectionResult = await container.initializeBrowserConnection();
if (!connectionResult.success) {
logger.error('Browser connection failed', undefined, { errorMessage: connectionResult.error });
return { success: false, error: connectionResult.error };
}
logger.info('Browser connection established');
// Check authentication before starting automation (production/development mode only)
const checkAuthUseCase = container.getCheckAuthenticationUseCase();
if (checkAuthUseCase) {
const authResult = await checkAuthUseCase.execute();
if (authResult.isOk()) {
const authState = authResult.unwrap();
if (authState !== AuthenticationState.AUTHENTICATED) {
logger.warn('Not authenticated - automation cannot proceed', { authState });
return {
success: false,
error: 'Not authenticated. Please login first.',
authRequired: true,
authState,
};
}
logger.info('Authentication verified');
} else {
logger.warn('Auth check failed, proceeding anyway', { error: authResult.unwrapErr().message });
}
}
const result = await startAutomationUseCase.execute(config);
logger.info('Automation session created', { sessionId: result.sessionId });
const session = await sessionRepository.findById(result.sessionId);
if (session) {
// Start the automation by executing step 1
logger.info('Executing step 1');
await automationEngine.executeStep(StepId.create(1), config);
}
// Set up progress monitoring
progressMonitorInterval = setInterval(async () => {
const updatedSession = await sessionRepository.findById(result.sessionId);
if (!updatedSession) {
if (progressMonitorInterval) {
clearInterval(progressMonitorInterval);
progressMonitorInterval = null;
}
return;
}
mainWindow.webContents.send('session-progress', {
sessionId: result.sessionId,
currentStep: updatedSession.currentStep.value,
state: updatedSession.state.value,
completedSteps: Array.from({ length: updatedSession.currentStep.value - 1 }, (_, i) => i + 1),
hasError: updatedSession.errorMessage !== undefined,
errorMessage: updatedSession.errorMessage || null
});
if (updatedSession.state.value === 'COMPLETED' ||
updatedSession.state.value === 'FAILED' ||
updatedSession.state.value === 'STOPPED_AT_STEP_18') {
logger.info('Automation ended', { state: updatedSession.state.value });
if (progressMonitorInterval) {
clearInterval(progressMonitorInterval);
progressMonitorInterval = null;
}
}
}, 100);
return {
success: true,
sessionId: result.sessionId
};
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
logger.error('Automation failed', err);
return {
success: false,
error: err.message
};
}
});
ipcMain.handle('get-session-status', async (_event: IpcMainInvokeEvent, sessionId: string) => {
const session = await sessionRepository.findById(sessionId);
if (!session) {
return { found: false };
}
return {
found: true,
currentStep: session.currentStep.value,
state: session.state.value,
completedSteps: Array.from({ length: session.currentStep.value - 1 }, (_, i) => i + 1),
hasError: session.errorMessage !== undefined,
errorMessage: session.errorMessage || null
};
});
ipcMain.handle('pause-automation', async (_event: IpcMainInvokeEvent, _sessionId: string) => {
return { success: false, error: 'Pause not implemented in POC' };
});
ipcMain.handle('resume-automation', async (_event: IpcMainInvokeEvent, _sessionId: string) => {
return { success: false, error: 'Resume not implemented in POC' };
});
ipcMain.handle('stop-automation', async (_event: IpcMainInvokeEvent, sessionId: string) => {
try {
logger.info('Stopping automation', { sessionId });
// Clear progress monitoring interval
if (progressMonitorInterval) {
clearInterval(progressMonitorInterval);
progressMonitorInterval = null;
logger.info('Progress monitor cleared');
}
// Stop the automation engine interval
automationEngine.stopAutomation();
logger.info('Automation engine stopped');
// Update session state to failed with user stop reason
const session = await sessionRepository.findById(sessionId);
if (session) {
session.fail('User stopped automation');
await sessionRepository.update(session);
logger.info('Session marked as failed', { sessionId });
}
return { success: true };
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
logger.error('Stop automation failed', err);
return {
success: false,
error: err.message
};
}
});
}