working companion prototype
This commit is contained in:
@@ -3,6 +3,7 @@ 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;
|
||||
|
||||
@@ -11,58 +12,139 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
|
||||
const startAutomationUseCase = container.getStartAutomationUseCase();
|
||||
const sessionRepository = container.getSessionRepository();
|
||||
const automationEngine = container.getAutomationEngine();
|
||||
const permissionService = container.getPermissionService();
|
||||
const logger = container.getLogger();
|
||||
|
||||
// Permission handlers
|
||||
ipcMain.handle('automation:checkPermissions', async () => {
|
||||
// Authentication handlers
|
||||
ipcMain.handle('auth:check', async () => {
|
||||
try {
|
||||
const result = await permissionService.checkPermissions();
|
||||
return {
|
||||
success: true,
|
||||
granted: result.granted,
|
||||
status: result.status,
|
||||
missingPermissions: result.missingPermissions,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error('Unknown error');
|
||||
logger.error('Permission check failed', err);
|
||||
return {
|
||||
success: false,
|
||||
error: err.message,
|
||||
granted: false,
|
||||
status: {
|
||||
accessibility: false,
|
||||
screenRecording: false,
|
||||
platform: process.platform,
|
||||
},
|
||||
missingPermissions: ['Accessibility', 'Screen Recording'],
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('automation:requestAccessibility', async () => {
|
||||
try {
|
||||
const granted = permissionService.requestAccessibilityPermission();
|
||||
return { success: true, granted };
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error('Unknown error');
|
||||
logger.error('Accessibility permission request failed', err);
|
||||
return { success: false, granted: false, error: err.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('automation:openPermissionSettings', async (_event: IpcMainInvokeEvent, pane?: 'accessibility' | 'screenRecording') => {
|
||||
try {
|
||||
if (pane) {
|
||||
await permissionService.openSystemPreferences(pane);
|
||||
} else {
|
||||
await permissionService.openPermissionsSettings();
|
||||
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('Failed to open permission settings', err);
|
||||
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 };
|
||||
}
|
||||
});
|
||||
@@ -76,20 +158,6 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
|
||||
clearInterval(progressMonitorInterval);
|
||||
progressMonitorInterval = null;
|
||||
}
|
||||
|
||||
// Check permissions before starting automation (macOS only)
|
||||
const permissionResult = await permissionService.checkPermissions();
|
||||
if (!permissionResult.granted) {
|
||||
logger.warn('Automation blocked due to missing permissions', {
|
||||
missingPermissions: permissionResult.missingPermissions,
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: `Missing required permissions: ${permissionResult.missingPermissions.join(', ')}. Please grant permissions in System Preferences and try again.`,
|
||||
permissionError: true,
|
||||
missingPermissions: permissionResult.missingPermissions,
|
||||
};
|
||||
}
|
||||
|
||||
// Connect to browser first (required for dev mode)
|
||||
const connectionResult = await container.initializeBrowserConnection();
|
||||
@@ -99,6 +167,27 @@ export function setupIpcHandlers(mainWindow: BrowserWindow): void {
|
||||
}
|
||||
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 });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user