452 lines
17 KiB
JavaScript
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);
|
|
});
|
|
});
|