- 添加本地游戏库扫描功能,支持 Steam、Microsoft、Epic、EA 平台自动识别已安装游戏 - 实现运行时日志系统,支持前端、后端、核心日志的统一收集与查看 - 优化进程代理监控,支持动态更新子进程规则并自动重启 sing-box - 重构 sing-box 配置生成逻辑,增强配置可读性与维护性 - 新增日志查看页面,支持按分类、级别、关键字筛选日志 - 改进错误处理,提供更友好的原生模块加载失败提示 - 更新 README 文档,完善项目说明与功能特性描述
135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
import { BrowserWindow, ipcMain } from 'electron';
|
|
import { ProcessManager } from '../process/manager';
|
|
import { SingBoxManager } from '../singbox/manager';
|
|
|
|
interface ProcessInfo {
|
|
pid: number;
|
|
ppid: number;
|
|
name: string;
|
|
children?: ProcessInfo[];
|
|
}
|
|
|
|
export class ProxyMonitor {
|
|
private mainWindow: BrowserWindow;
|
|
private processManager: ProcessManager;
|
|
private singboxManager: SingBoxManager;
|
|
private interval: NodeJS.Timeout | null = null;
|
|
private monitoredProcessNames: Set<string> = new Set();
|
|
private activeGameId: string | null = null;
|
|
private detectedChildProcesses: Set<string> = new Set();
|
|
|
|
constructor(window: BrowserWindow, processManager: ProcessManager, singboxManager: SingBoxManager) {
|
|
this.mainWindow = window;
|
|
this.processManager = processManager;
|
|
this.singboxManager = singboxManager;
|
|
this.setupIPC();
|
|
}
|
|
|
|
private setupIPC() {
|
|
ipcMain.handle('proxy-monitor:start', (_, gameId: string, processNames: string[]) => {
|
|
this.startMonitoring(gameId, processNames);
|
|
});
|
|
|
|
ipcMain.handle('proxy-monitor:stop', () => {
|
|
this.stopMonitoring();
|
|
});
|
|
}
|
|
|
|
startMonitoring(gameId: string, processNames: string[]) {
|
|
this.stopMonitoring();
|
|
this.activeGameId = gameId;
|
|
this.monitoredProcessNames = new Set(this.normalizeProcessNames(processNames));
|
|
this.detectedChildProcesses.clear();
|
|
|
|
console.log(`[ProxyMonitor] Started monitoring for game ${gameId} with processes:`, [...this.monitoredProcessNames]);
|
|
|
|
// Start polling
|
|
this.interval = setInterval(() => this.checkChainProxy(), 3000);
|
|
this.mainWindow.webContents.send('proxy-monitor:status', { status: 'active', gameId });
|
|
|
|
void this.singboxManager.updateProcessNames([...this.monitoredProcessNames]);
|
|
}
|
|
|
|
stopMonitoring() {
|
|
if (this.interval) {
|
|
clearInterval(this.interval);
|
|
this.interval = null;
|
|
}
|
|
this.activeGameId = null;
|
|
this.monitoredProcessNames.clear();
|
|
this.detectedChildProcesses.clear();
|
|
this.mainWindow.webContents.send('proxy-monitor:status', { status: 'idle' });
|
|
console.log('[ProxyMonitor] Stopped monitoring');
|
|
}
|
|
|
|
private async checkChainProxy() {
|
|
if (!this.activeGameId) return;
|
|
|
|
try {
|
|
const tree = await this.processManager.getProcessTree();
|
|
const newChildren = this.normalizeProcessNames(this.findNewChildren(tree))
|
|
.filter(name => !this.monitoredProcessNames.has(name));
|
|
|
|
if (newChildren.length > 0) {
|
|
// Add new children to monitored set
|
|
newChildren.forEach(name => {
|
|
this.monitoredProcessNames.add(name);
|
|
this.detectedChildProcesses.add(name);
|
|
console.log(`[ProxyMonitor] Detected new child process: ${name}`);
|
|
});
|
|
|
|
// Notify frontend
|
|
this.mainWindow.webContents.send('proxy-monitor:detected', newChildren);
|
|
|
|
await this.singboxManager.updateProcessNames([...this.monitoredProcessNames]);
|
|
}
|
|
} catch (error) {
|
|
console.error('Proxy monitor error:', error);
|
|
}
|
|
}
|
|
|
|
private findNewChildren(nodes: ProcessInfo[]): string[] {
|
|
const found: string[] = [];
|
|
|
|
// Recursive traversal to find children of monitored processes
|
|
const traverse = (node: ProcessInfo, isProxiedParent: boolean) => {
|
|
const normalizedName = this.normalizeProcessName(node.name);
|
|
if (!normalizedName) return;
|
|
|
|
const isMonitored = this.monitoredProcessNames.has(normalizedName);
|
|
// If parent is proxied, child should be proxied (Chain Proxy)
|
|
const shouldProxy = isProxiedParent || isMonitored;
|
|
|
|
if (shouldProxy && !isMonitored) {
|
|
found.push(normalizedName);
|
|
}
|
|
|
|
if (node.children) {
|
|
node.children.forEach(child => traverse(child, shouldProxy));
|
|
}
|
|
};
|
|
|
|
nodes.forEach(node => traverse(node, false));
|
|
return found;
|
|
}
|
|
|
|
private normalizeProcessNames(processNames: string[]): string[] {
|
|
const set = new Set<string>();
|
|
processNames.forEach(name => {
|
|
const normalized = this.normalizeProcessName(name);
|
|
if (!normalized) return;
|
|
set.add(normalized);
|
|
const lower = normalized.toLowerCase();
|
|
if (lower !== normalized) set.add(lower);
|
|
});
|
|
return Array.from(set);
|
|
}
|
|
|
|
private normalizeProcessName(processName: string): string {
|
|
const raw = String(processName || '').trim();
|
|
if (!raw) return '';
|
|
const normalized = raw.replace(/\\/g, '/');
|
|
return (normalized.split('/').pop() || normalized).trim();
|
|
}
|
|
}
|