potentially fixed auto updater, made sdk also up to date

This commit is contained in:
Devin Bot
2026-10-25 19:27:25 -07:00
parent d9ae75e9cb
commit 1b2c97e74b
5 changed files with 125 additions and 19 deletions

49
main.js
View File

@@ -179,20 +179,37 @@ async function start() {
debugLog('Download complete. Verifying checksum...');
sendToTauri('status', { message: 'Verifying SHA-256 integrity...' });
if (!release.sha256) {
throw new Error('Release has no SHA-256 in manifest.json; refusing unverified install.');
}
if (!patcher.verifyChecksum(tempZipPath, release.sha256)) {
throw new Error('SHA-256 checksum verification failed.');
}
debugLog('Checksum verified. Extracting...');
debugLog('Checksum verified. Extracting to staging...');
sendToTauri('status', { message: 'Unpacking update archive...' });
patcher.purgeOldCode();
patcher.extractZipPayload(tempZipPath);
let lastEmit = 0;
const stagingDir = await patcher.extractToStaging(tempZipPath, (file, done, total) => {
const now = Date.now();
if (done !== total && now - lastEmit < 40) return;
lastEmit = now;
sendToTauri('extract-progress', {
file,
done,
total,
percent: Math.round((done / total) * 100)
});
});
sendToTauri('status', { message: 'Verifying file integrity...' });
if (!patcher.verifyExtraction()) {
if (!patcher.verifyExtraction(stagingDir)) {
fs.rmSync(stagingDir, { recursive: true, force: true });
throw new Error('Extraction verification failed: critical startup files missing.');
}
sendToTauri('status', { message: 'Replacing old version...' });
patcher.commitStaging(stagingDir);
debugLog('Extraction complete. Applying patches...');
sendToTauri('status', { message: 'Applying configuration patches...' });
patcher.applyPatches(getReplacementsConfig());
@@ -224,9 +241,9 @@ async function start() {
try {
sendToTauri('status', { message: 'Inspecting local workspace and installed files...' });
let isFirstRun = false;
let localVersion = '0.0.0';
let localSha = 'force-update-sha';
const isFirstRun = !patcher.verifyExtraction();
const localVersion = patcher.getCurrentVersion();
const localSha = patcher.getCurrentSha();
debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}, Local SHA: ${localSha}`);
function findLocalZip() {
@@ -273,11 +290,23 @@ async function start() {
}
const normalizeVer = (v) => (v || '').toString().replace(/^v/, '').trim();
const parseVer = (v) => normalizeVer(v).split(/[.\-+]/).map((n) => parseInt(n, 10) || 0);
const isNewer = (remote, local) => {
const a = parseVer(remote);
const b = parseVer(local);
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const x = a[i] || 0;
const y = b[i] || 0;
if (x !== y) return x > y;
}
return false;
};
if (release) {
const isNewerSha = release.sha256 && localSha && release.sha256 !== localSha;
const isDifferentVer = normalizeVer(release.version) !== normalizeVer(localVersion);
const hasUpdate = (isDifferentVer || isNewerSha) && !localZipPath;
const versionNewer = isNewer(release.version, localVersion);
const sameVerNewBuild = normalizeVer(release.version) === normalizeVer(localVersion) &&
!!release.sha256 && !!localSha && release.sha256 !== localSha;
const hasUpdate = (versionNewer || sameVerNewBuild) && !localZipPath;
if (isFirstRun) {
debugLog("First run with no local payload. Installing remote build...");

View File

@@ -15,7 +15,7 @@
"@tauri-apps/plugin-opener": "^2.7.0",
"@tauri-apps/plugin-process": "^2.4.0",
"@tauri-apps/plugin-shell": "^2.4.0",
"@vyla-entertainment/sdk": "^1.18.0",
"@vyla-entertainment/sdk": "^1.20.0",
"adm-zip": "^0.6.0",
"express": "^4.19.2"
},

View File

@@ -105,23 +105,51 @@ class Patcher {
}
}
async extractToStaging(zipPath, onFile) {
const stagingDir = path.join(this.appDataPath, '.staging');
fs.rmSync(stagingDir, { recursive: true, force: true });
fs.mkdirSync(stagingDir, { recursive: true });
const zip = new AdmZip(zipPath);
const entries = zip.getEntries().filter((e) => !e.isDirectory);
if (entries.length === 0) {
throw new Error('Downloaded archive is empty, invalid, or truncated.');
}
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
zip.extractEntryTo(entry, stagingDir, true, true);
if (onFile) onFile(entry.entryName, i + 1, entries.length);
if (i % 20 === 0) await new Promise((resolve) => setImmediate(resolve));
}
return stagingDir;
}
commitStaging(stagingDir) {
this.purgeOldCode();
for (const name of fs.readdirSync(stagingDir)) {
const dest = path.join(this.appDataPath, name);
fs.rmSync(dest, { recursive: true, force: true });
fs.renameSync(path.join(stagingDir, name), dest);
}
fs.rmSync(stagingDir, { recursive: true, force: true });
}
verifyChecksum(zipPath, expectedSha256) {
if (!expectedSha256) return true;
if (!expectedSha256) return false;
const hash = crypto.createHash('sha256');
const data = fs.readFileSync(zipPath);
hash.update(data);
const calculated = hash.digest('hex');
return calculated === expectedSha256;
return calculated === String(expectedSha256).trim().toLowerCase();
}
verifyExtraction() {
verifyExtraction(root = this.appDataPath) {
const requiredFiles = [
'frontend/server.js',
'player/server.js'
];
for (const file of requiredFiles) {
const fullPath = path.join(this.appDataPath, file);
const fullPath = path.join(root, file);
if (!fs.existsSync(fullPath)) {
return false;
}

View File

@@ -87,6 +87,21 @@
}
}
.file-line {
width: 100%;
min-height: 16px;
margin-top: -12px;
margin-bottom: 14px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 11px;
color: rgba(255, 255, 255, 0.45);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
direction: rtl;
text-align: center;
}
.progress-track {
width: 100%;
height: 10px;
@@ -198,6 +213,7 @@
<div id="debug-log-box"
style="display: none; width: 100%; max-height: 180px; overflow-y: auto; background: rgba(255, 50, 50, 0.12); border-radius: 15px; padding: 12px; font-family: monospace; font-size: 12px; color: #fca5a5; text-align: left; white-space: pre-wrap; word-break: break-all; margin-bottom: 20px; user-select: text;">
</div>
<div class="file-line" id="file-line"></div>
<div class="progress-track" id="progress-track" style="display: none;">
<div class="progress-fill" id="progress-fill"></div>
</div>
@@ -515,6 +531,8 @@ void main() {
const actionsEl = document.getElementById("update-actions");
const btnYes = document.getElementById("btn-update-yes");
const btnNo = document.getElementById("btn-update-no");
const fileLine = document.getElementById("file-line");
let currentService = "";
if (actionsEl) actionsEl.style.display = "none";
if (progressTrack) progressTrack.style.display = "none";
@@ -562,10 +580,28 @@ void main() {
eventApi.listen("status", (e) => {
const data = e.payload || e;
if (data && data.message) {
if (fileLine) fileLine.textContent = "";
currentService = "";
setStatus(data.message);
}
});
eventApi.listen("extract-progress", (e) => {
const data = e.payload || e;
if (!data || !data.file) return;
if (progressTrack) progressTrack.style.display = "block";
if (actionsEl) actionsEl.style.display = "none";
if (progressFill) progressFill.style.width = `${data.percent}%`;
const parts = String(data.file).split("/");
const service = parts.length > 1 ? parts[0] : "core files";
if (service !== currentService) {
currentService = service;
setStatus(`Installing ${service}...`);
}
if (fileLine) fileLine.textContent = `${data.file} (${data.done}/${data.total})`;
});
eventApi.listen("download-progress", (e) => {
const data = e.payload || e;
if (data) {
@@ -592,6 +628,7 @@ void main() {
if (data && data.url) {
if (progressTrack) progressTrack.style.display = "none";
if (actionsEl) actionsEl.style.display = "none";
if (fileLine) fileLine.textContent = "";
setStatus("Launching...");
}
});
@@ -599,6 +636,7 @@ void main() {
eventApi.listen("error", (e) => {
const data = e.payload || e;
const msg = data && data.message ? data.message : JSON.stringify(data);
if (fileLine) fileLine.textContent = "";
setStatus("Runtime Error", "#fca5a5");
showDebug(msg);
});

View File

@@ -36,7 +36,9 @@ class UpdateClient {
const urlObj = new URL(url);
const headers = {
'User-Agent': 'VylaHome-Client',
'Accept': 'application/json'
'Accept': 'application/json',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
};
if (this.token) {
@@ -235,12 +237,21 @@ class UpdateClient {
}
async checkForUpdates() {
const cacheBuster = `?cb=${Date.now()}`;
const releases = await this.fetchJson(RELEASES_API + cacheBuster);
const query = `?per_page=20&order_by=released_at&sort=desc&cb=${Date.now()}`;
const releases = await this.fetchJson(RELEASES_API + query);
if (!releases || releases.length === 0) {
return null;
}
const release = releases[0];
const verParts = (v) => String(v || '').replace(/^v/, '').split(/[.\-+]/).map((n) => parseInt(n, 10) || 0);
const cmp = (a, b) => {
const x = verParts(a);
const y = verParts(b);
for (let i = 0; i < Math.max(x.length, y.length); i++) {
if ((x[i] || 0) !== (y[i] || 0)) return (x[i] || 0) - (y[i] || 0);
}
return 0;
};
const release = releases.slice().sort((a, b) => cmp(b.tag_name, a.tag_name))[0];
const payloadAsset = (release.assets && release.assets.links ? release.assets.links : []).find(a => a.name === 'update-payload.zip');
const manifestAsset = (release.assets && release.assets.links ? release.assets.links : []).find(a => a.name === 'manifest.json');