import { app, BrowserWindow, ipcMain, dialog, Tray, Menu, nativeImage } from 'electron'; import * as path from 'path'; import Store from 'electron-store'; import { RelayClient } from './relay-client'; import { LocalProxy } from './local-proxy'; import { ApiClient } from './api-client'; const store = new Store({ defaults: { serverUrl: 'http://localhost:3000', playerName: 'Player', localPort: 25566, recentServers: [] as string[], autoReconnect: true, minimizeToTray: true, } }); let mainWindow: BrowserWindow | null = null; let tray: Tray | null = null; let relayClient: RelayClient | null = null; let localProxy: LocalProxy | null = null; let apiClient: ApiClient | null = null; function createWindow() { mainWindow = new BrowserWindow({ width: 900, height: 650, minWidth: 800, minHeight: 600, title: 'FunConnect - Minecraft 联机客户端', frame: false, titleBarStyle: 'hidden', backgroundColor: '#1a1a2e', webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, }, }); mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html')); mainWindow.on('close', (e) => { if (store.get('minimizeToTray') && tray) { e.preventDefault(); mainWindow?.hide(); } }); mainWindow.on('closed', () => { mainWindow = null; cleanup(); }); } function createTray() { const icon = nativeImage.createEmpty(); tray = new Tray(icon); tray.setToolTip('FunConnect - Minecraft 联机客户端'); tray.setContextMenu(Menu.buildFromTemplate([ { label: '显示主窗口', click: () => { mainWindow?.show(); mainWindow?.focus(); } }, { type: 'separator' }, { label: '断开连接', click: () => cleanup() }, { type: 'separator' }, { label: '退出', click: () => { cleanup(); app.quit(); } }, ])); tray.on('double-click', () => { mainWindow?.show(); mainWindow?.focus(); }); } function cleanup() { if (localProxy) { localProxy.stop(); localProxy = null; } if (relayClient) { relayClient.disconnect(); relayClient = null; } } app.whenReady().then(() => { createWindow(); createTray(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); else mainWindow?.show(); }); }); app.on('window-all-closed', () => { cleanup(); if (process.platform !== 'darwin') app.quit(); }); // ===== IPC Handlers ===== // Window controls ipcMain.handle('window:minimize', () => mainWindow?.minimize()); ipcMain.handle('window:maximize', () => { if (mainWindow?.isMaximized()) mainWindow.unmaximize(); else mainWindow?.maximize(); }); ipcMain.handle('window:close', () => mainWindow?.close()); // Server connection ipcMain.handle('server:connect', async (_event, serverUrl: string) => { try { apiClient = new ApiClient(serverUrl); const health = await apiClient.getHealth(); return { success: true, data: health }; } catch (err: any) { return { success: false, error: err.message }; } }); // Room operations ipcMain.handle('rooms:list', async () => { if (!apiClient) return { success: false, error: '未连接服务器' }; try { const data = await apiClient.getRooms(); return { success: true, data }; } catch (err: any) { return { success: false, error: err.message }; } }); ipcMain.handle('rooms:create', async (_event, roomData: any) => { if (!apiClient) return { success: false, error: '未连接服务器' }; try { const data = await apiClient.createRoom(roomData); return { success: true, data }; } catch (err: any) { return { success: false, error: err.response?.data?.error || err.message }; } }); ipcMain.handle('rooms:delete', async (_event, roomId: string) => { if (!apiClient) return { success: false, error: '未连接服务器' }; try { await apiClient.deleteRoom(roomId); return { success: true }; } catch (err: any) { return { success: false, error: err.message }; } }); // Join room - start local proxy and relay connection ipcMain.handle('rooms:join', async (_event, opts: { serverHost: string; serverPort: number; roomId: string; localPort: number }) => { try { cleanup(); relayClient = new RelayClient(opts.serverHost, opts.serverPort, opts.roomId); localProxy = new LocalProxy(opts.localPort, relayClient); await localProxy.start(); relayClient.on('connected', () => { mainWindow?.webContents.send('relay:status', { status: 'connected' }); }); relayClient.on('disconnected', () => { mainWindow?.webContents.send('relay:status', { status: 'disconnected' }); }); relayClient.on('error', (err: string) => { mainWindow?.webContents.send('relay:status', { status: 'error', error: err }); }); return { success: true, localPort: opts.localPort }; } catch (err: any) { return { success: false, error: err.message }; } }); // Host room - start relay and local proxy from local MC server ipcMain.handle('rooms:host', async (_event, opts: { serverUrl: string; roomName: string; localMcPort: number; gameVersion: string; gameEdition: string; maxPlayers: number; password?: string }) => { if (!apiClient) return { success: false, error: '未连接服务器' }; try { const result = await apiClient.createRoom({ name: opts.roomName, hostName: 'FunConnect', hostPort: opts.localMcPort, gameVersion: opts.gameVersion, gameEdition: opts.gameEdition, maxPlayers: opts.maxPlayers, password: opts.password, }); return { success: true, data: result }; } catch (err: any) { return { success: false, error: err.response?.data?.error || err.message }; } }); // Disconnect ipcMain.handle('relay:disconnect', () => { cleanup(); return { success: true }; }); // Stats ipcMain.handle('server:stats', async () => { if (!apiClient) return { success: false, error: '未连接服务器' }; try { const data = await apiClient.getStats(); return { success: true, data }; } catch (err: any) { return { success: false, error: err.message }; } }); // ===== Settings ===== ipcMain.handle('settings:get', () => { return { serverUrl: store.get('serverUrl'), playerName: store.get('playerName'), localPort: store.get('localPort'), recentServers: store.get('recentServers'), autoReconnect: store.get('autoReconnect'), minimizeToTray: store.get('minimizeToTray'), }; }); ipcMain.handle('settings:set', (_event, settings: Record) => { for (const [key, value] of Object.entries(settings)) { store.set(key, value); } return { success: true }; }); ipcMain.handle('settings:addRecentServer', (_event, url: string) => { const recent = store.get('recentServers') as string[]; const filtered = recent.filter((s: string) => s !== url); filtered.unshift(url); store.set('recentServers', filtered.slice(0, 10)); return { success: true }; }); // ===== Room password verification ===== ipcMain.handle('rooms:verify', async (_event, opts: { roomId: string; password?: string }) => { if (!apiClient) return { success: false, error: '未连接服务器' }; try { const data = await apiClient.joinRoom(opts.roomId, opts.password); return { success: true, data }; } catch (err: any) { return { success: false, error: err.response?.data?.error || err.message }; } });