import { spawn, exec, ChildProcess } from 'child_process'; import { app } from 'electron'; import path from 'path'; import log from 'electron-log'; const SDD_PORT = 9998; const HEALTH_CHECK_INTERVAL = 10000; const HEALTH_CHECK_TIMEOUT = 2000; class SDDService { private process: ChildProcess | null = null; private processPid: number | null = null; private healthCheckTimer: NodeJS.Timeout | null = null; private _isRunning = false; private _isStarting = false; get port(): number { return SDD_PORT; } isRunning(): boolean { return this._isRunning; } getStatus(): { running: boolean; port: number } { return { running: this._isRunning, port: this.port, }; } private getExePath(): string { const exeName = 'XCSDD.exe'; const isPackaged = app.isPackaged; let basePath: string; if (isPackaged) { basePath = path.join(process.resourcesPath, 'app.asar.unpacked'); } else { basePath = process.cwd(); } return path.join(basePath, 'services', 'xcsdd', exeName); } private getExeArgs(): string[] { return ['--port', this.port.toString(), '--headless']; } private async checkHealth(): Promise { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT); const response = await fetch(`http://127.0.0.1:${this.port}`, { signal: controller.signal, }); clearTimeout(timeoutId); return response.ok || response.status === 401; } catch (error) { log.warn('[SDDService] Health check failed:', error); return false; } } private startHealthCheck(): void { if (this.healthCheckTimer) { clearInterval(this.healthCheckTimer); } this.healthCheckTimer = setInterval(async () => { const isHealthy = await this.checkHealth(); if (!isHealthy && this._isRunning) { log.warn('[SDDService] Service not responding'); } }, HEALTH_CHECK_INTERVAL); } async start(): Promise<{ success: boolean; error?: string }> { if (this._isRunning && this.process) { log.info('[SDDService] Already running'); return { success: true }; } if (this._isStarting) { log.info('[SDDService] Already starting'); return { success: true }; } this._isStarting = true; try { const exePath = this.getExePath(); const exeArgs = this.getExeArgs(); log.info(`[SDDService] Starting from: ${exePath} with args: ${exeArgs.join(' ')}`); this.process = spawn(exePath, exeArgs, { stdio: 'pipe', }); this.processPid = this.process.pid ?? null; this.process.stdout?.on('data', (data) => { log.info(`[SDD] ${data}`); }); this.process.stderr?.on('data', (data) => { log.error(`[SDD error] ${data}`); }); this.process.on('error', (err) => { log.error('[SDDService] Process error:', err); this._isRunning = false; this._isStarting = false; this.process = null; this.processPid = null; }); this.process.on('exit', (code) => { log.info(`[SDDService] Process exited with code ${code}`); this._isRunning = false; this._isStarting = false; this.process = null; this.processPid = null; }); const maxWaitTime = 30000; const checkInterval = 500; const startTime = Date.now(); while (Date.now() - startTime < maxWaitTime) { const isHealthy = await this.checkHealth(); if (isHealthy) { break; } await new Promise((resolve) => setTimeout(resolve, checkInterval)); } const finalHealth = await this.checkHealth(); if (!finalHealth) { log.warn('[SDDService] Health check failed after max wait time'); } this._isRunning = true; this._isStarting = false; this.startHealthCheck(); log.info(`[SDDService] Started successfully on port ${this.port}`); return { success: true }; } catch (error: any) { log.error('[SDDService] Failed to start:', error); this._isRunning = false; this._isStarting = false; return { success: false, error: error.message }; } } async stop(): Promise<{ success: boolean; error?: string }> { if (this.healthCheckTimer) { clearInterval(this.healthCheckTimer); this.healthCheckTimer = null; } if (!this.processPid) { log.info('[SDDService] Not running'); return { success: true }; } return new Promise((resolve) => { log.info(`[SDDService] Stopping process ${this.processPid}...`); exec(`taskkill /F /T /PID ${this.processPid}`, (error) => { this.process = null; this.processPid = null; this._isRunning = false; if (error) { log.error('[SDDService] Failed to stop:', error); resolve({ success: false, error: error.message }); } else { log.info('[SDDService] Stopped'); resolve({ success: true }); } }); }); } } export const sddService = new SDDService();