mirror of
https://gitlab.com/vyla-entertainment/home.git
synced 2026-11-13 21:57:21 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
998e16674c | ||
|
|
179372dad3 | ||
|
|
59d25f53dd | ||
|
|
4ed853b5ae | ||
|
|
1b2c97e74b | ||
|
|
d9ae75e9cb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
dist/
|
||||
dist-build/
|
||||
|
||||
@@ -21,7 +21,7 @@ workflows:
|
||||
- name: Install dependencies
|
||||
script: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
npm install
|
||||
npm ci
|
||||
- name: Build & Publish macOS Release
|
||||
script: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
|
||||
49
main.js
49
main.js
@@ -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...");
|
||||
|
||||
1534
package-lock.json
generated
Normal file
1534
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vyla-home",
|
||||
"version": "1.1.45",
|
||||
"version": "1.1.48",
|
||||
"description": "Vyla Home Desktop App Launcher",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
@@ -11,16 +11,16 @@
|
||||
"release:full": "node release.js --installer"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.12.1",
|
||||
"@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",
|
||||
"@tauri-apps/api": "2.12.1",
|
||||
"@tauri-apps/plugin-opener": "2.7.0",
|
||||
"@tauri-apps/plugin-process": "2.4.0",
|
||||
"@tauri-apps/plugin-shell": "2.5.0",
|
||||
"@vyla-entertainment/sdk": "^1.20.0",
|
||||
"adm-zip": "^0.6.0",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.0"
|
||||
"@tauri-apps/cli": "2.12.1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.vyla.home",
|
||||
@@ -54,4 +54,4 @@
|
||||
"installerHeaderIcon": "build/icon.ico"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
patcher.js
36
patcher.js
@@ -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;
|
||||
}
|
||||
|
||||
52
src-tauri/Cargo.lock
generated
52
src-tauri/Cargo.lock
generated
@@ -741,7 +741,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -921,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2062,7 +2062,7 @@ dependencies = [
|
||||
"png 0.18.1",
|
||||
"serde",
|
||||
"thiserror 2.0.20",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2390,7 +2390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2844,7 +2844,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3125,7 +3125,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"sigchld",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
@@ -3135,27 +3134,6 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "sigchld"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"os_pipe",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
@@ -3197,7 +3175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3580,11 +3558,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "094bdbc47e5efe36544ba21b3a53ab5c724150236cf55e6c9be46191deb6eab6"
|
||||
checksum = "8567d42b2c7264355daa1df415ec77f52aa42e885924c86a969be2e920e05521"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"libc",
|
||||
"log",
|
||||
"open",
|
||||
"os_pipe",
|
||||
@@ -3597,6 +3576,7 @@ dependencies = [
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3709,7 +3689,7 @@ dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4034,7 +4014,7 @@ dependencies = [
|
||||
"png 0.18.1",
|
||||
"serde",
|
||||
"thiserror 2.0.20",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4063,7 +4043,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4155,7 +4135,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vyla-home"
|
||||
version = "1.1.45"
|
||||
version = "1.1.48"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"objc2 0.5.2",
|
||||
@@ -4402,7 +4382,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4423,7 +4403,7 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"raw-window-handle",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-version",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vyla-home"
|
||||
version = "1.1.45"
|
||||
version = "1.1.48"
|
||||
description = "Vyla Home Desktop App Launcher"
|
||||
edition = "2021"
|
||||
|
||||
@@ -11,7 +11,7 @@ tauri-build = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = ["devtools"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-shell = "2.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
base64 = "0.22"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Vyla Home",
|
||||
"version": "1.1.45",
|
||||
"version": "1.1.48",
|
||||
"identifier": "com.vyla.home",
|
||||
"build": {
|
||||
"frontendDist": "../ui"
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user