Compare commits

...

6 Commits

Author SHA1 Message Date
2de96960d9 fixed update issue 2026-09-13 19:32:21 -07:00
8fd8be0d05 v1.1.36 2026-09-13 18:59:54 -07:00
e3283fff45 updated sdk 2026-09-13 18:51:05 -07:00
64ed5981be v1.1.35 2026-09-07 11:26:23 -07:00
ba8266bd86 fixed files not downloading properly 2026-09-07 11:21:39 -07:00
bb39db0272 v1.1.34 2026-09-06 19:37:27 -07:00
10 changed files with 149 additions and 35 deletions

4
.gitignore vendored
View File

@@ -35,4 +35,6 @@ yarn-error.log*
*.tmp *.tmp
*.temp *.temp
download-temp.zip download-temp.zip
.env

View File

@@ -172,7 +172,6 @@ function validatePayload(zipPath) {
zip.extractAllTo(TEMP_EXTRACT_DIR, true); zip.extractAllTo(TEMP_EXTRACT_DIR, true);
const requiredFiles = [ const requiredFiles = [
'player/server.js',
'frontend/index.html' 'frontend/index.html'
]; ];

83
main.js
View File

@@ -34,6 +34,7 @@ debugLog("--- Node.js Process Started ---");
let serviceManager; let serviceManager;
let expressApp; let expressApp;
let expressServer; let expressServer;
let updateDecisionHandler = null;
debugLog(`AppData Dir: ${APPDATA_DIR}`); debugLog(`AppData Dir: ${APPDATA_DIR}`);
@@ -84,12 +85,25 @@ cmdApp.use((req, res, next) => {
if (req.method === 'OPTIONS') return res.sendStatus(200); if (req.method === 'OPTIONS') return res.sendStatus(200);
next(); next();
}); });
cmdApp.all('/update/:choice', (req, res) => {
const c = String(req.params.choice).toLowerCase();
if (updateDecisionHandler) {
if (c === 'yes' || c === 'true' || c === 'install') updateDecisionHandler(true);
if (c === 'no' || c === 'false' || c === 'skip') updateDecisionHandler(false);
}
res.sendStatus(200);
});
cmdApp.get('/__tauri_cmd/:cmd', (req, res) => { cmdApp.get('/__tauri_cmd/:cmd', (req, res) => {
const cmd = req.params.cmd;
if (updateDecisionHandler) {
if (cmd === 'update_yes' || cmd === 'update_install') updateDecisionHandler(true);
if (cmd === 'update_no' || cmd === 'update_skip') updateDecisionHandler(false);
}
debugLog(`[CMD-SERVER:4892] Command received: ${req.params.cmd} (window: ${req.query.window || 'main'})`); debugLog(`[CMD-SERVER:4892] Command received: ${req.params.cmd} (window: ${req.query.window || 'main'})`);
sendToTauri('tauri-cmd', { cmd: req.params.cmd, url: req.query.url, window: req.query.window }); sendToTauri('tauri-cmd', { cmd: req.params.cmd, url: req.query.url, window: req.query.window });
res.sendStatus(200); res.sendStatus(200);
}); });
cmdApp.listen(4892, '127.0.0.1', () => debugLog("[CMD-SERVER] Dedicated command server listening on 127.0.0.1:4892")); cmdApp.listen(4892, () => debugLog("[CMD-SERVER] Dedicated command server listening on port 4892"));
process.stdin.resume(); process.stdin.resume();
process.stdin.on('end', () => { process.stdin.on('end', () => {
@@ -216,18 +230,20 @@ async function start() {
debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}, Local SHA: ${localSha}`); debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}, Local SHA: ${localSha}`);
function findLocalZip() { function findLocalZip() {
const searchRoots = [__dirname, process.cwd()]; const searchRoots = [__dirname, process.cwd(), path.join(__dirname, '../../..')];
for (const root of searchRoots) { for (const root of searchRoots) {
let cur = root; let cur = root;
for (let i = 0; i < 6; i++) { for (let i = 0; i < 6; i++) {
const zp = path.join(cur, 'update-payload.zip'); try {
if (fs.existsSync(zp)) { const zp = path.join(cur, 'update-payload.zip');
const mp = path.join(cur, 'manifest.json'); if (fs.existsSync(zp)) {
return { zipPath: zp, manifestPath: fs.existsSync(mp) ? mp : null }; 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; const parent = path.dirname(cur);
cur = parent; if (parent === cur) break;
cur = parent;
} catch (e) { }
} }
} }
return null; return null;
@@ -309,8 +325,21 @@ async function start() {
debugLog("Update available. Prompting user..."); debugLog("Update available. Prompting user...");
sendToTauri('status', { message: `New update available (${release.version}). Awaiting confirmation...` }); 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(); const tempApp = express();
let userDecision = null; let userDecision = null;
updateDecisionHandler = (val) => { userDecision = val; };
tempApp.use((req, res, next) => { tempApp.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
@@ -321,20 +350,44 @@ async function start() {
tempApp.get('/update/info', (req, res) => { tempApp.get('/update/info', (req, res) => {
res.json({ available: true, current: localVersion, latest: release.version }); res.json({ available: true, current: localVersion, latest: release.version });
}); });
tempApp.post('/update/yes', (req, res) => { userDecision = true; res.sendStatus(200); }); const handleYes = (req, res) => { userDecision = true; res.sendStatus(200); };
tempApp.post('/update/no', (req, res) => { userDecision = false; res.sendStatus(200); }); const handleNo = (req, res) => { userDecision = false; res.sendStatus(200); };
tempApp.all('/update/yes', handleYes);
tempApp.all('/update/install', handleYes);
tempApp.all('/update/no', handleNo);
tempApp.all('/update/skip', handleNo);
tempApp.all('/update/:choice', (req, res) => {
const c = String(req.params.choice || '').toLowerCase();
if (c === 'yes' || c === 'install' || c === 'true') userDecision = true;
if (c === 'no' || c === 'skip' || c === 'false') userDecision = false;
res.sendStatus(200);
});
const tempServer = await new Promise((resolve) => { const tempServer = await new Promise((resolve) => {
const s = tempApp.listen(4891, '127.0.0.1', () => resolve(s)); const s = tempApp.listen(4891, () => resolve(s));
s.on('error', (err) => {
debugLog(`Update server bind error: ${err.message}`);
resolve(null);
});
}); });
sendToTauri('update-available', { current: localVersion, latest: release.version }); sendToTauri('update-available', { current: localVersion, latest: release.version });
let waitLoops = 0;
while (userDecision === null) { while (userDecision === null) {
if (!tempServer && waitLoops > 400) {
userDecision = false;
break;
}
waitLoops++;
await new Promise(r => setTimeout(r, 25)); await new Promise(r => setTimeout(r, 25));
} }
await new Promise((resolve) => tempServer.close(resolve)); updateDecisionHandler = null;
if (tempServer) {
tempServer.close();
}
if (userDecision) { if (userDecision) {
debugLog("User accepted update."); debugLog("User accepted update.");
@@ -374,7 +427,7 @@ async function start() {
sendToTauri('status', { message: 'Validating critical components...' }); sendToTauri('status', { message: 'Validating critical components...' });
if (!patcher.verifyExtraction()) { 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...' }); sendToTauri('status', { message: 'Loading encrypted credentials...' });

View File

@@ -1,6 +1,6 @@
{ {
"name": "vyla-home", "name": "vyla-home",
"version": "1.1.33", "version": "1.1.36",
"description": "Vyla Home Desktop App Launcher", "description": "Vyla Home Desktop App Launcher",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
@@ -15,7 +15,7 @@
"@tauri-apps/plugin-opener": "^2.0.0", "@tauri-apps/plugin-opener": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0", "@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.0", "@tauri-apps/plugin-shell": "^2.0.0",
"@vyla-entertainment/sdk": "^1.11.0", "@vyla-entertainment/sdk": "^1.12.0",
"adm-zip": "^0.6.0", "adm-zip": "^0.6.0",
"express": "^4.19.2" "express": "^4.19.2"
}, },

View File

@@ -84,8 +84,25 @@ class Patcher {
} }
extractZipPayload(zipPath) { extractZipPayload(zipPath) {
const AdmZip = require('adm-zip');
const zip = new AdmZip(zipPath); 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); 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) { verifyChecksum(zipPath, expectedSha256) {
@@ -99,8 +116,8 @@ class Patcher {
verifyExtraction() { verifyExtraction() {
const requiredFiles = [ const requiredFiles = [
'player/server.js', 'frontend/index.html',
'frontend/index.html' 'player/server.js'
]; ];
for (const file of requiredFiles) { for (const file of requiredFiles) {
@@ -112,12 +129,16 @@ class Patcher {
if (stat.size === 0) { if (stat.size === 0) {
return false; return false;
} }
const sampleSize = Math.min(stat.size, 4096); try {
const fd = fs.openSync(fullPath, 'r'); const sampleSize = Math.min(stat.size, 4096);
const buffer = Buffer.alloc(sampleSize); const fd = fs.openSync(fullPath, 'r');
fs.readSync(fd, buffer, 0, sampleSize, 0); const buffer = Buffer.alloc(sampleSize);
fs.closeSync(fd); fs.readSync(fd, buffer, 0, sampleSize, 0);
if (buffer.every(byte => byte === 0)) { fs.closeSync(fd);
if (buffer.every(byte => byte === 0)) {
return false;
}
} catch (e) {
return false; return false;
} }
} }

2
src-tauri/Cargo.lock generated
View File

@@ -4238,7 +4238,7 @@ dependencies = [
[[package]] [[package]]
name = "vyla-home" name = "vyla-home"
version = "1.1.33" version = "1.1.36"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"objc2 0.5.2", "objc2 0.5.2",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "vyla-home" name = "vyla-home"
version = "1.1.33" version = "1.1.36"
description = "Vyla Home Desktop App Launcher" description = "Vyla Home Desktop App Launcher"
edition = "2021" edition = "2021"

View File

@@ -195,7 +195,34 @@ fn resolve_script_and_root(app: &tauri::App) -> (String, String) {
(clean_path(&candidate), clean_path(&dir)) (clean_path(&candidate), clean_path(&dir))
} }
const LINK_HANDLER_SCRIPT: &str = r##" const LINK_HANDLER_SCRIPT: &str =
r##"
window.vylaHome = {
sendUpdateChoice: function(choice) {
var action = choice ? 'yes' : 'no';
[4891, 4892].forEach(function(port) {
['127.0.0.1', 'localhost'].forEach(function(host) {
fetch('http://' + host + ':' + port + '/update/' + action, { method: 'POST' }).catch(function() {});
fetch('http://' + host + ':' + port + '/update/' + action, { method: 'GET' }).catch(function() {});
fetch('http://' + host + ':' + port + '/__tauri_cmd/update_' + action, { method: 'GET' }).catch(function() {});
});
});
}
};
document.addEventListener('click', function(e) {
var el = e.target.closest('button, a, .btn');
if (!el) return;
var text = (el.innerText || el.textContent || '').toLowerCase();
var id = (el.id || '').toLowerCase();
if (id.includes('install') || id.includes('update-yes') || text.includes('install update')) {
window.vylaHome.sendUpdateChoice(true);
} else if (id.includes('skip') || id.includes('update-no') || text.includes('skip')) {
window.vylaHome.sendUpdateChoice(false);
}
}, true);
function doInvoke(cmd, args) { function doInvoke(cmd, args) {
if (window.__TAURI__ && window.__TAURI__.core) { if (window.__TAURI__ && window.__TAURI__.core) {
return window.__TAURI__.core.invoke(cmd, args || {}); return window.__TAURI__.core.invoke(cmd, args || {});
@@ -831,7 +858,12 @@ fn main() {
let msg = match serde_json::from_str::<BackendMessage>(&line) { let msg = match serde_json::from_str::<BackendMessage>(&line) {
Ok(m) => m, 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)); debug_log(&format!("[MAIN-DEBUG] Received event: {}", msg.event));

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Vyla Home", "productName": "Vyla Home",
"version": "1.1.33", "version": "1.1.36",
"identifier": "com.vyla.home", "identifier": "com.vyla.home",
"build": { "build": {
"frontendDist": "../ui" "frontendDist": "../ui"

View File

@@ -136,7 +136,14 @@ class UpdateClient {
}); });
file.on('finish', () => { 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) => { file.on('error', (err) => {
@@ -150,10 +157,10 @@ class UpdateClient {
reject(err); reject(err);
}); });
req.setTimeout(120000, () => { req.setTimeout(900000, () => {
req.destroy(); req.destroy();
fs.unlink(destPath, () => { }); fs.unlink(destPath, () => { });
reject(new Error('Download timeout')); reject(new Error('Download timeout (15 minutes reached)'));
}); });
}; };