zhuyuan-ice-gl-zy001/QODER-CN-DEV-20260807/state-wake-20260812/bridge-chat/electron/main.cjs
2026-08-20 00:01:53 +08:00

119 lines
4.2 KiB
JavaScript
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// main.cjs —— 光湖桥聊天 · Electron 壳
// 集成方式:spawn 光湖桥节点子进程(stdio JSON 模式),IPC 转发事件
const { app, BrowserWindow, ipcMain } = require('electron');
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
// ── 配置:节点号 / 会合点 / node 可执行路径 ──
const cfgPath = path.join(__dirname, 'config.json');
let cfg = {};
try { cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); } catch { /* 用默认值 */ }
const NODE_ID = process.env.GHB_NODE_ID || cfg.nodeId || 'GHB-ELECTRON';
const RELAY = process.env.GHB_RELAY || cfg.relay || '127.0.0.1:9701';
const NODE_BIN = process.env.GHB_NODE_BIN || cfg.nodeBin || 'node';
// 打包态下 bridge 脚本被 unpack 到 app.asar.unpacked(外部 node 读不了 asar 内文件)
const BRIDGE_MJS = [
path.join(__dirname, '..', 'app.asar.unpacked', 'bridge', 'bridge-node.mjs'),
path.join(__dirname, 'bridge', 'bridge-node.mjs'),
].find((p) => fs.existsSync(p));
let win = null;
let bridge = null;
let winReady = false;
const evQueue = []; // 窗口就绪前到达的事件先缓存,就绪后补发
function forward(ev) {
if (!winReady || !win || win.isDestroyed()) { evQueue.push(ev); return; }
win.webContents.send('ghb:event', ev);
}
function startBridge() {
const dataDir = path.join(app.getPath('userData'), 'bridge-node');
const args = [BRIDGE_MJS, 'node', '--stdio', '--id', NODE_ID, '--relay', RELAY, '--state-dir', dataDir];
bridge = spawn(NODE_BIN, args, { stdio: ['pipe', 'pipe', 'pipe'] });
let buf = '';
bridge.stdout.on('data', (chunk) => {
buf += chunk.toString('utf8');
let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i).trim();
buf = buf.slice(i + 1);
if (!line) continue;
try {
const ev = JSON.parse(line);
forward(ev);
} catch { /* 非 JSON 行忽略 */ }
}
});
bridge.stderr.on('data', (d) => console.error(`[bridge] ${d.toString().trim()}`));
bridge.on('exit', (code) => {
console.error(`[bridge] 子进程退出 code=${code}`);
if (win && !win.isDestroyed()) win.webContents.send('ghb:event', { event: 'error', message: `桥接进程退出(${code})` });
});
}
function createWindow() {
win = new BrowserWindow({
width: 920,
height: 660,
title: `光湖桥 · 聊天 · ${NODE_ID}`,
backgroundColor: '#0b1220',
webPreferences: {
preload: path.join(__dirname, 'preload.cjs'),
contextIsolation: true,
nodeIntegration: false,
},
});
win.loadFile(path.join(__dirname, 'chat.html'));
win.webContents.once('did-finish-load', () => {
winReady = true;
for (const ev of evQueue.splice(0)) win.webContents.send('ghb:event', ev); // 补发早到事件
});
}
app.whenReady().then(() => {
startBridge();
createWindow();
// 自检钩子:GHB_AUTOSEND="对端|文本|延迟秒" 自动发一条;GHB_SHOT=/path.png 延迟截屏取证
const auto = process.env.GHB_AUTOSEND;
if (auto) {
const [to, text, delay] = auto.split('|');
setTimeout(() => {
// 走渲染进程的 UI 发送路径,让气泡/状态行真实渲染出来
if (win && !win.isDestroyed()) {
win.webContents.send('ghb:event', { event: '__autosend', to, text });
console.log(`[autotest] 已触发 UI 自动发送 → ${to}`);
}
}, (Number(delay) || 3) * 1000);
}
const shot = process.env.GHB_SHOT;
if (shot) {
setTimeout(async () => {
try {
const img = await win.capturePage();
fs.writeFileSync(shot, img.toPNG());
console.log(`[autotest] 截屏已存 ${shot}`);
} catch (e) { console.error(`[autotest] 截屏失败:${e.message}`); }
}, (Number(process.env.GHB_SHOT_DELAY) || 8) * 1000);
}
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
ipcMain.on('ghb:send', (_e, to, text) => {
if (bridge && bridge.stdin.writable) {
bridge.stdin.write(JSON.stringify({ cmd: 'send', to, text }) + '\n');
}
});
app.on('window-all-closed', () => {
if (bridge) bridge.kill('SIGTERM');
if (process.platform !== 'darwin') app.quit();
else app.quit();
});
app.on('quit', () => { if (bridge) bridge.kill('SIGTERM'); });