119 lines
4.2 KiB
JavaScript
Executable file
119 lines
4.2 KiB
JavaScript
Executable file
// 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'); });
|