import { app, BrowserWindow, dialog, ipcMain } from 'electron'; import { createServer, IncomingMessage, ServerResponse } from 'http'; import next from 'next'; import path from 'path'; import fs from 'fs'; import net from 'net'; // ─── Types ─────────────────────────────────────────────────────────────────── interface AppConfig { recentProjects: RecentProject[]; lastActiveProjectPath?: string; } interface RecentProject { name: string; path: string; lastOpenedAt: string; novelType: string; } // ─── Globals ───────────────────────────────────────────────────────────────── const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged; // 开发模式下直接连接已运行的 Next.js dev server,不自己启动 let nextApp: ReturnType | null = null; let handle: ((req: IncomingMessage, res: ServerResponse) => void) | null = null; if (!isDev) { nextApp = next({ dev: false, dir: path.join(__dirname, '..') }); handle = nextApp.getRequestHandler(); } let httpServer: ReturnType | null = null; let mainWindow: BrowserWindow | null = null; // ─── Port Discovery ─────────────────────────────────────────────────────────── function findAvailablePort(startPort: number): Promise { return new Promise((resolve, reject) => { const server = net.createServer(); server.listen(startPort, '127.0.0.1', () => { const address = server.address(); const port = typeof address === 'object' && address ? address.port : startPort; server.close(() => resolve(port)); }); server.on('error', () => { // Port in use, try next one findAvailablePort(startPort + 1).then(resolve).catch(reject); }); }); } // ─── App Config (userData) ──────────────────────────────────────────────────── function getAppConfigPath(): string { return path.join(app.getPath('userData'), 'app-config.json'); } function readAppConfig(): AppConfig { const configPath = getAppConfigPath(); try { if (fs.existsSync(configPath)) { const raw = fs.readFileSync(configPath, 'utf-8'); return JSON.parse(raw) as AppConfig; } } catch { // Return default config if file is missing or malformed } return { recentProjects: [] }; } function writeAppConfig(config: AppConfig): void { const configPath = getAppConfigPath(); const dir = path.dirname(configPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // Atomic write: write to temp file then rename const tmpPath = configPath + '.tmp'; fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), 'utf-8'); fs.renameSync(tmpPath, configPath); } // ─── IPC Handlers ───────────────────────────────────────────────────────────── // Select a folder using the native OS dialog ipcMain.handle('select-folder', async () => { const result = await dialog.showOpenDialog({ properties: ['openDirectory'], }); return result.canceled ? null : result.filePaths[0]; }); // Read global app config from userData ipcMain.handle('get-app-config', () => { return readAppConfig(); }); // Write global app config to userData ipcMain.handle('save-app-config', (_event, config: AppConfig) => { writeAppConfig(config); return { success: true }; }); // ─── App Startup ────────────────────────────────────────────────────────────── async function startApp(): Promise { let appPort = 3000; if (isDev) { // 开发模式:直接连接已运行的 Next.js dev server (localhost:3000) appPort = 3000; } else { // 生产模式:自己启动内嵌 Next.js 服务器 await nextApp!.prepare(); appPort = await findAvailablePort(3000); httpServer = createServer((req: IncomingMessage, res: ServerResponse) => { handle!(req, res); }); await new Promise((resolve) => { httpServer!.listen(appPort, '127.0.0.1', () => { resolve(); }); }); } mainWindow = new BrowserWindow({ width: 1400, height: 900, webPreferences: { contextIsolation: true, nodeIntegration: false, preload: path.join(__dirname, 'preload.js'), }, }); mainWindow.loadURL(`http://localhost:${appPort}/features/novel-writer`); mainWindow.on('closed', () => { mainWindow = null; }); } // ─── Lifecycle ──────────────────────────────────────────────────────────────── app.whenReady().then(startApp).catch((err) => { console.error('Failed to start app:', err); app.quit(); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (mainWindow === null) { startApp().catch((err) => { console.error('Failed to restart app:', err); }); } }); app.on('before-quit', () => { if (httpServer) { httpServer.close(); httpServer = null; } });