main.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. tray = new Tray(path.join(__dirname, 'tray-iconTemplate.png'));
  122. const contextMenu = Menu.buildFromTemplate([
  123. {
  124. label: 'Show New API',
  125. click: () => {
  126. if (mainWindow === null) {
  127. createWindow();
  128. } else {
  129. mainWindow.show();
  130. if (process.platform === 'darwin') {
  131. app.dock.show();
  132. }
  133. }
  134. }
  135. },
  136. { type: 'separator' },
  137. {
  138. label: 'Quit',
  139. click: () => {
  140. app.isQuitting = true;
  141. app.quit();
  142. }
  143. }
  144. ]);
  145. tray.setToolTip('New API');
  146. tray.setContextMenu(contextMenu);
  147. // On macOS, clicking the tray icon shows the window
  148. tray.on('click', () => {
  149. if (mainWindow === null) {
  150. createWindow();
  151. } else {
  152. mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
  153. if (mainWindow.isVisible() && process.platform === 'darwin') {
  154. app.dock.show();
  155. }
  156. }
  157. });
  158. }
  159. app.whenReady().then(async () => {
  160. try {
  161. await startServer();
  162. createTray();
  163. createWindow();
  164. } catch (err) {
  165. console.error('Failed to start application:', err);
  166. dialog.showErrorBox('Startup Error', `Failed to start server: ${err.message}`);
  167. app.quit();
  168. }
  169. });
  170. app.on('window-all-closed', () => {
  171. // Don't quit when window is closed, keep running in tray
  172. // Only quit when explicitly choosing Quit from tray menu
  173. });
  174. app.on('activate', () => {
  175. if (BrowserWindow.getAllWindows().length === 0) {
  176. createWindow();
  177. }
  178. });
  179. app.on('before-quit', (event) => {
  180. if (serverProcess) {
  181. event.preventDefault();
  182. console.log('Shutting down server...');
  183. serverProcess.kill('SIGTERM');
  184. setTimeout(() => {
  185. if (serverProcess) {
  186. serverProcess.kill('SIGKILL');
  187. }
  188. app.exit();
  189. }, 5000);
  190. serverProcess.on('close', () => {
  191. serverProcess = null;
  192. app.exit();
  193. });
  194. }
  195. });