Files
XCDesktop/electron/services/xcOpenCodeWebService.ts
2026-03-13 21:15:26 +08:00

159 lines
4.2 KiB
TypeScript

import { spawn, ChildProcess } from 'child_process';
import { app } from 'electron';
import path from 'path';
import log from 'electron-log';
const XCOPENCODEWEB_PORT = 3002;
const HEALTH_CHECK_INTERVAL = 10000;
const HEALTH_CHECK_TIMEOUT = 2000;
class XCOpenCodeWebService {
private process: ChildProcess | null = null;
private healthCheckTimer: NodeJS.Timeout | null = null;
private _isRunning = false;
get port(): number {
return XCOPENCODEWEB_PORT;
}
isRunning(): boolean {
return this._isRunning;
}
getStatus(): { running: boolean; port: number } {
return {
running: this._isRunning,
port: this.port,
};
}
private getExePath(): string {
const exeName = 'XCOpenCodeWeb.exe';
const isPackaged = app.isPackaged;
let basePath: string;
if (isPackaged) {
basePath = path.dirname(app.getPath('exe'));
} else {
basePath = process.cwd();
}
return path.join(basePath, 'services', 'xcopencodeweb', exeName);
}
private async checkHealth(): Promise<boolean> {
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('[XCOpenCodeWebService] 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('[XCOpenCodeWebService] Service not responding');
}
}, HEALTH_CHECK_INTERVAL);
}
async start(): Promise<{ success: boolean; error?: string }> {
if (this._isRunning && this.process) {
log.info('[XCOpenCodeWebService] Already running');
return { success: true };
}
try {
const exePath = this.getExePath();
log.info(`[XCOpenCodeWebService] Starting from: ${exePath}`);
this.process = spawn(exePath, [], {
stdio: 'pipe',
shell: true,
detached: false,
});
this.process.stdout?.on('data', (data) => {
log.info(`[XCOpenCodeWeb] ${data}`);
});
this.process.stderr?.on('data', (data) => {
log.error(`[XCOpenCodeWeb error] ${data}`);
});
this.process.on('error', (err) => {
log.error('[XCOpenCodeWebService] Process error:', err);
this._isRunning = false;
this.process = null;
});
this.process.on('exit', (code) => {
log.info(`[XCOpenCodeWebService] Process exited with code ${code}`);
this._isRunning = false;
this.process = null;
});
await new Promise<void>((resolve) => setTimeout(resolve, 2000));
this._isRunning = true;
this.startHealthCheck();
log.info(`[XCOpenCodeWebService] Started successfully on port ${this.port}`);
return { success: true };
} catch (error: any) {
log.error('[XCOpenCodeWebService] Failed to start:', error);
this._isRunning = 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.process) {
log.info('[XCOpenCodeWebService] Not running');
return { success: true };
}
try {
log.info('[XCOpenCodeWebService] Stopping...');
this.process.kill('SIGTERM');
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
if (this.process && !this.process.killed) {
this.process.kill('SIGKILL');
}
this.process = null;
this._isRunning = false;
log.info('[XCOpenCodeWebService] Stopped');
return { success: true };
} catch (error: any) {
log.error('[XCOpenCodeWebService] Failed to stop:', error);
return { success: false, error: error.message };
}
}
}
export const xcOpenCodeWebService = new XCOpenCodeWebService();