const { app, BrowserWindow, Menu, shell, ipcMain, Notification } = require('electron'); const path = require('path'); const fs = require('fs'); const logger = require('./js/core/Logger'); console.log('[MAIN PROCESS] Electron main process starting...'); console.log('[MAIN PROCESS] Node.js version:', process.version); console.log('[MAIN PROCESS] Electron version:', process.versions.electron); console.log('[MAIN PROCESS] Platform:', process.platform); console.log('[MAIN PROCESS] Current working directory:', process.cwd()); // Keep a global reference of the window object let mainWindow; function createWindow() { console.log('[MAIN PROCESS] createWindow() called'); try { console.log('[MAIN PROCESS] Creating BrowserWindow...'); // Create the browser window mainWindow = new BrowserWindow({ width: 1200, height: 832, // 800 + 32px for custom title bar minWidth: 1200, minHeight: 832, maxWidth: 1200, maxHeight: 832, resizable: false, frame: false, titleBarStyle: 'hidden', webPreferences: { nodeIntegration: true, contextIsolation: false, enableRemoteModule: true, webSecurity: true }, icon: path.join(__dirname, 'assets/icon.png'), show: false, // Don't show until ready-to-show title: 'Galaxy Strike Online' }); console.log('[MAIN PROCESS] BrowserWindow created successfully'); console.log('[MAIN PROCESS] Loading index.html...'); // Load the index.html file mainWindow.loadFile('index.html'); console.log('[MAIN PROCESS] index.html loaded, setting up electronAPI...'); // Set up electronAPI after DOM is ready mainWindow.webContents.on('dom-ready', () => { console.log('[MAIN PROCESS] DOM is ready, setting up electronAPI...'); mainWindow.webContents.executeJavaScript(` console.log('[RENDERER] Setting up electronAPI...'); window.electronAPI = { minimizeWindow: () => require('electron').ipcRenderer.send('minimize-window'), closeWindow: () => require('electron').ipcRenderer.send('close-window'), toggleFullscreen: () => require('electron').ipcRenderer.send('toggle-fullscreen'), log: (level, message, data) => require('electron').ipcRenderer.send('log-message', { level, message, data }), createSaveFolders: (saveSlots) => require('electron').ipcRenderer.invoke('create-save-folders', saveSlots), testFileAccess: (slotPath) => require('electron').ipcRenderer.invoke('test-file-access', slotPath), saveGame: (slot, saveData) => require('electron').ipcRenderer.invoke('save-game', slot, saveData), loadGame: (slot) => require('electron').ipcRenderer.invoke('load-game', slot), getPath: (name) => require('electron').ipcRenderer.invoke('get-path', name), deleteSaveFile: (slot) => require('electron').ipcRenderer.invoke('delete-save-file', slot) }; console.log('[RENDERER] electronAPI setup completed'); `).then(() => { console.log('[MAIN PROCESS] electronAPI setup completed'); }).catch((error) => { console.error('[MAIN PROCESS] Failed to setup electronAPI:', error); }); }); // Show window when ready mainWindow.once('ready-to-show', () => { console.log('[MAIN PROCESS] Window ready-to-show event fired'); mainWindow.show(); }); // Open DevTools in development if (process.argv.includes('--dev')) { console.log('[MAIN PROCESS] Opening DevTools...'); mainWindow.webContents.openDevTools(); } // Handle window closed mainWindow.on('closed', () => { console.log('[MAIN PROCESS] Window closed event fired'); mainWindow = null; }); // Handle renderer process crashes mainWindow.webContents.on('render-process-gone', (event, details) => { console.error('[MAIN PROCESS] Renderer process crashed:', details); console.error('[MAIN PROCESS] Crash reason:', details.reason); console.error('[MAIN PROCESS] Exit code:', details.exitCode); }); // Handle renderer process unresponsive mainWindow.webContents.on('unresponsive', () => { console.warn('[MAIN PROCESS] Renderer process unresponsive'); }); // Handle renderer process responsive again mainWindow.webContents.on('responsive', () => { console.log('[MAIN PROCESS] Renderer process responsive again'); }); // Handle console messages from renderer mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => { console.log(`[RENDERER CONSOLE] [${level}] ${message} (line: ${line}, source: ${sourceId})`); }); // Handle page load errors mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => { console.error('[MAIN PROCESS] Page failed to load:', errorCode, errorDescription, validatedURL); }); // Handle page load success mainWindow.webContents.on('did-finish-load', () => { console.log('[MAIN PROCESS] Page finished loading'); }); // Handle DOM ready mainWindow.webContents.on('dom-ready', () => { console.log('[MAIN PROCESS] DOM is ready'); }); // Handle external links mainWindow.webContents.setWindowOpenHandler(({ url }) => { console.log('[MAIN PROCESS] External link requested:', url); shell.openExternal(url); return { action: 'deny' }; }); console.log('[MAIN PROCESS] createWindow() completed successfully'); } catch (error) { console.error('[MAIN PROCESS] Error in createWindow():', error); console.error('[MAIN PROCESS] Error stack:', error.stack); } } // IPC handlers for save operations ipcMain.handle('create-save-folders', async (event, saveSlots) => { console.log('[MAIN PROCESS] create-save-folders called with saveSlots:', saveSlots); try { const userDataPath = app.getPath('userData'); console.log('[MAIN PROCESS] userDataPath:', userDataPath); const savesDir = path.join(userDataPath, 'saves'); console.log('[MAIN PROCESS] savesDir:', savesDir); // Create main saves directory if (!fs.existsSync(savesDir)) { console.log('[MAIN PROCESS] Creating saves directory:', savesDir); fs.mkdirSync(savesDir, { recursive: true }); console.log('[MAIN PROCESS] Saves directory created successfully'); } else { console.log('[MAIN PROCESS] Saves directory already exists'); } const paths = { base: savesDir, slots: [] }; // Create save slot directories for (let i = 1; i <= saveSlots; i++) { const slotDir = path.join(savesDir, `slot${i}`); console.log(`[MAIN PROCESS] Checking/creating slot ${i} directory:`, slotDir); if (!fs.existsSync(slotDir)) { console.log(`[MAIN PROCESS] Creating slot ${i} directory`); fs.mkdirSync(slotDir, { recursive: true }); // Create initial save info file const saveInfo = { slot: i, created: new Date().toISOString(), version: '1.0.0', exists: false }; const infoPath = path.join(slotDir, 'saveinfo.json'); fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2)); console.log(`[MAIN PROCESS] Created save info for slot ${i}`); } else { console.log(`[MAIN PROCESS] Slot ${i} directory already exists`); } paths.slots.push(slotDir); } console.log('[MAIN PROCESS] Save folders created successfully, returning paths:', paths); return { success: true, paths }; } catch (error) { console.error('[MAIN PROCESS] Failed to create save folders:', error); return { success: false, error: error.message }; } }); ipcMain.handle('test-file-access', async (event, slotPath) => { try { const testFile = path.join(slotPath, 'access_test.txt'); fs.writeFileSync(testFile, 'test'); fs.unlinkSync(testFile); return { success: true }; } catch (error) { return { success: false, error: error.message }; } }); ipcMain.handle('save-game', async (event, slot, saveData) => { try { const userDataPath = app.getPath('userData'); const savesDir = path.join(userDataPath, 'saves'); const slotDir = path.join(savesDir, `slot${slot}`); // Save game data const saveFilePath = path.join(slotDir, 'save.json'); fs.writeFileSync(saveFilePath, JSON.stringify(saveData, null, 2)); // Update save info const infoPath = path.join(slotDir, 'saveinfo.json'); const saveInfo = { slot: slot, created: new Date().toISOString(), lastSaved: new Date().toISOString(), version: '1.0.0', exists: true, playTime: saveData.gameTime || 0 }; fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2)); return { success: true }; } catch (error) { console.error('Failed to save game:', error); return { success: false, error: error.message }; } }); ipcMain.handle('load-game', async (event, slot) => { try { const userDataPath = app.getPath('userData'); const savesDir = path.join(userDataPath, 'saves'); const slotDir = path.join(savesDir, `slot${slot}`); const saveFilePath = path.join(slotDir, 'save.json'); if (fs.existsSync(saveFilePath)) { const saveContent = fs.readFileSync(saveFilePath, 'utf8'); const saveData = JSON.parse(saveContent); return { success: true, data: saveData }; } else { return { success: false, error: 'Save file not found' }; } } catch (error) { console.error('Failed to load game:', error); return { success: false, error: error.message }; } }); ipcMain.handle('get-path', async (event, name) => { try { return app.getPath(name); } catch (error) { return null; } }); ipcMain.handle('delete-save-file', async (event, slot) => { console.log('[MAIN PROCESS] delete-save-file called for slot:', slot); try { const userDataPath = app.getPath('userData'); const savesDir = path.join(userDataPath, 'saves'); const slotDir = path.join(savesDir, `slot${slot}`); const saveFilePath = path.join(slotDir, 'save.json'); const infoFilePath = path.join(slotDir, 'saveinfo.json'); console.log('[MAIN PROCESS] Attempting to delete save files from:', slotDir); let deletedFiles = []; // Delete save file if it exists if (fs.existsSync(saveFilePath)) { console.log('[MAIN PROCESS] Deleting save file:', saveFilePath); fs.unlinkSync(saveFilePath); deletedFiles.push('save.json'); } // Delete save info file if it exists if (fs.existsSync(infoFilePath)) { console.log('[MAIN PROCESS] Deleting save info file:', infoFilePath); fs.unlinkSync(infoFilePath); deletedFiles.push('saveinfo.json'); } // Create empty save info file to indicate slot is empty const saveInfo = { slot: slot, created: new Date().toISOString(), version: '1.0.0', exists: false, deleted: new Date().toISOString() }; fs.writeFileSync(infoFilePath, JSON.stringify(saveInfo, null, 2)); console.log('[MAIN PROCESS] Successfully deleted save files for slot', slot, ':', deletedFiles); return { success: true, deletedFiles }; } catch (error) { console.error('[MAIN PROCESS] Failed to delete save file:', error); return { success: false, error: error.message }; } }); // IPC handlers for window controls // ── Push Notifications (Electron desktop) ──────────────────────────────────── ipcMain.on('show-notification', (event, { title, body, icon, tag }) => { try { if (!Notification.isSupported()) return; const n = new Notification({ title: title || 'Galaxy Strike Online', body: body || '', icon: icon || path.join(__dirname, 'assets/icon.png'), silent: false, urgency: 'normal', // 'low' | 'normal' | 'critical' }); n.on('click', () => { if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } }); n.show(); } catch (err) { console.error('[MAIN PROCESS] Notification error:', err.message); } }); // Allow renderer to check if window is focused (suppress notifications when in focus) ipcMain.handle('is-window-focused', () => { return mainWindow ? mainWindow.isFocused() : true; }); // Handle logging from renderer process ipcMain.on('log-message', async (event, { level, message, data }) => { try { switch (level) { case 'error': await logger.error(message, data); break; case 'warn': await logger.warn(message, data); break; case 'info': await logger.info(message, data); break; case 'debug': await logger.debug(message, data); break; default: await logger.info(message, data); } } catch (error) { console.error('Failed to log message from renderer:', error); // Fallback to console logging to prevent infinite loops console.log(`[${level}] ${message}`, data || ''); } }); ipcMain.on('minimize-window', () => { if (mainWindow) { mainWindow.minimize(); } }); ipcMain.on('close-window', () => { if (mainWindow) { mainWindow.close(); } }); ipcMain.on('toggle-fullscreen', () => { if (mainWindow) { const isFullscreen = mainWindow.isFullScreen(); if (isFullscreen) { mainWindow.setFullScreen(false); mainWindow.setSize(1200, 832); mainWindow.center(); } else { mainWindow.setFullScreen(true); } } }); // This method will be called when Electron has finished initialization app.whenReady().then(async () => { console.log('[MAIN PROCESS] Electron app ready, starting initialization...'); try { // Initialize logger with app data path console.log('[MAIN PROCESS] Initializing logger...'); await logger.initialize(app.getPath('userData')); console.log('[MAIN PROCESS] Logger initialized'); await logger.info('Galaxy Strike Online application starting'); console.log('[MAIN PROCESS] Logger info message sent'); console.log('[MAIN PROCESS] Creating main window...'); createWindow(); app.on('activate', () => { console.log('[MAIN PROCESS] Activate event fired'); // On macOS it's common to re-create a window in the app when the dock icon is clicked if (BrowserWindow.getAllWindows().length === 0) { console.log('[MAIN PROCESS] No windows exist, creating new window'); createWindow(); } }); console.log('[MAIN PROCESS] App initialization completed successfully'); } catch (error) { console.error('[MAIN PROCESS] Error during app initialization:', error); console.error('[MAIN PROCESS] Error stack:', error.stack); } }).catch((error) => { console.error('[MAIN PROCESS] Error in app.whenReady():', error); console.error('[MAIN PROCESS] Error stack:', error.stack); }); // Quit when all windows are closed app.on('window-all-closed', () => { // On macOS it's common for applications and their menu bar to stay active if (process.platform !== 'darwin') { logger.info('Application shutting down'); app.quit(); } }); // Handle uncaught exceptions process.on('uncaughtException', async (error) => { console.error('[MAIN PROCESS] Uncaught Exception:', error); console.error('[MAIN PROCESS] Uncaught Exception stack:', error.stack); try { if (logger && typeof logger.errorEvent === 'function') { await logger.errorEvent(error, 'Uncaught Exception in Main Process'); } } catch (logError) { console.error('[MAIN PROCESS] Failed to log uncaught exception:', logError); } console.error('[MAIN PROCESS] Application will continue running despite uncaught exception'); }); // Handle unhandled promise rejections process.on('unhandledRejection', (reason, promise) => { console.error('[MAIN PROCESS] Unhandled Promise Rejection at:', promise, 'reason:', reason); console.error('[MAIN PROCESS] Rejection reason stack:', reason.stack); }); // Handle unhandled rejections process.on('unhandledRejection', async (reason, promise) => { // Avoid logging the logging system's own errors to prevent infinite loops if (reason && reason.message && reason.message.includes('object could not be cloned')) { console.warn('IPC cloning error detected - this is expected during logger initialization'); return; } await logger.error('Unhandled Rejection', { reason: reason.toString(), promise: promise.toString() }); console.error('Unhandled Rejection at:', promise, 'reason:', reason); }); // Security: Prevent new window creation app.on('web-contents-created', (event, contents) => { contents.on('new-window', (event, navigationUrl) => { event.preventDefault(); shell.openExternal(navigationUrl); }); });