mirror of
https://gitlab.com/vyla-entertainment/home.git
synced 2026-11-13 21:57:21 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b2c97e74b | ||
|
|
d9ae75e9cb | ||
|
|
51951ac192 | ||
|
|
c131515209 | ||
|
|
b44f59f4b7 | ||
|
|
35087d24e8 | ||
| 2435ee51e6 | |||
| 59f4d9855f |
BIN
build/white_favicon.png
Normal file
BIN
build/white_favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 223 KiB |
161
main.js
161
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 = !patcher.verifyExtraction();
|
||||
let localVersion = patcher.getCurrentVersion();
|
||||
let localSha = patcher.getCurrentSha();
|
||||
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() {
|
||||
@@ -249,36 +266,10 @@ async function start() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const localFound = findLocalZip();
|
||||
let localZipPath = localFound ? localFound.zipPath : null;
|
||||
let localManifestPath = localFound ? localFound.manifestPath : null;
|
||||
let localZipPath = null;
|
||||
let localManifestPath = null;
|
||||
let localZipManifest = null;
|
||||
|
||||
if (localManifestPath) {
|
||||
try {
|
||||
localZipManifest = JSON.parse(fs.readFileSync(localManifestPath, 'utf8'));
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
if (localZipPath && (isFirstRun || (localZipManifest && localZipManifest.sha256 !== localSha))) {
|
||||
debugLog(`Applying local development payload from: ${localZipPath}`);
|
||||
sendToTauri('status', { message: 'Extracting local development payload...' });
|
||||
patcher.purgeOldCode();
|
||||
patcher.extractZipPayload(localZipPath);
|
||||
patcher.applyPatches(getReplacementsConfig());
|
||||
patcher.bridgeCredentials(serviceManager.credentialManager);
|
||||
|
||||
const ver = localZipManifest ? localZipManifest.version : '1.1.32';
|
||||
const sha = localZipManifest ? localZipManifest.sha256 : null;
|
||||
patcher.saveCurrentVersion(ver, sha);
|
||||
serviceManager.envConfig = getEnvConfig();
|
||||
|
||||
isFirstRun = false;
|
||||
localVersion = ver;
|
||||
localSha = sha;
|
||||
debugLog("Local payload applied successfully.");
|
||||
}
|
||||
|
||||
sendToTauri('status', { message: 'Connecting to update servers...' });
|
||||
let release = null;
|
||||
try {
|
||||
@@ -292,17 +283,6 @@ async function start() {
|
||||
} catch (e) { }
|
||||
|
||||
if (release && release.version) {
|
||||
const rVer = release.version.replace('v', '').split('.').map(Number);
|
||||
const aVer = appPkgVer.split('.').map(Number);
|
||||
let isOlder = false;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if ((rVer[i] || 0) < (aVer[i] || 0)) { isOlder = true; break; }
|
||||
if ((rVer[i] || 0) > (aVer[i] || 0)) { break; }
|
||||
}
|
||||
if (isOlder) {
|
||||
debugLog(`Remote version ${release.version} is older than local package ${appPkgVer}. Ignoring remote release.`);
|
||||
release = null;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugLog(`Update check failed (offline?): ${e.message}`);
|
||||
@@ -310,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...");
|
||||
@@ -323,7 +315,7 @@ async function start() {
|
||||
} else if (hasUpdate) {
|
||||
|
||||
debugLog("Update available. Prompting user...");
|
||||
sendToTauri('status', { message: `New update available (${release.version}). Awaiting confirmation...` });
|
||||
sendToTauri('status', { message: `New update available (${release.version}).` });
|
||||
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
@@ -434,58 +426,33 @@ async function start() {
|
||||
sendToTauri('status', { message: 'Loading encrypted credentials...' });
|
||||
serviceManager.credentialManager.loadCredentials();
|
||||
|
||||
sendToTauri('status', { message: 'Starting background services (player)...' });
|
||||
sendToTauri('status', { message: 'Installing dependencies...' });
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
const frontendDir = path.join(APPDATA_DIR, 'frontend');
|
||||
if (fs.existsSync(path.join(frontendDir, 'package.json'))) {
|
||||
const { spawnSync } = require('child_process');
|
||||
spawnSync(npmCmd, ['install'], {
|
||||
cwd: frontendDir,
|
||||
stdio: 'ignore',
|
||||
windowsHide: true,
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
}
|
||||
|
||||
sendToTauri('status', { message: 'Starting services (player, frontend)...' });
|
||||
await serviceManager.startServices([
|
||||
{ name: 'player', relativeDir: 'player', startScript: 'server.js' }
|
||||
{ name: 'player', relativeDir: 'player', startScript: 'server.js' },
|
||||
{ name: 'frontend', relativeDir: 'frontend', startScript: 'server.js' }
|
||||
]);
|
||||
debugLog("Backend services spawn requested.");
|
||||
|
||||
sendToTauri('status', { message: 'Waiting for microservices to bind port 3000...' });
|
||||
await serviceManager.waitForPorts([3000]);
|
||||
debugLog("Port 3000 is ready.");
|
||||
sendToTauri('status', { message: 'Waiting for microservices to bind...' });
|
||||
await serviceManager.waitForPorts([3000, 4890]);
|
||||
debugLog("Ports 3000 and 4890 are ready.");
|
||||
|
||||
sendToTauri('status', { message: 'Binding UI server on port 4890...' });
|
||||
const frontendDir = path.join(APPDATA_DIR, 'frontend');
|
||||
expressApp = express();
|
||||
expressApp.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', '*');
|
||||
if (req.method === 'OPTIONS') return res.sendStatus(200);
|
||||
next();
|
||||
});
|
||||
expressApp.get('/__tauri_cmd/:cmd', (req, res) => {
|
||||
debugLog(`[FRONTEND-SERVER:4890] 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 });
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
expressApp.use(sameTabNavMiddleware(frontendDir));
|
||||
expressApp.use(express.static(frontendDir, {
|
||||
etag: false,
|
||||
setHeaders: (res) => {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
||||
}
|
||||
}));
|
||||
expressApp.use(express.static(path.join(frontendDir, 'src'), {
|
||||
etag: false,
|
||||
setHeaders: (res) => {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
||||
}
|
||||
}));
|
||||
expressApp.get('*', (req, res) => {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
||||
res.sendFile(path.join(frontendDir, 'index.html'));
|
||||
});
|
||||
|
||||
const FRONTEND_PORT = 4890;
|
||||
expressApp.use('/build', express.static(path.join(__dirname, 'build')));
|
||||
|
||||
expressServer = expressApp.listen(FRONTEND_PORT, '127.0.0.1', () => {
|
||||
sendToTauri('status', { message: 'All backend services running and healthy.' });
|
||||
debugLog("Frontend Express running on 4890. Sending READY to Tauri.");
|
||||
sendToTauri('ready', { url: `http://127.0.0.1:${FRONTEND_PORT}` });
|
||||
});
|
||||
sendToTauri('status', { message: 'All backend services running and healthy.' });
|
||||
debugLog("Sending READY to Tauri.");
|
||||
sendToTauri('ready', { url: 'http://127.0.0.1:4890' });
|
||||
|
||||
} catch (error) {
|
||||
debugLog(`Fatal Start Error: ${error.message}`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vyla-home",
|
||||
"version": "1.1.42",
|
||||
"version": "1.1.46",
|
||||
"description": "Vyla Home Desktop App Launcher",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
@@ -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.16.0",
|
||||
"@vyla-entertainment/sdk": "^1.20.0",
|
||||
"adm-zip": "^0.6.0",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
@@ -54,4 +54,4 @@
|
||||
"installerHeaderIcon": "build/icon.ico"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
patcher.js
38
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/index.html',
|
||||
'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;
|
||||
}
|
||||
|
||||
@@ -47,8 +47,14 @@ class ServiceManager {
|
||||
this.credentialManager.setCredential('player', 'TOKEN_SECRET', tokenSecret);
|
||||
}
|
||||
|
||||
const defaultPorts = {
|
||||
player: 3000,
|
||||
frontend: 4890
|
||||
};
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
PORT: (this.envConfig[name] && this.envConfig[name].PORT) || defaultPorts[name] || process.env.PORT || 3000,
|
||||
TOKEN_SECRET: tokenSecret,
|
||||
...(this.envConfig[name] || {}),
|
||||
...(this.credentialManager.getServiceCredentials(name) || {})
|
||||
|
||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -4155,7 +4155,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vyla-home"
|
||||
version = "1.1.43"
|
||||
version = "1.1.46"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"objc2 0.5.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vyla-home"
|
||||
version = "1.1.42"
|
||||
version = "1.1.46"
|
||||
description = "Vyla Home Desktop App Launcher"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -269,110 +269,11 @@ const LINK_HANDLER_SCRIPT: &str =
|
||||
}, true);
|
||||
|
||||
function injectLiquidNavBar() {
|
||||
if (document.getElementById('vyla-floating-nav')) return;
|
||||
if (document.getElementById('tauri-custom-titlebar')) return;
|
||||
if (!document.body) return;
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
#vyla-floating-nav {
|
||||
position: fixed !important;
|
||||
top: 12px !important;
|
||||
left: 12px !important;
|
||||
z-index: 2147483647 !important;
|
||||
height: 42px !important;
|
||||
width: 42px !important;
|
||||
border-radius: 9999px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
background: rgba(255, 255, 255, 0.04) !important;
|
||||
backdrop-filter: blur(16px) saturate(1.8) contrast(1.1) !important;
|
||||
-webkit-backdrop-filter: blur(16px) saturate(1.8) contrast(1.1) !important;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45) !important;
|
||||
transition: width 0.3s cubic-bezier(0.16, 1, 0.3, 1), padding 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
user-select: none !important;
|
||||
pointer-events: auto !important;
|
||||
-webkit-app-region: no-drag !important;
|
||||
}
|
||||
|
||||
#vyla-floating-nav:hover {
|
||||
width: 82px !important;
|
||||
padding-right: 6px !important;
|
||||
}
|
||||
|
||||
.vyla-nav-logo-wrap {
|
||||
width: 42px !important;
|
||||
min-width: 42px !important;
|
||||
height: 42px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
cursor: pointer !important;
|
||||
border-radius: 9999px !important;
|
||||
position: relative !important;
|
||||
z-index: 2147483647 !important;
|
||||
pointer-events: auto !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.vyla-nav-logo-wrap img {
|
||||
width: 26px !important;
|
||||
height: 26px !important;
|
||||
min-width: 26px !important;
|
||||
min-height: 26px !important;
|
||||
object-fit: contain !important;
|
||||
pointer-events: none !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.vyla-nav-links {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 4px !important;
|
||||
opacity: 0 !important;
|
||||
transform: translateX(-6px) !important;
|
||||
transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
||||
pointer-events: none !important;
|
||||
position: relative !important;
|
||||
z-index: 2 !important;
|
||||
margin-left: 2px !important;
|
||||
}
|
||||
|
||||
#vyla-floating-nav:hover .vyla-nav-links {
|
||||
opacity: 1 !important;
|
||||
transform: translateX(0) !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
.vyla-nav-btn {
|
||||
width: 32px !important;
|
||||
min-width: 32px !important;
|
||||
height: 32px !important;
|
||||
border-radius: 9999px !important;
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
color: #ffffff !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
cursor: pointer !important;
|
||||
transition: transform 0.15s ease, opacity 0.2s ease !important;
|
||||
opacity: 0.85 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.vyla-nav-btn:hover { opacity: 1 !important; }
|
||||
.vyla-nav-btn svg {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
fill: #ffffff !important;
|
||||
pointer-events: none !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
#tauri-custom-titlebar {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
@@ -429,38 +330,6 @@ const LINK_HANDLER_SCRIPT: &str =
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
const nav = document.createElement('div');
|
||||
nav.id = 'vyla-floating-nav';
|
||||
nav.innerHTML = `
|
||||
<div class="vyla-nav-logo-wrap" title="Go to Home">
|
||||
<img src="https://gitlab.com/vyla-entertainment/home-frontend/-/raw/main/assets/images/icons/logo_white.png" alt="Logo" />
|
||||
</div>
|
||||
<div class="vyla-nav-links">
|
||||
<button class="vyla-nav-btn" id="vyla-btn-discord" title="Discord Community">
|
||||
<svg viewBox="0 0 127.14 96.36"><path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,45.91,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,45.91,96.12,53,91.08,65.69,84.69,65.69Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(nav);
|
||||
|
||||
const logoBtn = nav.querySelector('.vyla-nav-logo-wrap');
|
||||
if (logoBtn) {
|
||||
logoBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.location.href = 'http://127.0.0.1:4890';
|
||||
};
|
||||
}
|
||||
|
||||
const btnDiscord = nav.querySelector('#vyla-btn-discord');
|
||||
if (btnDiscord) {
|
||||
btnDiscord.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openExternalUrl('https://discord.gg/5eqRhKeywG');
|
||||
};
|
||||
}
|
||||
|
||||
const tb = document.createElement('div');
|
||||
tb.id = 'tauri-custom-titlebar';
|
||||
tb.innerHTML = `
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Vyla Home",
|
||||
"version": "1.1.42",
|
||||
"version": "1.1.46",
|
||||
"identifier": "com.vyla.home",
|
||||
"build": {
|
||||
"frontendDist": "../ui"
|
||||
@@ -45,4 +45,4 @@
|
||||
"../node_modules/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -196,8 +211,9 @@
|
||||
<div class="loader-card">
|
||||
<div class="status-text" id="status-message">Starting runtime engine...</div>
|
||||
<div id="debug-log-box"
|
||||
style="display: none; width: 100%; max-height: 180px; overflow-y: auto; background: rgba(255, 50, 50, 0.12); border: 1px solid rgba(255, 100, 100, 0.3); border-radius: 8px; 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;">
|
||||
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,17 +580,37 @@ 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) {
|
||||
if (progressTrack) progressTrack.style.display = "block";
|
||||
if (progressFill) progressFill.style.width = "100%";
|
||||
if (progressFill && data.percent !== undefined) {
|
||||
progressFill.style.width = `${data.percent}%`;
|
||||
}
|
||||
if (actionsEl) actionsEl.style.display = "none";
|
||||
setStatus("Downloading update...");
|
||||
setStatus(`Downloading update... ${data.percent !== undefined ? data.percent + '%' : ''}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -590,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...");
|
||||
}
|
||||
});
|
||||
@@ -597,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