| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import path from 'path'; |
| import { app, BrowserWindow, dialog } from 'electron'; |
| import { |
| setElectronUserDataPath, |
| setElectronAppPaths, |
| initAllowedPaths, |
| } from '@automaker/platform'; |
| import { createLogger } from '@automaker/utils/logger'; |
| import { DEFAULT_SERVER_PORT, DEFAULT_STATIC_PORT } from './electron/constants'; |
| import { state } from './electron/state'; |
| import { findAvailablePort } from './electron/utils/port-manager'; |
| import { getIconPath } from './electron/utils/icon-manager'; |
| import { ensureApiKey } from './electron/security/api-key-manager'; |
| import { createWindow } from './electron/windows/main-window'; |
| import { startStaticServer, stopStaticServer } from './electron/server/static-server'; |
| import { startServer, waitForServer, stopServer } from './electron/server/backend-server'; |
| import { registerAllHandlers } from './electron/ipc'; |
|
|
| const logger = createLogger('Electron'); |
|
|
| |
| const isDev = !app.isPackaged; |
|
|
| |
| if (isDev) { |
| try { |
| |
| require('dotenv').config({ path: path.join(__dirname, '../.env') }); |
| } catch (error) { |
| logger.warn('dotenv not available:', (error as Error).message); |
| } |
| } |
|
|
| |
| |
| |
| if (process.platform === 'linux') { |
| app.commandLine.appendSwitch('ozone-platform-hint', 'auto'); |
| } |
|
|
| |
| registerAllHandlers(); |
|
|
| |
| app.whenReady().then(handleAppReady); |
| app.on('window-all-closed', handleWindowAllClosed); |
| app.on('before-quit', handleBeforeQuit); |
|
|
| |
| |
| |
| async function handleAppReady(): Promise<void> { |
| |
| |
| let userDataPathToUse: string; |
|
|
| if (app.isPackaged) { |
| |
| try { |
| const desiredUserDataPath = path.join(app.getPath('appData'), 'Automaker'); |
|
|
| if (app.getPath('userData') !== desiredUserDataPath) { |
| app.setPath('userData', desiredUserDataPath); |
| logger.info('[PRODUCTION] userData path set to:', desiredUserDataPath); |
| } |
|
|
| userDataPathToUse = desiredUserDataPath; |
| } catch (error) { |
| logger.warn('[PRODUCTION] Failed to set userData path:', (error as Error).message); |
| userDataPathToUse = app.getPath('userData'); |
| } |
| } else { |
| |
| |
| |
| const projectRoot = path.join(__dirname, '../../..'); |
| userDataPathToUse = path.join(projectRoot, 'data'); |
|
|
| try { |
| app.setPath('userData', userDataPathToUse); |
| logger.info('[DEVELOPMENT] userData path explicitly set to:', userDataPathToUse); |
| } catch (error) { |
| logger.warn( |
| '[DEVELOPMENT] Failed to set userData path, using fallback:', |
| (error as Error).message |
| ); |
| userDataPathToUse = path.join(projectRoot, 'data'); |
| } |
| } |
|
|
| |
| |
| setElectronUserDataPath(userDataPathToUse); |
|
|
| |
| |
| if (isDev) { |
| |
| const projectRoot = path.join(__dirname, '../../..'); |
| setElectronAppPaths([__dirname, projectRoot]); |
| } else { |
| setElectronAppPaths(__dirname, process.resourcesPath); |
| } |
|
|
| logger.info('Initialized path security helpers'); |
|
|
| |
| |
| |
| const mainProcessDataDir = app.isPackaged |
| ? app.getPath('userData') |
| : path.join(process.cwd(), 'data'); |
| process.env.DATA_DIR = mainProcessDataDir; |
| logger.info('[MAIN_PROCESS_DATA_DIR]', mainProcessDataDir); |
|
|
| |
| |
| initAllowedPaths(); |
|
|
| if (process.platform === 'darwin' && app.dock) { |
| const iconPath = getIconPath(); |
| if (iconPath) { |
| try { |
| app.dock.setIcon(iconPath); |
| } catch (error) { |
| logger.warn('Failed to set dock icon:', (error as Error).message); |
| } |
| } |
| } |
|
|
| try { |
| |
| const skipEmbeddedServer = process.env.SKIP_EMBEDDED_SERVER === 'true'; |
| state.isExternalServerMode = skipEmbeddedServer; |
|
|
| if (skipEmbeddedServer) { |
| |
| state.serverPort = DEFAULT_SERVER_PORT; |
| logger.info('SKIP_EMBEDDED_SERVER=true, using external server at port', state.serverPort); |
|
|
| |
| logger.info('Waiting for external server...'); |
| await waitForServer(60); |
| logger.info('External server is ready'); |
|
|
| |
| |
| |
| |
| logger.info('External server mode: using session-based authentication'); |
| } else { |
| |
| ensureApiKey(); |
|
|
| |
| state.serverPort = await findAvailablePort(DEFAULT_SERVER_PORT); |
| if (state.serverPort !== DEFAULT_SERVER_PORT) { |
| logger.info( |
| 'Default server port', |
| DEFAULT_SERVER_PORT, |
| 'in use, using port', |
| state.serverPort |
| ); |
| } |
| } |
|
|
| state.staticPort = await findAvailablePort(DEFAULT_STATIC_PORT); |
| if (state.staticPort !== DEFAULT_STATIC_PORT) { |
| logger.info( |
| 'Default static port', |
| DEFAULT_STATIC_PORT, |
| 'in use, using port', |
| state.staticPort |
| ); |
| } |
|
|
| |
| if (app.isPackaged) { |
| await startStaticServer(); |
| } |
|
|
| |
| if (!skipEmbeddedServer) { |
| await startServer(); |
| } |
|
|
| |
| createWindow(); |
| } catch (error) { |
| logger.error('Failed to start:', error); |
|
|
| const errorMessage = (error as Error).message; |
| const isNodeError = errorMessage.includes('Node.js'); |
|
|
| dialog.showErrorBox( |
| 'Automaker Failed to Start', |
| `The application failed to start.\n\n${errorMessage}\n\n${ |
| isNodeError |
| ? 'Please install Node.js from https://nodejs.org or via a package manager (Homebrew, nvm, fnm).' |
| : 'Please check the application logs for more details.' |
| }` |
| ); |
| app.quit(); |
| } |
|
|
| app.on('activate', () => { |
| if (BrowserWindow.getAllWindows().length === 0) { |
| createWindow(); |
| } |
| }); |
| } |
|
|
| |
| |
| |
| function handleWindowAllClosed(): void { |
| |
| |
| if (process.platform !== 'darwin') { |
| stopServer(); |
| stopStaticServer(); |
| app.quit(); |
| } |
| } |
|
|
| |
| |
| |
| function handleBeforeQuit(): void { |
| stopServer(); |
| stopStaticServer(); |
| } |
|
|