const { app, BrowserWindow, ipcMain, dialog } = require('electron'); const path = require('path'); const fs = require('fs'); const Store = require('electron-store'); const { v4: uuidv4 } = require('uuid'); const { WSClient } = require('@wecom/aibot-node-sdk'); const WebSocket = require('ws'); // 初始化配置存储 const store = new Store({ name: 'config', defaults: { bots: [], // 多 Bot 配置 [{botId, secret, name, enabled}] openclaw: { url: 'ws://localhost:18789', token: '', enabled: true } } }); let mainWindow; let wecomClients = new Map(); // botId -> WSClient let openclawSocket = null; let heartbeatInterval = null; // 创建主窗口 function createWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js') } }); const isDev = process.env.NODE_ENV === 'development'; if (isDev) { mainWindow.loadURL('http://localhost:3000'); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, '../renderer/build/index.html')); } mainWindow.on('closed', () => { mainWindow = null; }); } // 企业微信 WebSocket 连接管理 class WeComConnection { constructor(botId, secret, eventHandler) { this.botId = botId; this.secret = secret; this.eventHandler = eventHandler; this.client = null; this.isConnected = false; this.reconnectAttempts = 0; this.maxReconnectAttempts = 100; } async connect() { try { this.client = new WSClient({ botId: this.botId, secret: this.secret, onConnected: () => { this.isConnected = true; this.reconnectAttempts = 0; this.eventHandler('connected', { botId: this.botId }); console.log(`[WeCom] Bot ${this.botId} connected`); }, onDisconnected: () => { this.isConnected = false; this.eventHandler('disconnected', { botId: this.botId }); console.log(`[WeCom] Bot ${this.botId} disconnected`); this.scheduleReconnect(); }, onMessage: (frame) => { this.eventHandler('message', { botId: this.botId, frame }); }, onError: (error) => { this.eventHandler('error', { botId: this.botId, error: error.message }); console.error(`[WeCom] Bot ${this.botId} error:`, error); } }); await this.client.connect(); } catch (error) { this.eventHandler('error', { botId: this.botId, error: error.message }); this.scheduleReconnect(); } } scheduleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error(`[WeCom] Bot ${this.botId} max reconnect attempts reached`); return; } this.reconnectAttempts++; const delay = Math.min(1000 * Math.pow(1.5, this.reconnectAttempts), 60000); console.log(`[WeCom] Bot ${this.botId} reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`); setTimeout(() => this.connect(), delay); } async sendCommand(cmd, body) { if (!this.client || !this.isConnected) { throw new Error('Not connected'); } return await this.client.send({ cmd, headers: { req_id: uuidv4() }, body }); } disconnect() { if (this.client) { this.client.close(); this.isConnected = false; } } } // OpenClaw Gateway WebSocket 连接 class OpenClawConnection { constructor(url, token, eventHandler) { this.url = url; this.token = token; this.eventHandler = eventHandler; this.socket = null; this.isConnected = false; this.reconnectAttempts = 0; this.maxReconnectAttempts = 100; this.messageId = 0; } async connect() { try { this.socket = new WebSocket(this.url); this.socket.on('open', () => { this.isConnected = true; this.reconnectAttempts = 0; this.eventHandler('connected'); console.log('[OpenClaw] Connected'); // 发送 connect 请求 this.sendConnect(); }); this.socket.on('message', (data) => { try { const message = JSON.parse(data.toString()); this.eventHandler('message', message); } catch (error) { console.error('[OpenClaw] Parse error:', error); } }); this.socket.on('close', () => { this.isConnected = false; this.eventHandler('disconnected'); console.log('[OpenClaw] Disconnected'); this.scheduleReconnect(); }); this.socket.on('error', (error) => { this.eventHandler('error', { error: error.message }); console.error('[OpenClaw] Error:', error); }); } catch (error) { this.eventHandler('error', { error: error.message }); this.scheduleReconnect(); } } sendConnect() { const connectMessage = { type: 'req', id: this.nextId(), method: 'connect', params: { minProtocol: 3, maxProtocol: 3, client: { id: 'wecome-client', version: '1.0.0', platform: process.platform, mode: 'operator' }, role: 'operator', scopes: ['operator.read', 'operator.write'], auth: this.token ? { token: this.token } : {}, device: { id: this.getDeviceId(), publicKey: '', signature: '', signedAt: Date.now(), nonce: uuidv4() } } }; this.send(connectMessage); } send(message) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify(message)); } } nextId() { return `msg_${++this.messageId}_${Date.now()}`; } getDeviceId() { return `wecome-client_${process.platform}_${uuidv4()}`; } scheduleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('[OpenClaw] Max reconnect attempts reached'); return; } this.reconnectAttempts++; const delay = Math.min(1000 * Math.pow(1.5, this.reconnectAttempts), 60000); console.log(`[OpenClaw] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`); setTimeout(() => this.connect(), delay); } disconnect() { if (this.socket) { this.socket.close(); this.isConnected = false; } } } // IPC 处理器 function setupIpcHandlers() { // 获取配置 ipcMain.handle('get-config', () => { return store.store; }); // 保存配置 ipcMain.handle('save-config', (event, config) => { store.set(config); return { success: true }; }); // 连接企业微信 Bot ipcMain.handle('connect-wecom', (event, botConfig) => { const { botId, secret } = botConfig; if (wecomClients.has(botId)) { wecomClients.get(botId).disconnect(); } const connection = new WeComConnection(botId, secret, (eventType, data) => { mainWindow.webContents.send('wecom-event', { eventType, data }); }); wecomClients.set(botId, connection); connection.connect(); return { success: true }; }); // 断开企业微信 Bot ipcMain.handle('disconnect-wecom', (event, botId) => { if (wecomClients.has(botId)) { wecomClients.get(botId).disconnect(); wecomClients.delete(botId); } return { success: true }; }); // 连接 OpenClaw ipcMain.handle('connect-openclaw', (event, config) => { const { url, token } = config; if (openclawSocket) { openclawSocket.disconnect(); } openclawSocket = new OpenClawConnection(url, token, (eventType, data) => { mainWindow.webContents.send('openclaw-event', { eventType, data }); }); openclawSocket.connect(); return { success: true }; }); // 断开 OpenClaw ipcMain.handle('disconnect-openclaw', () => { if (openclawSocket) { openclawSocket.disconnect(); openclawSocket = null; } return { success: true }; }); // 获取连接状态 ipcMain.handle('get-connection-status', () => { const wecomStatus = {}; wecomClients.forEach((conn, botId) => { wecomStatus[botId] = { connected: conn.isConnected, reconnectAttempts: conn.reconnectAttempts }; }); return { wecom: wecomStatus, openclaw: openclawSocket ? { connected: openclawSocket.isConnected } : { connected: false } }; }); // 发送消息到企业微信 ipcMain.handle('send-wecom-message', (event, botId, message) => { if (!wecomClients.has(botId)) { return { success: false, error: 'Bot not connected' }; } const connection = wecomClients.get(botId); return connection.sendCommand('aibot_send_msg', message) .then(() => ({ success: true })) .catch(error => ({ success: false, error: error.message })); }); // 选择配置文件 ipcMain.handle('select-file', async (event, options) => { const result = await dialog.showOpenDialog(mainWindow, options); return result; }); } // 应用生命周期 app.whenReady().then(() => { createWindow(); setupIpcHandlers(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { // 清理所有连接 wecomClients.forEach(conn => conn.disconnect()); if (openclawSocket) { openclawSocket.disconnect(); } if (process.platform !== 'darwin') { app.quit(); } }); app.on('quit', () => { // 清理所有连接 wecomClients.forEach(conn => conn.disconnect()); if (openclawSocket) { openclawSocket.disconnect(); } }); // 安全策略 app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors');