|
|
@@ -0,0 +1,239 @@
|
|
|
+const { app, BrowserWindow, dialog, Tray, Menu } = require('electron');
|
|
|
+const { spawn } = require('child_process');
|
|
|
+const path = require('path');
|
|
|
+const http = require('http');
|
|
|
+const fs = require('fs');
|
|
|
+
|
|
|
+let mainWindow;
|
|
|
+let serverProcess;
|
|
|
+let tray = null;
|
|
|
+const PORT = 3000;
|
|
|
+
|
|
|
+function getBinaryPath() {
|
|
|
+ const isDev = process.env.NODE_ENV === 'development';
|
|
|
+ const platform = process.platform;
|
|
|
+
|
|
|
+ if (isDev) {
|
|
|
+ const binaryName = platform === 'win32' ? 'new-api.exe' : 'new-api';
|
|
|
+ return path.join(__dirname, '..', binaryName);
|
|
|
+ }
|
|
|
+
|
|
|
+ let binaryName;
|
|
|
+ switch (platform) {
|
|
|
+ case 'win32':
|
|
|
+ binaryName = 'new-api.exe';
|
|
|
+ break;
|
|
|
+ case 'darwin':
|
|
|
+ binaryName = 'new-api';
|
|
|
+ break;
|
|
|
+ case 'linux':
|
|
|
+ binaryName = 'new-api';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ binaryName = 'new-api';
|
|
|
+ }
|
|
|
+
|
|
|
+ return path.join(process.resourcesPath, 'bin', binaryName);
|
|
|
+}
|
|
|
+
|
|
|
+function startServer() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const binaryPath = getBinaryPath();
|
|
|
+ const isDev = process.env.NODE_ENV === 'development';
|
|
|
+
|
|
|
+ console.log('Starting server from:', binaryPath);
|
|
|
+
|
|
|
+ const env = { ...process.env, PORT: PORT.toString() };
|
|
|
+
|
|
|
+ let dataDir;
|
|
|
+ if (isDev) {
|
|
|
+ dataDir = path.join(__dirname, '..', 'data');
|
|
|
+ } else {
|
|
|
+ const userDataPath = app.getPath('userData');
|
|
|
+ dataDir = path.join(userDataPath, 'data');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!fs.existsSync(dataDir)) {
|
|
|
+ fs.mkdirSync(dataDir, { recursive: true });
|
|
|
+ }
|
|
|
+
|
|
|
+ env.SQLITE_PATH = path.join(dataDir, 'new-api.db');
|
|
|
+
|
|
|
+ const workingDir = isDev
|
|
|
+ ? path.join(__dirname, '..')
|
|
|
+ : process.resourcesPath;
|
|
|
+
|
|
|
+ serverProcess = spawn(binaryPath, [], {
|
|
|
+ env,
|
|
|
+ cwd: workingDir
|
|
|
+ });
|
|
|
+
|
|
|
+ serverProcess.stdout.on('data', (data) => {
|
|
|
+ console.log(`Server: ${data}`);
|
|
|
+ });
|
|
|
+
|
|
|
+ serverProcess.stderr.on('data', (data) => {
|
|
|
+ console.error(`Server Error: ${data}`);
|
|
|
+ });
|
|
|
+
|
|
|
+ serverProcess.on('error', (err) => {
|
|
|
+ console.error('Failed to start server:', err);
|
|
|
+ reject(err);
|
|
|
+ });
|
|
|
+
|
|
|
+ serverProcess.on('close', (code) => {
|
|
|
+ console.log(`Server process exited with code ${code}`);
|
|
|
+ if (mainWindow && !mainWindow.isDestroyed()) {
|
|
|
+ mainWindow.close();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ waitForServer(resolve, reject);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function waitForServer(resolve, reject, retries = 30) {
|
|
|
+ if (retries === 0) {
|
|
|
+ reject(new Error('Server failed to start within timeout'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const req = http.get(`http://localhost:${PORT}`, (res) => {
|
|
|
+ console.log('Server is ready');
|
|
|
+ resolve();
|
|
|
+ });
|
|
|
+
|
|
|
+ req.on('error', () => {
|
|
|
+ setTimeout(() => waitForServer(resolve, reject, retries - 1), 1000);
|
|
|
+ });
|
|
|
+
|
|
|
+ req.end();
|
|
|
+}
|
|
|
+
|
|
|
+function createWindow() {
|
|
|
+ mainWindow = new BrowserWindow({
|
|
|
+ width: 1400,
|
|
|
+ height: 900,
|
|
|
+ webPreferences: {
|
|
|
+ preload: path.join(__dirname, 'preload.js'),
|
|
|
+ nodeIntegration: false,
|
|
|
+ contextIsolation: true
|
|
|
+ },
|
|
|
+ title: 'New API',
|
|
|
+ icon: path.join(__dirname, 'icon.png')
|
|
|
+ });
|
|
|
+
|
|
|
+ mainWindow.loadURL(`http://localhost:${PORT}`);
|
|
|
+
|
|
|
+ if (process.env.NODE_ENV === 'development') {
|
|
|
+ mainWindow.webContents.openDevTools();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Close to tray instead of quitting
|
|
|
+ mainWindow.on('close', (event) => {
|
|
|
+ if (!app.isQuitting) {
|
|
|
+ event.preventDefault();
|
|
|
+ mainWindow.hide();
|
|
|
+ if (process.platform === 'darwin') {
|
|
|
+ app.dock.hide();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ mainWindow.on('closed', () => {
|
|
|
+ mainWindow = null;
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function createTray() {
|
|
|
+ // Use template icon for macOS (black with transparency, auto-adapts to theme)
|
|
|
+ // Use colored icon for Windows
|
|
|
+ const trayIconPath = process.platform === 'darwin'
|
|
|
+ ? path.join(__dirname, 'tray-iconTemplate.png')
|
|
|
+ : path.join(__dirname, 'tray-icon-windows.png');
|
|
|
+
|
|
|
+ tray = new Tray(trayIconPath);
|
|
|
+
|
|
|
+ const contextMenu = Menu.buildFromTemplate([
|
|
|
+ {
|
|
|
+ label: 'Show New API',
|
|
|
+ click: () => {
|
|
|
+ if (mainWindow === null) {
|
|
|
+ createWindow();
|
|
|
+ } else {
|
|
|
+ mainWindow.show();
|
|
|
+ if (process.platform === 'darwin') {
|
|
|
+ app.dock.show();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { type: 'separator' },
|
|
|
+ {
|
|
|
+ label: 'Quit',
|
|
|
+ click: () => {
|
|
|
+ app.isQuitting = true;
|
|
|
+ app.quit();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]);
|
|
|
+
|
|
|
+ tray.setToolTip('New API');
|
|
|
+ tray.setContextMenu(contextMenu);
|
|
|
+
|
|
|
+ // On macOS, clicking the tray icon shows the window
|
|
|
+ tray.on('click', () => {
|
|
|
+ if (mainWindow === null) {
|
|
|
+ createWindow();
|
|
|
+ } else {
|
|
|
+ mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
|
|
|
+ if (mainWindow.isVisible() && process.platform === 'darwin') {
|
|
|
+ app.dock.show();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+app.whenReady().then(async () => {
|
|
|
+ try {
|
|
|
+ await startServer();
|
|
|
+ createTray();
|
|
|
+ createWindow();
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Failed to start application:', err);
|
|
|
+ dialog.showErrorBox('Startup Error', `Failed to start server: ${err.message}`);
|
|
|
+ app.quit();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+app.on('window-all-closed', () => {
|
|
|
+ // Don't quit when window is closed, keep running in tray
|
|
|
+ // Only quit when explicitly choosing Quit from tray menu
|
|
|
+});
|
|
|
+
|
|
|
+app.on('activate', () => {
|
|
|
+ if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
+ createWindow();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+app.on('before-quit', (event) => {
|
|
|
+ if (serverProcess) {
|
|
|
+ event.preventDefault();
|
|
|
+
|
|
|
+ console.log('Shutting down server...');
|
|
|
+ serverProcess.kill('SIGTERM');
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ if (serverProcess) {
|
|
|
+ serverProcess.kill('SIGKILL');
|
|
|
+ }
|
|
|
+ app.exit();
|
|
|
+ }, 5000);
|
|
|
+
|
|
|
+ serverProcess.on('close', () => {
|
|
|
+ serverProcess = null;
|
|
|
+ app.exit();
|
|
|
+ });
|
|
|
+ }
|
|
|
+});
|