mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-08 17:09:07 +08:00
launch daemon in main process
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
"@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension": "workspace:*",
|
"@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension": "workspace:*",
|
||||||
"@geekgeekrun/sqlite-plugin": "workspace:*",
|
"@geekgeekrun/sqlite-plugin": "workspace:*",
|
||||||
"@geekgeekrun/utils": "workspace:*",
|
"@geekgeekrun/utils": "workspace:*",
|
||||||
|
"@geekgeekrun/pm": "workspace:*",
|
||||||
"JSONStream": "^1.3.5",
|
"JSONStream": "^1.3.5",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"electron-updater": "^6.1.7",
|
"electron-updater": "^6.1.7",
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
;(async () => {
|
||||||
|
await import('@geekgeekrun/pm/daemon.js')
|
||||||
|
})()
|
||||||
|
|
||||||
|
export {}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
const { app, ipcMain } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const net = require('net');
|
||||||
|
const split2 = require('split2');
|
||||||
|
|
||||||
|
const isUiDev = process.env.NODE_ENV === 'development'
|
||||||
|
const DAEMON_PORT = 12345;
|
||||||
|
|
||||||
|
export function launchDaemon() {
|
||||||
|
let daemonProcess = null;
|
||||||
|
|
||||||
|
// 所有窗口关闭时
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
// if (process.platform !== 'darwin') {
|
||||||
|
// 关闭守护进程
|
||||||
|
if (daemonProcess) {
|
||||||
|
daemonProcess.kill();
|
||||||
|
}
|
||||||
|
// app.quit();
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 应用退出前清理
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
if (daemonProcess) {
|
||||||
|
daemonProcess.kill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动守护进程
|
||||||
|
function startDaemon() {
|
||||||
|
console.log('启动守护进程...');
|
||||||
|
// 使用 Electron 可执行程序路径,如果没有则回退到 node
|
||||||
|
const electronPath = process.execPath;
|
||||||
|
console.log(`使用 Electron 路径: ${electronPath}`);
|
||||||
|
|
||||||
|
// 添加参数使守护进程在后台运行,不显示 UI
|
||||||
|
daemonProcess = spawn(
|
||||||
|
process.argv[0],
|
||||||
|
isUiDev
|
||||||
|
? [process.argv[1], `--mode=launchDaemon`]
|
||||||
|
: [`--mode=launchDaemon`],
|
||||||
|
{
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
detached: false,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// daemonProcess = spawn(electronPath, [
|
||||||
|
// '--no-sandbox',
|
||||||
|
// '--disable-dev-shm-usage',
|
||||||
|
// path.join(__dirname, 'daemon.js')
|
||||||
|
// ], {
|
||||||
|
// stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
// detached: false,
|
||||||
|
// env: {
|
||||||
|
// ...process.env,
|
||||||
|
// ELECTRON_EXEC_PATH: electronPath // 传递给守护进程,用于启动 worker
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
daemonProcess.stdout.on('data', (data) => {
|
||||||
|
console.log(`守护进程输出: ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
daemonProcess.stderr.on('data', (data) => {
|
||||||
|
console.error(`守护进程错误: ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
daemonProcess.on('exit', (code) => {
|
||||||
|
console.log(`守护进程退出,代码: ${code}`);
|
||||||
|
// 如果守护进程意外退出,尝试重启
|
||||||
|
if (code !== 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('尝试重启守护进程...');
|
||||||
|
startDaemon();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待守护进程启动后连接
|
||||||
|
setTimeout(() => {
|
||||||
|
connectToDaemon();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用准备就绪
|
||||||
|
return app.whenReady().then(() => {
|
||||||
|
startDaemon();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connectToDaemon() {
|
||||||
|
let daemonClient = null;
|
||||||
|
// 连接到守护进程
|
||||||
|
function _connectToDaemon() {
|
||||||
|
daemonClient = new net.Socket();
|
||||||
|
daemonClient.connect(DAEMON_PORT, 'localhost', () => {
|
||||||
|
console.log('已连接到守护进程');
|
||||||
|
// 通知渲染进程连接成功
|
||||||
|
// if (mainWindow) {
|
||||||
|
// mainWindow.webContents.send('daemon-connected');
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
// 使用 split2 按行分割流式数据,处理 JSONL 格式(每行一个 JSON)
|
||||||
|
const splitStream = split2();
|
||||||
|
daemonClient.pipe(splitStream).on('data', (line) => {
|
||||||
|
const trimmedLine = line.toString().trim();
|
||||||
|
if (!trimmedLine) {
|
||||||
|
return; // 跳过空行
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(trimmedLine);
|
||||||
|
console.log('收到守护进程消息:', message);
|
||||||
|
// 转发消息到渲染进程
|
||||||
|
// if (mainWindow) {
|
||||||
|
// mainWindow.webContents.send('daemon-message', message);
|
||||||
|
// }
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('解析守护进程消息失败:', parseError.message);
|
||||||
|
console.error('原始数据:', trimmedLine.substring(0, 100));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
splitStream.on('error', (err) => {
|
||||||
|
console.error('split2 流处理错误:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
daemonClient.on('error', (err) => {
|
||||||
|
console.error('守护进程连接错误:', err);
|
||||||
|
// 尝试重连
|
||||||
|
setTimeout(() => {
|
||||||
|
if (daemonClient.destroyed) {
|
||||||
|
_connectToDaemon();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
daemonClient.on('close', () => {
|
||||||
|
console.log('守护进程连接已关闭');
|
||||||
|
// 尝试重连
|
||||||
|
setTimeout(() => {
|
||||||
|
_connectToDaemon();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向守护进程发送消息
|
||||||
|
function sendToDaemon(message) {
|
||||||
|
if (daemonClient && !daemonClient.destroyed) {
|
||||||
|
daemonClient.write(JSON.stringify(message) + '\n');
|
||||||
|
} else {
|
||||||
|
console.error('守护进程未连接');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC处理:从渲染进程接收消息并转发到守护进程
|
||||||
|
ipcMain.on('send-to-daemon', (event, message) => {
|
||||||
|
sendToDaemon(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// IPC处理:启动工具进程
|
||||||
|
ipcMain.on('start-worker', (event, workerId) => {
|
||||||
|
sendToDaemon({ type: 'start-worker', workerId });
|
||||||
|
});
|
||||||
|
|
||||||
|
// IPC处理:停止工具进程
|
||||||
|
ipcMain.on('stop-worker', (event, workerId) => {
|
||||||
|
sendToDaemon({ type: 'stop-worker', workerId });
|
||||||
|
});
|
||||||
|
|
||||||
|
// IPC处理:获取所有工具进程状态
|
||||||
|
ipcMain.on('get-workers-status', () => {
|
||||||
|
sendToDaemon({ type: 'get-status' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (daemonClient) {
|
||||||
|
daemonClient.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
if (daemonClient) {
|
||||||
|
daemonClient.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_connectToDaemon()
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import './app-menu'
|
|||||||
import initIpc from './ipc'
|
import initIpc from './ipc'
|
||||||
import gtag from '../../utils/gtag'
|
import gtag from '../../utils/gtag'
|
||||||
import initPublicIpc from '../../utils/initPublicIpc'
|
import initPublicIpc from '../../utils/initPublicIpc'
|
||||||
|
import { connectToDaemon, launchDaemon } from './attach-daemon'
|
||||||
|
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
|
||||||
export function openSettingWindow() {
|
export function openSettingWindow() {
|
||||||
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
// TODO: singleton lock; how can we check if there is another process should run as singleton with arguments?
|
||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
@@ -65,4 +67,11 @@ export function openSettingWindow() {
|
|||||||
globalShortcut.unregister('Command+Option+Shift+/')
|
globalShortcut.unregister('Command+Option+Shift+/')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
whenReadyPromise.then(async () => {
|
||||||
|
await launchDaemon()
|
||||||
|
// FIXME:
|
||||||
|
await sleep(2000)
|
||||||
|
await connectToDaemon()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ const runMode = commandlineArgs['mode'];
|
|||||||
runEntry()
|
runEntry()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'launchDaemon': {
|
||||||
|
await import('./flow/LAUNCH_DAEMON')
|
||||||
|
break
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index')
|
const { openSettingWindow } = await import('./flow/OPEN_SETTING_WINDOW/index')
|
||||||
openSettingWindow()
|
openSettingWindow()
|
||||||
|
|||||||
Generated
+3
@@ -130,6 +130,9 @@ importers:
|
|||||||
'@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension':
|
'@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../launch-bosszhipin-login-page-with-preload-extension
|
version: link:../launch-bosszhipin-login-page-with-preload-extension
|
||||||
|
'@geekgeekrun/pm':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../pm
|
||||||
'@geekgeekrun/sqlite-plugin':
|
'@geekgeekrun/sqlite-plugin':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../sqlite-plugin
|
version: link:../sqlite-plugin
|
||||||
|
|||||||
Reference in New Issue
Block a user