main.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. const { app, BrowserWindow, dialog, Tray, Menu } = require('electron');
  2. const { spawn } = require('child_process');
  3. const path = require('path');
  4. const http = require('http');
  5. const fs = require('fs');
  6. let mainWindow;
  7. let serverProcess;
  8. let tray = null;
  9. const PORT = 3000;
  10. function getBinaryPath() {
  11. const isDev = process.env.NODE_ENV === 'development';
  12. const platform = process.platform;
  13. if (isDev) {
  14. const binaryName = platform === 'win32' ? 'new-api.exe' : 'new-api';
  15. return path.join(__dirname, '..', binaryName);
  16. }
  17. let binaryName;
  18. switch (platform) {
  19. case 'win32':
  20. binaryName = 'new-api.exe';
  21. break;
  22. case 'darwin':
  23. binaryName = 'new-api';
  24. break;
  25. case 'linux':
  26. binaryName = 'new-api';
  27. break;
  28. default:
  29. binaryName = 'new-api';
  30. }
  31. return path.join(process.resourcesPath, 'bin', binaryName);
  32. }
  33. function startServer() {
  34. return new Promise((resolve, reject) => {
  35. const binaryPath = getBinaryPath();
  36. const isDev = process.env.NODE_ENV === 'development';
  37. console.log('Starting server from:', binaryPath);
  38. const env = { ...process.env, PORT: PORT.toString() };
  39. let dataDir;
  40. if (isDev) {
  41. dataDir = path.join(__dirname, '..', 'data');
  42. } else {
  43. const userDataPath = app.getPath('userData');
  44. dataDir = path.join(userDataPath, 'data');
  45. }
  46. if (!fs.existsSync(dataDir)) {
  47. fs.mkdirSync(dataDir, { recursive: true });
  48. }
  49. env.SQLITE_PATH = path.join(dataDir, 'new-api.db');
  50. const workingDir = isDev
  51. ? path.join(__dirname, '..')
  52. : process.resourcesPath;
  53. serverProcess = spawn(binaryPath, [], {
  54. env,
  55. cwd: workingDir
  56. });
  57. serverProcess.stdout.on('data', (data) => {
  58. console.log(`Server: ${data}`);
  59. });
  60. serverProcess.stderr.on('data', (data) => {
  61. console.error(`Server Error: ${data}`);
  62. });
  63. serverProcess.on('error', (err) => {
  64. console.error('Failed to start server:', err);
  65. reject(err);
  66. });
  67. serverProcess.on('close', (code) => {
  68. console.log(`Server process exited with code ${code}`);
  69. if (mainWindow && !mainWindow.isDestroyed()) {
  70. mainWindow.close();
  71. }
  72. });
  73. waitForServer(resolve, reject);
  74. });
  75. }
  76. function waitForServer(resolve, reject, retries = 30) {
  77. if (retries === 0) {
  78. reject(new Error('Server failed to start within timeout'));
  79. return;
  80. }
  81. const req = http.get(`http://localhost:${PORT}`, (res) => {
  82. console.log('Server is ready');
  83. resolve();
  84. });
  85. req.on('error', () => {
  86. setTimeout(() => waitForServer(resolve, reject, retries - 1), 1000);
  87. });
  88. req.end();
  89. }
  90. function createWindow() {
  91. mainWindow = new BrowserWindow({
  92. width: 1400,
  93. height: 900,
  94. webPreferences: {
  95. preload: path.join(__dirname, 'preload.js'),
  96. nodeIntegration: false,
  97. contextIsolation: true
  98. },
  99. title: 'New API',
  100. icon: path.join(__dirname, 'icon.png')
  101. });
  102. mainWindow.loadURL(`http://localhost:${PORT}`);
  103. if (process.env.NODE_ENV === 'development') {
  104. mainWindow.webContents.openDevTools();
  105. }
  106. // Close to tray instead of quitting
  107. mainWindow.on('close', (event) => {
  108. if (!app.isQuitting) {
  109. event.preventDefault();
  110. mainWindow.hide();
  111. if (process.platform === 'darwin') {
  112. app.dock.hide();
  113. }
  114. }
  115. });
  116. mainWindow.on('closed', () => {
  117. mainWindow = null;
  118. });
  119. }
  120. function createTray() {
  121. // Use template icon for macOS (black with transparency, auto-adapts to theme)
  122. // Use colored icon for Windows
  123. const trayIconPath = process.platform === 'darwin'
  124. ? path.join(__dirname, 'tray-iconTemplate.png')
  125. : path.join(__dirname, 'tray-icon-windows.png');
  126. tray = new Tray(trayIconPath);
  127. const contextMenu = Menu.buildFromTemplate([
  128. {
  129. label: 'Show New API',
  130. click: () => {
  131. if (mainWindow === null) {
  132. createWindow();
  133. } else {
  134. mainWindow.show();
  135. if (process.platform === 'darwin') {
  136. app.dock.show();
  137. }
  138. }
  139. }
  140. },
  141. { type: 'separator' },
  142. {
  143. label: 'Quit',
  144. click: () => {
  145. app.isQuitting = true;
  146. app.quit();
  147. }
  148. }
  149. ]);
  150. tray.setToolTip('New API');
  151. tray.setContextMenu(contextMenu);
  152. // On macOS, clicking the tray icon shows the window
  153. tray.on('click', () => {
  154. if (mainWindow === null) {
  155. createWindow();
  156. } else {
  157. mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
  158. if (mainWindow.isVisible() && process.platform === 'darwin') {
  159. app.dock.show();
  160. }
  161. }
  162. });
  163. }
  164. app.whenReady().then(async () => {
  165. try {
  166. await startServer();
  167. createTray();
  168. createWindow();
  169. } catch (err) {
  170. console.error('Failed to start application:', err);
  171. dialog.showErrorBox('Startup Error', `Failed to start server: ${err.message}`);
  172. app.quit();
  173. }
  174. });
  175. app.on('window-all-closed', () => {
  176. // Don't quit when window is closed, keep running in tray
  177. // Only quit when explicitly choosing Quit from tray menu
  178. });
  179. app.on('activate', () => {
  180. if (BrowserWindow.getAllWindows().length === 0) {
  181. createWindow();
  182. }
  183. });
  184. app.on('before-quit', (event) => {
  185. if (serverProcess) {
  186. event.preventDefault();
  187. console.log('Shutting down server...');
  188. serverProcess.kill('SIGTERM');
  189. setTimeout(() => {
  190. if (serverProcess) {
  191. serverProcess.kill('SIGKILL');
  192. }
  193. app.exit();
  194. }, 5000);
  195. serverProcess.on('close', () => {
  196. serverProcess = null;
  197. app.exit();
  198. });
  199. }
  200. });