From 320ca547585c67d0773dba262612db5026378f52 Mon Sep 17 00:00:00 2001 From: sanasol Date: Tue, 24 Feb 2026 13:50:23 +0100 Subject: [PATCH] Add Send Logs button for one-click log submission to support Collects launcher logs, game client logs, and config snapshot into a ZIP file and uploads to auth server. Shows submission ID for sharing with support. Includes i18n for all 11 locales. --- GUI/index.html | 3 + GUI/js/logs.js | 109 ++++++++++++++++ GUI/locales/ar-SA.json | 6 + GUI/locales/de-DE.json | 6 + GUI/locales/en.json | 6 + GUI/locales/es-ES.json | 6 + GUI/locales/fr-FR.json | 6 + GUI/locales/id-ID.json | 6 + GUI/locales/pl-PL.json | 6 + GUI/locales/pt-BR.json | 6 + GUI/locales/ru-RU.json | 6 + GUI/locales/sv-SE.json | 6 + GUI/locales/tr-TR.json | 6 + GUI/style.css | 16 +++ backend/utils/logCollector.js | 238 ++++++++++++++++++++++++++++++++++ main.js | 77 +++++++++++ preload.js | 1 + 17 files changed, 510 insertions(+) create mode 100644 backend/utils/logCollector.js diff --git a/GUI/index.html b/GUI/index.html index 7ed1028..fe51bbc 100644 --- a/GUI/index.html +++ b/GUI/index.html @@ -575,6 +575,9 @@ Open Folder +
diff --git a/GUI/js/logs.js b/GUI/js/logs.js index ecc21a0..795da6f 100644 --- a/GUI/js/logs.js +++ b/GUI/js/logs.js @@ -66,6 +66,113 @@ async function openLogsFolder() { await window.electronAPI.openLogsFolder(); } +async function sendLogs() { + const btn = document.getElementById('sendLogsBtn'); + if (!btn || btn.disabled) return; + + // Get i18n strings with fallbacks + const i18n = window.i18n || {}; + const sendingText = (i18n.settings && i18n.settings.logsSending) || 'Sending...'; + const sentText = (i18n.settings && i18n.settings.logsSent) || 'Sent!'; + const failedText = (i18n.settings && i18n.settings.logsSendFailed) || 'Failed'; + const sendText = (i18n.settings && i18n.settings.logsSend) || 'Send Logs'; + + const originalHTML = btn.innerHTML; + btn.disabled = true; + btn.innerHTML = ` ${sendingText}`; + + try { + const result = await window.electronAPI.sendLogs(); + + if (result.success) { + btn.innerHTML = ` ${sentText}`; + showLogSubmissionResult(result.id); + } else { + btn.innerHTML = ` ${failedText}`; + console.error('Send logs failed:', result.error); + + // Show error notification if available + if (window.LauncherUI && window.LauncherUI.showNotification) { + window.LauncherUI.showNotification(result.error || 'Failed to send logs', 'error'); + } + } + } catch (err) { + console.error('Send logs error:', err); + btn.innerHTML = ` ${failedText}`; + } + + setTimeout(() => { + btn.disabled = false; + btn.innerHTML = originalHTML; + }, 3000); +} + +function showLogSubmissionResult(id) { + // Remove existing popup if any + const existing = document.getElementById('logSubmissionPopup'); + if (existing) existing.remove(); + + const i18n = window.i18n || {}; + const idLabel = (i18n.settings && i18n.settings.logsSubmissionId) || 'Submission ID'; + const copyText = (i18n.common && i18n.common.copy) || 'Copy'; + const closeText = (i18n.common && i18n.common.close) || 'Close'; + const shareText = (i18n.settings && i18n.settings.logsShareId) || 'Share this ID with support when reporting issues'; + + const popup = document.createElement('div'); + popup.id = 'logSubmissionPopup'; + popup.style.cssText = ` + position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); + background: rgba(20, 20, 35, 0.98); border: 1px solid rgba(0, 212, 255, 0.3); + border-radius: 12px; padding: 24px 32px; z-index: 10000; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); text-align: center; + min-width: 320px; backdrop-filter: blur(10px); + `; + + popup.innerHTML = ` +
+ +
+
${idLabel}
+
${id}
+
${shareText}
+
+ + +
+ `; + + document.body.appendChild(popup); + + // Auto-close after 30s + setTimeout(() => { + if (document.getElementById('logSubmissionPopup')) { + popup.remove(); + } + }, 30000); +} + +async function copyLogSubmissionId(id) { + try { + await navigator.clipboard.writeText(id); + const btn = event.target.closest('button'); + if (btn) { + const orig = btn.innerHTML; + btn.innerHTML = ' Copied!'; + setTimeout(() => { btn.innerHTML = orig; }, 1500); + } + } catch (err) { + console.error('Failed to copy submission ID:', err); + } +} + function openLogs() { // Navigation is handled by sidebar logic, but we can trigger a refresh window.LauncherUI.showPage('logs-page'); @@ -77,6 +184,8 @@ function openLogs() { window.refreshLogs = refreshLogs; window.copyLogs = copyLogs; window.openLogsFolder = openLogsFolder; +window.sendLogs = sendLogs; +window.copyLogSubmissionId = copyLogSubmissionId; window.openLogs = openLogs; // Auto-load logs when the page becomes active diff --git a/GUI/locales/ar-SA.json b/GUI/locales/ar-SA.json index cca6824..256bd02 100644 --- a/GUI/locales/ar-SA.json +++ b/GUI/locales/ar-SA.json @@ -127,6 +127,12 @@ "logsCopy": "نسخ", "logsRefresh": "تحديث", "logsFolder": "فتح المجلد", + "logsSend": "إرسال السجلات", + "logsSending": "جارٍ الإرسال...", + "logsSent": "تم الإرسال!", + "logsSendFailed": "فشل", + "logsSubmissionId": "معرف الإرسال", + "logsShareId": "شارك هذا المعرف مع الدعم عند الإبلاغ عن المشاكل", "logsLoading": "جاري تحميل السجلات...", "closeLauncher": "سلوك المشغل", "closeOnStart": "إغلاق المشغل عند بدء اللعبة", diff --git a/GUI/locales/de-DE.json b/GUI/locales/de-DE.json index 9adbbbe..4fe4eb5 100644 --- a/GUI/locales/de-DE.json +++ b/GUI/locales/de-DE.json @@ -127,6 +127,12 @@ "logsCopy": "Kopieren", "logsRefresh": "Aktualisieren", "logsFolder": "Ordner öffnen", + "logsSend": "Logs senden", + "logsSending": "Senden...", + "logsSent": "Gesendet!", + "logsSendFailed": "Fehlgeschlagen", + "logsSubmissionId": "Einreichungs-ID", + "logsShareId": "Teilen Sie diese ID dem Support mit, wenn Sie Probleme melden", "logsLoading": "Protokolle werden geladen...", "closeLauncher": "Launcher-Verhalten", "closeOnStart": "Launcher beim Spielstart schließen", diff --git a/GUI/locales/en.json b/GUI/locales/en.json index 722438a..a58ccff 100644 --- a/GUI/locales/en.json +++ b/GUI/locales/en.json @@ -127,6 +127,12 @@ "logsCopy": "Copy", "logsRefresh": "Refresh", "logsFolder": "Open Folder", + "logsSend": "Send Logs", + "logsSending": "Sending...", + "logsSent": "Sent!", + "logsSendFailed": "Failed", + "logsSubmissionId": "Submission ID", + "logsShareId": "Share this ID with support when reporting issues", "logsLoading": "Loading logs...", "closeLauncher": "Launcher Behavior", "closeOnStart": "Close Launcher on game start", diff --git a/GUI/locales/es-ES.json b/GUI/locales/es-ES.json index c847a4e..a12825f 100644 --- a/GUI/locales/es-ES.json +++ b/GUI/locales/es-ES.json @@ -127,6 +127,12 @@ "logsCopy": "Copiar", "logsRefresh": "Actualizar", "logsFolder": "Abrir Carpeta", + "logsSend": "Enviar logs", + "logsSending": "Enviando...", + "logsSent": "Enviado!", + "logsSendFailed": "Fallido", + "logsSubmissionId": "ID de envío", + "logsShareId": "Comparte este ID con soporte al reportar problemas", "logsLoading": "Cargando registros...", "closeLauncher": "Comportamiento del Launcher", "closeOnStart": "Cerrar Launcher al iniciar el juego", diff --git a/GUI/locales/fr-FR.json b/GUI/locales/fr-FR.json index 657aea9..20abc43 100644 --- a/GUI/locales/fr-FR.json +++ b/GUI/locales/fr-FR.json @@ -127,6 +127,12 @@ "logsCopy": "Copier", "logsRefresh": "Actualiser", "logsFolder": "Ouvrir le Dossier", + "logsSend": "Envoyer les logs", + "logsSending": "Envoi...", + "logsSent": "Envoyé !", + "logsSendFailed": "Échoué", + "logsSubmissionId": "ID de soumission", + "logsShareId": "Partagez cet ID avec le support pour signaler des problèmes", "logsLoading": "Chargement des journaux...", "closeLauncher": "Comportement du Launcher", "closeOnStart": "Fermer le Launcher au démarrage du jeu", diff --git a/GUI/locales/id-ID.json b/GUI/locales/id-ID.json index 9ba46ec..7b661b4 100644 --- a/GUI/locales/id-ID.json +++ b/GUI/locales/id-ID.json @@ -127,6 +127,12 @@ "logsCopy": "Salin", "logsRefresh": "Segarkan", "logsFolder": "Buka Folder", + "logsSend": "Kirim Log", + "logsSending": "Mengirim...", + "logsSent": "Terkirim!", + "logsSendFailed": "Gagal", + "logsSubmissionId": "ID Pengiriman", + "logsShareId": "Bagikan ID ini ke dukungan saat melaporkan masalah", "logsLoading": "Memuat log...", "closeLauncher": "Perilaku Launcher", "closeOnStart": "Tutup launcher saat game dimulai", diff --git a/GUI/locales/pl-PL.json b/GUI/locales/pl-PL.json index beeac96..14bfaef 100644 --- a/GUI/locales/pl-PL.json +++ b/GUI/locales/pl-PL.json @@ -127,6 +127,12 @@ "logsCopy": "Kopiuj", "logsRefresh": "Odśwież", "logsFolder": "Otwórz Folder", + "logsSend": "Wyślij logi", + "logsSending": "Wysyłanie...", + "logsSent": "Wysłano!", + "logsSendFailed": "Błąd", + "logsSubmissionId": "ID zgłoszenia", + "logsShareId": "Udostępnij ten ID wsparciu technicznemu przy zgłaszaniu problemów", "logsLoading": "Ładowanie logów...", "closeLauncher": "Zachowanie Launchera", "closeOnStart": "Zamknij Launcher przy starcie gry", diff --git a/GUI/locales/pt-BR.json b/GUI/locales/pt-BR.json index 6502b32..247bf4e 100644 --- a/GUI/locales/pt-BR.json +++ b/GUI/locales/pt-BR.json @@ -127,6 +127,12 @@ "logsCopy": "Copiar", "logsRefresh": "Atualizar", "logsFolder": "Abrir Pasta", + "logsSend": "Enviar logs", + "logsSending": "Enviando...", + "logsSent": "Enviado!", + "logsSendFailed": "Falhou", + "logsSubmissionId": "ID de envio", + "logsShareId": "Compartilhe este ID com o suporte ao relatar problemas", "logsLoading": "Carregando registros...", "closeLauncher": "Comportamento do Lançador", "closeOnStart": "Fechar Lançador ao iniciar o jogo", diff --git a/GUI/locales/ru-RU.json b/GUI/locales/ru-RU.json index 91d7389..5d2bd61 100644 --- a/GUI/locales/ru-RU.json +++ b/GUI/locales/ru-RU.json @@ -127,6 +127,12 @@ "logsCopy": "Копировать", "logsRefresh": "Обновить", "logsFolder": "Открыть папку", + "logsSend": "Отправить логи", + "logsSending": "Отправка...", + "logsSent": "Отправлено!", + "logsSendFailed": "Ошибка", + "logsSubmissionId": "ID отправки", + "logsShareId": "Поделитесь этим ID с поддержкой при обращении", "logsLoading": "Загрузка логов...", "closeLauncher": "Поведение лаунчера", "closeOnStart": "Закрыть лаунчер при старте игры", diff --git a/GUI/locales/sv-SE.json b/GUI/locales/sv-SE.json index 5588d8e..f7c199e 100644 --- a/GUI/locales/sv-SE.json +++ b/GUI/locales/sv-SE.json @@ -127,6 +127,12 @@ "logsCopy": "Kopiera", "logsRefresh": "Uppdatera", "logsFolder": "Öppna mapp", + "logsSend": "Skicka loggar", + "logsSending": "Skickar...", + "logsSent": "Skickat!", + "logsSendFailed": "Misslyckades", + "logsSubmissionId": "Inlämnings-ID", + "logsShareId": "Dela detta ID med support vid felanmälan", "logsLoading": "Laddar loggar...", "closeLauncher": "Launcher-beteende", "closeOnStart": "Stäng launcher vid spelstart", diff --git a/GUI/locales/tr-TR.json b/GUI/locales/tr-TR.json index b392e2e..f0210cd 100644 --- a/GUI/locales/tr-TR.json +++ b/GUI/locales/tr-TR.json @@ -127,6 +127,12 @@ "logsCopy": "Kopyala", "logsRefresh": "Yenile", "logsFolder": "Klasörü Aç", + "logsSend": "Logları Gönder", + "logsSending": "Gönderiliyor...", + "logsSent": "Gönderildi!", + "logsSendFailed": "Başarısız", + "logsSubmissionId": "Gönderim ID", + "logsShareId": "Sorun bildirirken bu ID'yi destekle paylaşın", "logsLoading": "Loglar yükleniyor...", "closeLauncher": "Başlatıcı Davranışı", "closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat", diff --git a/GUI/style.css b/GUI/style.css index ab36fcd..2bb344e 100644 --- a/GUI/style.css +++ b/GUI/style.css @@ -873,6 +873,22 @@ body { border-color: rgba(255, 255, 255, 0.2); } +.logs-action-btn.logs-send-btn { + background: rgba(0, 212, 255, 0.15); + border-color: rgba(0, 212, 255, 0.3); + color: #00d4ff; +} + +.logs-action-btn.logs-send-btn:hover { + background: rgba(0, 212, 255, 0.25); + border-color: rgba(0, 212, 255, 0.5); +} + +.logs-action-btn.logs-send-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + .logs-terminal { flex: 1; background: #0d1117; diff --git a/backend/utils/logCollector.js b/backend/utils/logCollector.js new file mode 100644 index 0000000..46d454c --- /dev/null +++ b/backend/utils/logCollector.js @@ -0,0 +1,238 @@ +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const zlib = require('zlib'); +const logger = require('../logger'); + +const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB per file + +/** + * Get the HytaleSaves directory (game client logs) + */ +function getHytaleSavesDir() { + const home = os.homedir(); + if (process.platform === 'win32') { + return path.join(home, 'AppData', 'Local', 'HytaleSaves'); + } else if (process.platform === 'darwin') { + return path.join(home, 'Library', 'Application Support', 'HytaleSaves'); + } else { + return path.join(home, '.hytalesaves'); + } +} + +/** + * Read a log file, capping at MAX_FILE_SIZE (keeps tail/most recent lines) + */ +function readLogFile(filePath) { + try { + const stats = fs.statSync(filePath); + if (stats.size <= MAX_FILE_SIZE) { + return fs.readFileSync(filePath, 'utf8'); + } + + // Read only the last MAX_FILE_SIZE bytes + const fd = fs.openSync(filePath, 'r'); + const buffer = Buffer.alloc(MAX_FILE_SIZE); + fs.readSync(fd, buffer, 0, MAX_FILE_SIZE, stats.size - MAX_FILE_SIZE); + fs.closeSync(fd); + + const content = buffer.toString('utf8'); + // Skip first partial line + const firstNewline = content.indexOf('\n'); + const trimmed = firstNewline >= 0 ? content.substring(firstNewline + 1) : content; + return `[... truncated ${stats.size - MAX_FILE_SIZE} bytes ...]\n` + trimmed; + } catch (err) { + return `[Error reading file: ${err.message}]`; + } +} + +/** + * Get files matching a date pattern from a directory + */ +function getFilesForDate(dir, dateStr, pattern) { + if (!fs.existsSync(dir)) return []; + + try { + return fs.readdirSync(dir) + .filter(f => f.includes(dateStr) && (pattern ? pattern.test(f) : true)) + .map(f => ({ + name: f, + path: path.join(dir, f), + mtime: fs.statSync(path.join(dir, f)).mtime + })) + .sort((a, b) => b.mtime - a.mtime); + } catch (err) { + return []; + } +} + +// ============================================================================ +// ZIP BUILDER (pure Node.js, no dependencies) +// ============================================================================ + +/** + * CRC32 lookup table + */ +const crc32Table = new Uint32Array(256); +for (let i = 0; i < 256; i++) { + let c = i; + for (let j = 0; j < 8; j++) { + c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); + } + crc32Table[i] = c; +} + +function crc32(buf) { + let crc = 0xFFFFFFFF; + for (let i = 0; i < buf.length; i++) { + crc = crc32Table[(crc ^ buf[i]) & 0xFF] ^ (crc >>> 8); + } + return (crc ^ 0xFFFFFFFF) >>> 0; +} + +/** + * Create a ZIP file from an array of {name, content} entries + * Uses DEFLATE compression via built-in zlib + */ +function createZipBuffer(files) { + const localHeaders = []; + const centralEntries = []; + let offset = 0; + + for (const file of files) { + const nameBytes = Buffer.from(file.name, 'utf8'); + const contentBytes = Buffer.from(file.content, 'utf8'); + const compressed = zlib.deflateRawSync(contentBytes); + const crcVal = crc32(contentBytes); + + // Local file header (30 bytes + filename) + const local = Buffer.alloc(30); + local.writeUInt32LE(0x04034b50, 0); // signature + local.writeUInt16LE(20, 4); // version needed + local.writeUInt16LE(0, 6); // flags + local.writeUInt16LE(8, 8); // compression: DEFLATE + local.writeUInt16LE(0, 10); // mod time + local.writeUInt16LE(0, 12); // mod date + local.writeUInt32LE(crcVal, 14); // crc32 + local.writeUInt32LE(compressed.length, 18); // compressed size + local.writeUInt32LE(contentBytes.length, 22); // uncompressed size + local.writeUInt16LE(nameBytes.length, 26); // filename length + local.writeUInt16LE(0, 28); // extra field length + + localHeaders.push(local, nameBytes, compressed); + + // Central directory entry (46 bytes + filename) + const central = Buffer.alloc(46); + central.writeUInt32LE(0x02014b50, 0); // signature + central.writeUInt16LE(20, 4); // version made by + central.writeUInt16LE(20, 6); // version needed + central.writeUInt16LE(0, 8); // flags + central.writeUInt16LE(8, 10); // compression + central.writeUInt16LE(0, 12); // mod time + central.writeUInt16LE(0, 14); // mod date + central.writeUInt32LE(crcVal, 16); // crc32 + central.writeUInt32LE(compressed.length, 20); // compressed size + central.writeUInt32LE(contentBytes.length, 24); // uncompressed size + central.writeUInt16LE(nameBytes.length, 28); // filename length + central.writeUInt16LE(0, 30); // extra field length + central.writeUInt16LE(0, 32); // comment length + central.writeUInt16LE(0, 34); // disk number + central.writeUInt16LE(0, 36); // internal attrs + central.writeUInt32LE(0, 38); // external attrs + central.writeUInt32LE(offset, 42); // local header offset + + centralEntries.push(central, nameBytes); + offset += 30 + nameBytes.length + compressed.length; + } + + const centralDirBuf = Buffer.concat(centralEntries); + + // End of central directory (22 bytes) + const eocd = Buffer.alloc(22); + eocd.writeUInt32LE(0x06054b50, 0); // signature + eocd.writeUInt16LE(0, 4); // disk number + eocd.writeUInt16LE(0, 6); // cd disk number + eocd.writeUInt16LE(files.length, 8); // entries on disk + eocd.writeUInt16LE(files.length, 10); // total entries + eocd.writeUInt32LE(centralDirBuf.length, 12); // cd size + eocd.writeUInt32LE(offset, 16); // cd offset + eocd.writeUInt16LE(0, 20); // comment length + + return Buffer.concat([...localHeaders, centralDirBuf, eocd]); +} + +// ============================================================================ + +/** + * Collect all relevant logs for submission + * Returns { files: [{name, content}], meta: {username, platform, version} } + */ +function collectLogs() { + const files = []; + const today = new Date(); + const todayStr = today.toISOString().split('T')[0]; // YYYY-MM-DD + + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + const yesterdayStr = yesterday.toISOString().split('T')[0]; + + // 1. Launcher logs + const launcherLogDir = logger.getLogDirectory(); + if (launcherLogDir && fs.existsSync(launcherLogDir)) { + // Today's launcher logs + const todayLogs = getFilesForDate(launcherLogDir, todayStr, /^launcher-.*\.log$/); + for (const f of todayLogs) { + files.push({ name: `launcher/${f.name}`, content: readLogFile(f.path) }); + } + + // Most recent from yesterday (just one) + const yesterdayLogs = getFilesForDate(launcherLogDir, yesterdayStr, /^launcher-.*\.log$/); + if (yesterdayLogs.length > 0) { + files.push({ name: `launcher/${yesterdayLogs[0].name}`, content: readLogFile(yesterdayLogs[0].path) }); + } + } + + // 2. Game client logs + const savesDir = getHytaleSavesDir(); + const clientLogDir = path.join(savesDir, 'Logs'); + if (fs.existsSync(clientLogDir)) { + const clientLogs = getFilesForDate(clientLogDir, todayStr, /client\.log$/); + for (const f of clientLogs) { + files.push({ name: `client/${f.name}`, content: readLogFile(f.path) }); + } + } + + // 3. Config snapshot + const appDir = logger.getAppDir ? logger.getAppDir() : logger.getInstallPath(); + const configPath = path.join(appDir, 'config.json'); + if (fs.existsSync(configPath)) { + try { + const configContent = fs.readFileSync(configPath, 'utf8'); + files.push({ name: 'config.json', content: configContent }); + } catch (err) { + files.push({ name: 'config.json', content: `[Error reading config: ${err.message}]` }); + } + } + + // Build metadata + const { app } = require('electron'); + let username = 'unknown'; + try { + const configFile = path.join(appDir, 'config.json'); + if (fs.existsSync(configFile)) { + const cfg = JSON.parse(fs.readFileSync(configFile, 'utf8')); + username = cfg.username || cfg.playerName || 'unknown'; + } + } catch (err) {} + + return { + files, + meta: { + username, + platform: `${process.platform}-${process.arch}`, + version: app.getVersion() + } + }; +} + +module.exports = { collectLogs, createZipBuffer }; diff --git a/main.js b/main.js index 94b39bb..a792a29 100644 --- a/main.js +++ b/main.js @@ -1407,6 +1407,83 @@ ipcMain.handle('get-recent-logs', async (event, maxLines = 100) => { +ipcMain.handle('send-logs', async () => { + try { + const https = require('https'); + const http = require('http'); + const { collectLogs, createZipBuffer } = require('./backend/utils/logCollector'); + + const { files, meta } = collectLogs(); + if (files.length === 0) { + return { success: false, error: 'No log files found' }; + } + + // Create ZIP with individual log files + const zipBuffer = createZipBuffer(files); + + // Get auth server URL from core config + const { getAuthServerUrl } = require('./backend/core/config'); + const authUrl = getAuthServerUrl(); + + // Build file names list + const fileNames = files.map(f => f.name).join(','); + + return await new Promise((resolve) => { + const url = new URL(authUrl + '/logs/submit'); + const transport = url.protocol === 'https:' ? https : http; + + const options = { + hostname: url.hostname, + port: url.port || (url.protocol === 'https:' ? 443 : 80), + path: url.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/zip', + 'Content-Length': zipBuffer.length, + 'X-Log-Username': meta.username || 'unknown', + 'X-Log-Platform': meta.platform || 'unknown', + 'X-Log-Version': meta.version || 'unknown', + 'X-Log-File-Count': String(files.length), + 'X-Log-Files': fileNames + }, + timeout: 30000 + }; + + const req = transport.request(options, (res) => { + let body = ''; + res.on('data', (chunk) => body += chunk); + res.on('end', () => { + try { + const data = JSON.parse(body); + if (res.statusCode === 200) { + resolve({ success: true, id: data.id, message: data.message }); + } else { + resolve({ success: false, error: data.error || `Server error ${res.statusCode}` }); + } + } catch (e) { + resolve({ success: false, error: `Invalid response: ${res.statusCode}` }); + } + }); + }); + + req.on('error', (err) => { + resolve({ success: false, error: err.message }); + }); + + req.on('timeout', () => { + req.destroy(); + resolve({ success: false, error: 'Request timed out' }); + }); + + req.write(zipBuffer); + req.end(); + }); + } catch (error) { + console.error('Error sending logs:', error); + return { success: false, error: error.message }; + } +}); + ipcMain.handle('open-logs-folder', async () => { try { const logDir = logger.getLogDirectory(); diff --git a/preload.js b/preload.js index b217e5c..09e5438 100644 --- a/preload.js +++ b/preload.js @@ -94,6 +94,7 @@ contextBridge.exposeInMainWorld('electronAPI', { getLogDirectory: () => ipcRenderer.invoke('get-log-directory'), openLogsFolder: () => ipcRenderer.invoke('open-logs-folder'), getRecentLogs: (maxLines) => ipcRenderer.invoke('get-recent-logs', maxLines), + sendLogs: () => ipcRenderer.invoke('send-logs'), // UUID Management methods getCurrentUuid: () => ipcRenderer.invoke('get-current-uuid'),