This repository has been archived on 2026-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
Galaxy-Strike-Online/Client/electron-main.js
2026-01-24 16:47:19 -04:00

452 lines
17 KiB
JavaScript

const { app, BrowserWindow, Menu, shell, ipcMain } = 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
// 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);
});
});