diff --git a/.gitignore b/.gitignore index 1df40e3..fc19798 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ yarn-error.log* *.tmp *.temp -download-temp.zip \ No newline at end of file +download-temp.zip + +.env \ No newline at end of file diff --git a/build-pipeline.js b/build-pipeline.js index 57e518c..f93e710 100644 --- a/build-pipeline.js +++ b/build-pipeline.js @@ -172,7 +172,6 @@ function validatePayload(zipPath) { zip.extractAllTo(TEMP_EXTRACT_DIR, true); const requiredFiles = [ - 'player/server.js', 'frontend/index.html' ]; diff --git a/main.js b/main.js index 375f16e..0a59852 100644 --- a/main.js +++ b/main.js @@ -216,18 +216,20 @@ async function start() { debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}, Local SHA: ${localSha}`); function findLocalZip() { - const searchRoots = [__dirname, process.cwd()]; + const searchRoots = [__dirname, process.cwd(), path.join(__dirname, '../../..')]; for (const root of searchRoots) { let cur = root; for (let i = 0; i < 6; i++) { - const zp = path.join(cur, 'update-payload.zip'); - if (fs.existsSync(zp)) { - const mp = path.join(cur, 'manifest.json'); - return { zipPath: zp, manifestPath: fs.existsSync(mp) ? mp : null }; - } - const parent = path.dirname(cur); - if (parent === cur) break; - cur = parent; + try { + const zp = path.join(cur, 'update-payload.zip'); + if (fs.existsSync(zp)) { + const mp = path.join(cur, 'manifest.json'); + return { zipPath: zp, manifestPath: fs.existsSync(mp) ? mp : null }; + } + const parent = path.dirname(cur); + if (parent === cur) break; + cur = parent; + } catch (e) { } } } return null; @@ -309,12 +311,24 @@ async function start() { debugLog("Update available. Prompting user..."); sendToTauri('status', { message: `New update available (${release.version}). Awaiting confirmation...` }); + try { + if (process.platform === 'win32') { + require('child_process').spawnSync('powershell.exe', [ + '-NoProfile', '-NonInteractive', '-Command', + `Get-NetTCPConnection -LocalPort 4891 -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }` + ], { stdio: 'ignore', windowsHide: true }); + } else { + require('child_process').spawnSync('fuser', ['-k', '-9', `4891/tcp`], { stdio: 'ignore' }); + } + } catch (e) { } + const tempApp = express(); let userDecision = null; tempApp.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); res.header('Access-Control-Allow-Headers', '*'); + res.header('Connection', 'close'); if (req.method === 'OPTIONS') return res.sendStatus(200); next(); }); @@ -326,15 +340,27 @@ async function start() { const tempServer = await new Promise((resolve) => { const s = tempApp.listen(4891, '127.0.0.1', () => resolve(s)); + s.on('error', (err) => { + debugLog(`Update server bind error: ${err.message}`); + resolve(null); + }); }); sendToTauri('update-available', { current: localVersion, latest: release.version }); + let waitLoops = 0; while (userDecision === null) { + if (!tempServer && waitLoops > 400) { + userDecision = false; + break; + } + waitLoops++; await new Promise(r => setTimeout(r, 25)); } - await new Promise((resolve) => tempServer.close(resolve)); + if (tempServer) { + tempServer.close(); + } if (userDecision) { debugLog("User accepted update."); @@ -374,7 +400,7 @@ async function start() { sendToTauri('status', { message: 'Validating critical components...' }); if (!patcher.verifyExtraction()) { - throw new Error('No application files found. Ensure you run "npm run build:payload" to generate the local development payload.'); + throw new Error('No application files found. Ensure you ran "npm run build:payload" and the extraction succeeded.'); } sendToTauri('status', { message: 'Loading encrypted credentials...' }); diff --git a/patcher.js b/patcher.js index 96a7d40..e95f825 100644 --- a/patcher.js +++ b/patcher.js @@ -84,8 +84,25 @@ class Patcher { } extractZipPayload(zipPath) { + const AdmZip = require('adm-zip'); const zip = new AdmZip(zipPath); + const entries = zip.getEntries(); + + if (!entries || entries.length === 0) { + throw new Error('Downloaded archive is empty, invalid, or truncated.'); + } + zip.extractAllTo(this.appDataPath, true); + + const testPath = path.join(this.appDataPath, 'frontend', 'index.html'); + if (!fs.existsSync(testPath)) { + const { execSync } = require('child_process'); + if (process.platform === 'win32') { + execSync(`powershell.exe -NoProfile -NonInteractive -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${this.appDataPath}' -Force"`, { windowsHide: true }); + } else { + execSync(`unzip -o "${zipPath}" -d "${this.appDataPath}"`); + } + } } verifyChecksum(zipPath, expectedSha256) { @@ -99,8 +116,8 @@ class Patcher { verifyExtraction() { const requiredFiles = [ - 'player/server.js', - 'frontend/index.html' + 'frontend/index.html', + 'player/server.js' ]; for (const file of requiredFiles) { @@ -112,12 +129,16 @@ class Patcher { if (stat.size === 0) { return false; } - const sampleSize = Math.min(stat.size, 4096); - const fd = fs.openSync(fullPath, 'r'); - const buffer = Buffer.alloc(sampleSize); - fs.readSync(fd, buffer, 0, sampleSize, 0); - fs.closeSync(fd); - if (buffer.every(byte => byte === 0)) { + try { + const sampleSize = Math.min(stat.size, 4096); + const fd = fs.openSync(fullPath, 'r'); + const buffer = Buffer.alloc(sampleSize); + fs.readSync(fd, buffer, 0, sampleSize, 0); + fs.closeSync(fd); + if (buffer.every(byte => byte === 0)) { + return false; + } + } catch (e) { return false; } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index d83ce39..7988659 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -831,7 +831,12 @@ fn main() { let msg = match serde_json::from_str::(&line) { Ok(m) => m, - Err(_) => continue, + Err(_) => { + if !line.trim().is_empty() { + debug_log(&format!("[NODE RAW] {}", line)); + } + continue; + } }; debug_log(&format!("[MAIN-DEBUG] Received event: {}", msg.event)); diff --git a/update-client.js b/update-client.js index 80eee90..3cf0253 100644 --- a/update-client.js +++ b/update-client.js @@ -136,7 +136,14 @@ class UpdateClient { }); file.on('finish', () => { - file.close(resolve); + file.close(() => { + if (totalSize && downloadedSize < totalSize) { + fs.unlink(destPath, () => { }); + reject(new Error(`Download truncated: Received ${downloadedSize} of ${totalSize} bytes. Connection dropped prematurely.`)); + } else { + resolve(); + } + }); }); file.on('error', (err) => { @@ -150,10 +157,10 @@ class UpdateClient { reject(err); }); - req.setTimeout(120000, () => { + req.setTimeout(900000, () => { req.destroy(); fs.unlink(destPath, () => { }); - reject(new Error('Download timeout')); + reject(new Error('Download timeout (15 minutes reached)')); }); };