diff --git a/lib/upload-manager.js b/lib/upload-manager.js index f08210f..8562aaf 100644 --- a/lib/upload-manager.js +++ b/lib/upload-manager.js @@ -67,6 +67,10 @@ class UploadManager extends EventEmitter { return this._accountOverrides.get(hoster) || null; } + getActiveJobCount() { + return this.activeJobs.size; + } + clearFailedAccount(hoster, accountId) { return this._failedAccounts.delete(`${hoster}:${accountId}`); } diff --git a/main.js b/main.js index 3c6fb5d..de00f21 100644 --- a/main.js +++ b/main.js @@ -227,11 +227,51 @@ function rotLog(msg, ts) { } catch {} } -// Catch unhandled rejections from fire-and-forget async calls +function _writeCrashLog(prefix, err, extra) { + try { + const ts = new Date().toISOString(); + const line = `[${ts}] ${prefix} ${err && err.stack ? err.stack : (err && err.message) || String(err)}${extra ? ' :: ' + JSON.stringify(extra) : ''}\n`; + try { + const target = getDebugLogPath(); + fs.appendFileSync(target, line, 'utf-8'); + } catch {} + try { + const crashDir = path.dirname(getDebugLogPath()); + fs.appendFileSync(path.join(crashDir, 'crash.log'), line, 'utf-8'); + } catch {} + } catch {} +} + process.on('unhandledRejection', (reason) => { debugLog(`UNHANDLED REJECTION: ${reason && reason.stack ? reason.stack : reason}`); + _writeCrashLog('UNHANDLED REJECTION', reason); }); +process.on('uncaughtException', (err, origin) => { + _writeCrashLog('UNCAUGHT EXCEPTION (' + origin + ')', err); + debugLog(`UNCAUGHT EXCEPTION (${origin}): ${err && err.stack ? err.stack : err}`); +}); + +process.on('exit', (code) => { + try { _writeCrashLog('PROCESS EXIT', new Error('code=' + code)); } catch {} +}); + +process.on('warning', (warning) => { + debugLog(`PROCESS WARNING: ${warning.name} ${warning.message}`); +}); + +for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK']) { + try { + process.on(sig, () => { + _writeCrashLog('SIGNAL ' + sig, new Error('process received ' + sig)); + try { + if (_debugLogBuffer.length) fs.appendFileSync(getDebugLogPath(), _debugLogBuffer.join(''), 'utf-8'); + } catch {} + process.exit(0); + }); + } catch {} +} + function withTimeout(promise, timeoutMs, label) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { @@ -947,6 +987,51 @@ function createWindow() { }); mainWindow.webContents.setBackgroundThrottling(false); + + mainWindow.webContents.on('render-process-gone', (_event, details) => { + _writeCrashLog('RENDER PROCESS GONE', new Error(details.reason || 'unknown'), details); + debugLog(`RENDER PROCESS GONE: reason=${details.reason} exitCode=${details.exitCode}`); + if (mainWindow && !mainWindow.isDestroyed()) { + try { + const choice = dialog.showMessageBoxSync(mainWindow, { + type: 'error', + title: 'Renderer abgestürzt', + message: `Der Renderer-Prozess ist abgestürzt (${details.reason}).`, + detail: 'Bitte Diagnose-Paket exportieren und einsenden. Klick "Neu laden" um die UI wiederherzustellen — laufende Uploads im Main-Process bleiben aktiv.', + buttons: ['Neu laden', 'Beenden'], + defaultId: 0, + cancelId: 1 + }); + if (choice === 0) { + mainWindow.webContents.reload(); + } else { + app.exit(1); + } + } catch { + try { mainWindow.webContents.reload(); } catch {} + } + } + }); + + mainWindow.webContents.on('unresponsive', () => { + _writeCrashLog('RENDERER UNRESPONSIVE', new Error('webContents unresponsive')); + debugLog('RENDERER UNRESPONSIVE'); + }); + + mainWindow.webContents.on('responsive', () => { + debugLog('RENDERER RESPONSIVE AGAIN'); + }); + + mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => { + _writeCrashLog('DID-FAIL-LOAD', new Error(errorDescription), { errorCode, validatedURL }); + debugLog(`DID-FAIL-LOAD: ${errorCode} ${errorDescription} url=${validatedURL}`); + }); + + app.on('child-process-gone', (_event, details) => { + _writeCrashLog('CHILD PROCESS GONE', new Error(details.reason || 'unknown'), details); + debugLog(`CHILD PROCESS GONE: type=${details.type} reason=${details.reason} exitCode=${details.exitCode}`); + }); + mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html')); } @@ -1049,6 +1134,9 @@ app.whenReady().then(() => { }); app.on('window-all-closed', () => { + const activeJobs = uploadManager && typeof uploadManager.getActiveJobCount === 'function' ? uploadManager.getActiveJobCount() : 0; + debugLog(`window-all-closed: activeJobs=${activeJobs}, uploadManager=${!!uploadManager}`); + _writeCrashLog('WINDOW-ALL-CLOSED', new Error('all windows closed'), { activeJobs, uploadManager: !!uploadManager }); app.quit(); }); diff --git a/renderer/app.js b/renderer/app.js index 4ba315a..52e0d20 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -77,6 +77,19 @@ let _sessionErrorCount = 0; // Huge with thousands of rows × thousands of incoming results. const _sessionFileKeys = new Set(); +window.addEventListener('error', (e) => { + try { + const msg = `RENDERER ERROR: ${e.message} at ${e.filename}:${e.lineno}:${e.colno}${e.error && e.error.stack ? '\n' + e.error.stack : ''}`; + if (window.api && window.api.debugLog) window.api.debugLog(msg); + } catch {} +}); +window.addEventListener('unhandledrejection', (e) => { + try { + const reason = e.reason && e.reason.stack ? e.reason.stack : (e.reason && e.reason.message) || String(e.reason); + if (window.api && window.api.debugLog) window.api.debugLog(`RENDERER UNHANDLED REJECTION: ${reason}`); + } catch {} +}); + // --- Init --- async function init() { config = await window.api.getConfig();