mirror of
https://gitlab.com/vyla-entertainment/home.git
synced 2026-11-13 21:57:21 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d799da3d2a | |||
| f4aaf2fa7c | |||
| 8ce4edc251 | |||
| bef6fd2326 | |||
| 774f9a5e03 |
3
DOCUEMENTATION/FIXES.md
Normal file
3
DOCUEMENTATION/FIXES.md
Normal file
@@ -0,0 +1,3 @@
|
||||
If someone get's a Webview2 error, they need to download the WebView2Loader.dll and drag it in bin
|
||||
|
||||
bin\WebView2Loader.dll
|
||||
38
README.md
38
README.md
@@ -1,30 +1,24 @@
|
||||
# Vyla Home Engine
|
||||
# Vyla Home
|
||||
|
||||
A high-performance, locally executed environment for the Vyla media ecosystem. Built on the Tauri v2 framework, Vyla Home acts as a secure native bridge between local microservices and a lightweight WebView2 rendering engine.
|
||||
A desktop application that provides a fast, secure, and private way to access the Vyla media ecosystem. Vyla Home runs everything locally on your computer, giving you quick access to streaming services without relying on external servers.
|
||||
|
||||
## Architecture & Security
|
||||
## What It Does
|
||||
|
||||
Vyla Home is designed around complete local isolation to maximize speed, privacy, and reliability:
|
||||
Vyla Home is a native desktop app that:
|
||||
|
||||
- **Zero External Reverse-Proxies**: All routing, API endpoints, and authentication middleware run strictly on local loopback interfaces (`127.0.0.1`), eliminating ISP throttling and data-center IP bans.
|
||||
- **Native Memory Management**: Background Node.js lifecycle states are managed directly via OS-level memory handles (bypassing slow shell wrappers) for instant startup and teardown.
|
||||
- **Cryptographic Obfuscation**: Client configurations and credentials are secured on-disk via hardware-derived AES-256-GCM encryption.
|
||||
- **Automated Integrity Patching**: Payloads are dynamically verified using SHA-256 checksums and patched in-memory via C-level buffer scans.
|
||||
- **Runs locally** - All services run on your computer for maximum privacy and speed
|
||||
- **Manages streaming services** - Handles authentication and streaming APIs automatically
|
||||
- **Updates automatically** - Keeps itself up to date with the latest features and fixes
|
||||
- **Works offline** - Core functionality doesn't require constant internet connectivity
|
||||
- **Keeps your data secure** - Your credentials and settings are encrypted and stored locally
|
||||
|
||||
## Build Requirements
|
||||
## How It Works
|
||||
|
||||
- [Rust](https://www.rust-lang.org/) (Latest Stable)
|
||||
- [Node.js](https://nodejs.org/) (v20+ LTS)
|
||||
- Tauri CLI (`npm install -g @tauri-apps/cli`)
|
||||
When you launch Vyla Home, it:
|
||||
|
||||
## Compilation
|
||||
1. Starts the necessary background services (media player server)
|
||||
2. Loads your saved settings and credentials (encrypted for security)
|
||||
3. Checks for updates and installs them if available
|
||||
4. Opens the user interface in a secure, embedded browser window
|
||||
|
||||
The build pipeline has been optimized with Fat LTO and aggressive panic aborts to generate the smallest possible binary footprint.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
|
||||
npm run build:payload
|
||||
|
||||
npm run build
|
||||
```
|
||||
Everything happens on your machine - your data never leaves your computer except when communicating directly with streaming services.
|
||||
@@ -5,7 +5,7 @@ const AdmZip = require('adm-zip');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const BUILD_DIR = path.join(__dirname, 'dist-build');
|
||||
const REPOS = ['frontend', 'player', 'auth-proxy', 'stream-api'];
|
||||
const REPOS = ['frontend', 'player'];
|
||||
|
||||
const TEMP_EXTRACT_DIR = path.join(__dirname, 'temp-validation-extract');
|
||||
|
||||
@@ -14,8 +14,6 @@ const GITLAB_GROUP = 'vyla-entertainment';
|
||||
const REPO_CONFIG = {
|
||||
'frontend': { name: 'frontend', branch: 'main' },
|
||||
'player': { name: 'player', branch: 'main' },
|
||||
'auth-proxy': { name: 'auth-proxy', branch: 'main' },
|
||||
'stream-api': { name: 'stream-api', branch: 'main' },
|
||||
};
|
||||
|
||||
const clonedRepos = new Set();
|
||||
@@ -174,12 +172,10 @@ function validatePayload(zipPath) {
|
||||
zip.extractAllTo(TEMP_EXTRACT_DIR, true);
|
||||
|
||||
const requiredFiles = [
|
||||
'player/index.html',
|
||||
'auth-proxy/server.js',
|
||||
'stream-api/server.js',
|
||||
'player/server.js',
|
||||
'frontend/index.html'
|
||||
];
|
||||
|
||||
|
||||
const validationResults = {
|
||||
valid: true,
|
||||
errors: [],
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
{
|
||||
"player": {
|
||||
"TMDB_KEY": "YOUR_TMDB_KEY_HERE",
|
||||
"API_KEY": "YOUR_API_KEY_HERE"
|
||||
},
|
||||
"auth-proxy": {
|
||||
"VITE_API_KEY": "YOUR_VITE_API_KEY_HERE",
|
||||
"PROXY_VITE_API_KEY": "YOUR_PROXY_VITE_API_KEY_HERE"
|
||||
},
|
||||
"stream-api": {
|
||||
"TMDB_API_KEY": "YOUR_TMDB_API_KEY_HERE",
|
||||
"GA_MEASUREMENT_ID": "YOUR_GA_MEASUREMENT_ID_HERE",
|
||||
"GA_API_SECRET": "YOUR_GA_API_SECRET_HERE",
|
||||
"TOKEN_SECRET": "YOUR_TOKEN_SECRET_HERE"
|
||||
"TMDB_KEY": "YOUR_TMDB_KEY_HERE"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,5 @@
|
||||
{
|
||||
"player": {
|
||||
"PORT": "3001",
|
||||
"BASE_URL": "http://localhost:7860",
|
||||
"BYPASS_AUTH": "true"
|
||||
},
|
||||
"auth-proxy": {
|
||||
"PORT": "3000",
|
||||
"VITE_BASE_URL": "http://localhost:7860",
|
||||
"DEV_BYPASS_AUTH": "true"
|
||||
},
|
||||
"stream-api": {
|
||||
"PORT": "7860",
|
||||
"PROXY_URL": "https://txt.primebox.workers.dev",
|
||||
"PROXY_STREAMS": "true",
|
||||
"ENABLE_DEBUG_ROUTE": "true",
|
||||
"BYPASS_AUTH": "true",
|
||||
"TOKEN_SECRET": "vyla_default_secure_token_secret_local_dev_32chars"
|
||||
"PORT": "3000"
|
||||
}
|
||||
}
|
||||
@@ -2,40 +2,17 @@
|
||||
"frontend": [
|
||||
{
|
||||
"find": "https://player.vyla.cc",
|
||||
"replace": "http://localhost:3001"
|
||||
"replace": "http://localhost:3000"
|
||||
},
|
||||
{
|
||||
"find": "http://player.vyla.cc",
|
||||
"replace": "http://localhost:3001"
|
||||
},
|
||||
{
|
||||
"find": "https://api.vyla.cc",
|
||||
"replace": "http://localhost:7860"
|
||||
},
|
||||
{
|
||||
"find": "https://auth.vyla.cc",
|
||||
"replace": "http://localhost:3000"
|
||||
}
|
||||
],
|
||||
"player": [
|
||||
{
|
||||
"find": "https://api.vyla.cc",
|
||||
"replace": "http://localhost:7860"
|
||||
},
|
||||
{
|
||||
"find": "https://auth.vyla.cc",
|
||||
"replace": "http://localhost:3000"
|
||||
},
|
||||
{
|
||||
"find": "https://player.vyla.cc",
|
||||
"replace": "http://localhost:4890"
|
||||
}
|
||||
],
|
||||
"auth-proxy": [
|
||||
{
|
||||
"find": "https://player.vyla.cc",
|
||||
"replace": "http://localhost:3001"
|
||||
}
|
||||
],
|
||||
"stream-api": []
|
||||
]
|
||||
}
|
||||
@@ -4,9 +4,7 @@ const path = require('path');
|
||||
|
||||
const REPOS = {
|
||||
frontend: { branch: 'main' },
|
||||
player: { branch: 'main' },
|
||||
'stream-api': { branch: 'main' },
|
||||
'auth-proxy': { branch: 'main' }
|
||||
player: { branch: 'main' }
|
||||
};
|
||||
|
||||
const GITLAB_GROUP = 'vyla-entertainment';
|
||||
|
||||
122
main.js
122
main.js
@@ -34,8 +34,6 @@ debugLog("--- Node.js Process Started ---");
|
||||
let serviceManager;
|
||||
let expressApp;
|
||||
let expressServer;
|
||||
let playerExpressApp;
|
||||
let playerExpressServer;
|
||||
|
||||
debugLog(`AppData Dir: ${APPDATA_DIR}`);
|
||||
|
||||
@@ -217,27 +215,51 @@ async function start() {
|
||||
let localSha = patcher.getCurrentSha();
|
||||
debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}, Local SHA: ${localSha}`);
|
||||
|
||||
const localZipPath = path.join(__dirname, 'update-payload.zip');
|
||||
const localManifestPath = path.join(__dirname, 'manifest.json');
|
||||
let localZipManifest = null;
|
||||
|
||||
if (fs.existsSync(localZipPath) && fs.existsSync(localManifestPath)) {
|
||||
try { localZipManifest = JSON.parse(fs.readFileSync(localManifestPath, 'utf8')); } catch (e) { }
|
||||
function findLocalZip() {
|
||||
const searchRoots = [__dirname, process.cwd()];
|
||||
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;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (localZipManifest && (isFirstRun || localZipManifest.sha256 !== localSha)) {
|
||||
debugLog("New local payload detected! Applying local build...");
|
||||
const localFound = findLocalZip();
|
||||
let localZipPath = localFound ? localFound.zipPath : null;
|
||||
let localManifestPath = localFound ? localFound.manifestPath : 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);
|
||||
patcher.saveCurrentVersion(localZipManifest.version, localZipManifest.sha256);
|
||||
|
||||
const ver = localZipManifest ? localZipManifest.version : '1.1.32';
|
||||
const sha = localZipManifest ? localZipManifest.sha256 : null;
|
||||
patcher.saveCurrentVersion(ver, sha);
|
||||
serviceManager.envConfig = getEnvConfig();
|
||||
|
||||
isFirstRun = false;
|
||||
localVersion = localZipManifest.version;
|
||||
localSha = localZipManifest.sha256;
|
||||
localVersion = ver;
|
||||
localSha = sha;
|
||||
debugLog("Local payload applied successfully.");
|
||||
}
|
||||
|
||||
@@ -247,18 +269,43 @@ async function start() {
|
||||
debugLog("Fetching updates from updateClient...");
|
||||
release = await updateClient.checkForUpdates();
|
||||
debugLog(`Update check finished. Found: ${release ? release.version : 'None'}`);
|
||||
|
||||
let appPkgVer = '0.0.0';
|
||||
try {
|
||||
appPkgVer = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8')).version || '0.0.0';
|
||||
} 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}`);
|
||||
sendToTauri('status', { message: `Update server check skipped (${e.message})` });
|
||||
}
|
||||
|
||||
const normalizeVer = (v) => (v || '').toString().replace(/^v/, '').trim();
|
||||
|
||||
if (release) {
|
||||
const hasUpdate = release.version !== localVersion || (release.sha256 && release.sha256 !== localSha);
|
||||
const isNewerSha = release.sha256 && localSha && release.sha256 !== localSha;
|
||||
const isDifferentVer = normalizeVer(release.version) !== normalizeVer(localVersion);
|
||||
const hasUpdate = (isDifferentVer || isNewerSha) && !localZipPath;
|
||||
|
||||
if (isFirstRun) {
|
||||
debugLog("First run. Installing remote build...");
|
||||
debugLog("First run with no local payload. Installing remote build...");
|
||||
sendToTauri('status', { message: 'First run detected. Preparing initial installation...' });
|
||||
await installBuild(release);
|
||||
} else if (hasUpdate) {
|
||||
|
||||
debugLog("Update available. Prompting user...");
|
||||
sendToTauri('status', { message: `New update available (${release.version}). Awaiting confirmation...` });
|
||||
|
||||
@@ -327,51 +374,21 @@ async function start() {
|
||||
|
||||
sendToTauri('status', { message: 'Validating critical components...' });
|
||||
if (!patcher.verifyExtraction()) {
|
||||
throw new Error('No application files found. Check your internet connection and try again.');
|
||||
throw new Error('No application files found. Ensure you run "npm run build:payload" to generate the local development payload.');
|
||||
}
|
||||
|
||||
sendToTauri('status', { message: 'Loading encrypted credentials...' });
|
||||
serviceManager.credentialManager.loadCredentials();
|
||||
|
||||
sendToTauri('status', { message: 'Starting background services (auth-proxy, stream-api)...' });
|
||||
sendToTauri('status', { message: 'Starting background services (player)...' });
|
||||
await serviceManager.startServices([
|
||||
{ name: 'auth-proxy', relativeDir: 'auth-proxy' },
|
||||
{ name: 'stream-api', relativeDir: 'stream-api' },
|
||||
{ name: 'player', relativeDir: 'player', startScript: 'server.js' }
|
||||
]);
|
||||
debugLog("Backend services spawn requested.");
|
||||
|
||||
sendToTauri('status', { message: 'Binding player web server on port 3001...' });
|
||||
const playerDir = path.join(APPDATA_DIR, 'player');
|
||||
playerExpressApp = express();
|
||||
playerExpressApp.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();
|
||||
});
|
||||
playerExpressApp.get('/__tauri_cmd/:cmd', (req, res) => {
|
||||
debugLog(`[PLAYER-SERVER:3001] 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);
|
||||
});
|
||||
playerExpressApp.use(sameTabNavMiddleware(playerDir));
|
||||
playerExpressApp.use(express.static(playerDir, {
|
||||
etag: false,
|
||||
setHeaders: (res) => {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
||||
}
|
||||
}));
|
||||
playerExpressApp.get('*', (req, res) => {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
||||
res.sendFile(path.join(playerDir, 'index.html'));
|
||||
});
|
||||
playerExpressServer = playerExpressApp.listen(3001, '127.0.0.1');
|
||||
debugLog("Player Express running on 3001");
|
||||
|
||||
sendToTauri('status', { message: 'Waiting for microservices to bind ports (3000, 3001, 7860)...' });
|
||||
await serviceManager.waitForPorts([3000, 3001, 7860]);
|
||||
debugLog("Ports 3000, 3001, 7860 are ready.");
|
||||
sendToTauri('status', { message: 'Waiting for microservices to bind port 3000...' });
|
||||
await serviceManager.waitForPorts([3000]);
|
||||
debugLog("Port 3000 is ready.");
|
||||
|
||||
sendToTauri('status', { message: 'Binding UI server on port 4890...' });
|
||||
const frontendDir = path.join(APPDATA_DIR, 'frontend');
|
||||
@@ -409,9 +426,6 @@ async function start() {
|
||||
|
||||
const FRONTEND_PORT = 4890;
|
||||
expressApp.use('/build', express.static(path.join(__dirname, 'build')));
|
||||
if (playerExpressApp) {
|
||||
playerExpressApp.use('/build', express.static(path.join(__dirname, 'build')));
|
||||
}
|
||||
|
||||
expressServer = expressApp.listen(FRONTEND_PORT, () => {
|
||||
sendToTauri('status', { message: 'All backend services running and healthy.' });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vyla-home",
|
||||
"version": "1.1.31",
|
||||
"version": "1.1.33",
|
||||
"description": "Vyla Home Desktop App Launcher",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
@@ -54,4 +54,4 @@
|
||||
"installerHeaderIcon": "build/icon.ico"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +99,7 @@ class Patcher {
|
||||
|
||||
verifyExtraction() {
|
||||
const requiredFiles = [
|
||||
'player/index.html',
|
||||
'auth-proxy/server.js',
|
||||
'stream-api/server.js',
|
||||
'player/server.js',
|
||||
'frontend/index.html'
|
||||
];
|
||||
|
||||
@@ -155,7 +153,7 @@ class Patcher {
|
||||
}
|
||||
|
||||
applyPatches(replacements) {
|
||||
const targetDirs = ['frontend', 'player', 'auth-proxy', 'stream-api'];
|
||||
const targetDirs = ['frontend', 'player'];
|
||||
for (const dirName of targetDirs) {
|
||||
const dirPath = path.join(this.appDataPath, dirName);
|
||||
if (!fs.existsSync(dirPath)) continue;
|
||||
|
||||
@@ -40,18 +40,16 @@ class ServiceManager {
|
||||
}
|
||||
|
||||
let tokenSecret = process.env.TOKEN_SECRET ||
|
||||
process.env.STREAM_API_TOKEN_SECRET ||
|
||||
this.credentialManager.getCredential('stream-api', 'TOKEN_SECRET');
|
||||
this.credentialManager.getCredential('player', 'TOKEN_SECRET');
|
||||
|
||||
if (!tokenSecret || tokenSecret.length < 32) {
|
||||
tokenSecret = require('crypto').randomBytes(32).toString('hex');
|
||||
this.credentialManager.setCredential('stream-api', 'TOKEN_SECRET', tokenSecret);
|
||||
this.credentialManager.setCredential('player', 'TOKEN_SECRET', tokenSecret);
|
||||
}
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
TOKEN_SECRET: tokenSecret,
|
||||
STREAM_API_TOKEN_SECRET: tokenSecret,
|
||||
...(this.envConfig[name] || {}),
|
||||
...(this.credentialManager.getServiceCredentials(name) || {})
|
||||
};
|
||||
|
||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -4238,7 +4238,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vyla-home"
|
||||
version = "1.1.31"
|
||||
version = "1.1.33"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"objc2 0.5.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vyla-home"
|
||||
version = "1.1.31"
|
||||
version = "1.1.33"
|
||||
description = "Vyla Home Desktop App Launcher"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -746,37 +746,7 @@ fn main() {
|
||||
app_data.join("vyla-home-files")
|
||||
};
|
||||
|
||||
let version_file = vyla_files.join("version.json");
|
||||
let current_app_version = app.package_info().version.to_string();
|
||||
let mut version_mismatch = false;
|
||||
|
||||
if version_file.exists() {
|
||||
if let Ok(content) = std::fs::read_to_string(&version_file) {
|
||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
if let Some(version) = json.get("version").and_then(|v| v.as_str()) {
|
||||
if version != current_app_version {
|
||||
version_mismatch = true;
|
||||
}
|
||||
} else {
|
||||
version_mismatch = true;
|
||||
}
|
||||
} else {
|
||||
version_mismatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if version_mismatch {
|
||||
debug_log(&format!(
|
||||
"App version updated to {}. Cleaning old files.",
|
||||
current_app_version
|
||||
));
|
||||
let dirs_to_remove = ["frontend", "player", "stream-api", "auth-proxy", "config"];
|
||||
for d in dirs_to_remove {
|
||||
let _ = std::fs::remove_dir_all(vyla_files.join(d));
|
||||
}
|
||||
let _ = std::fs::remove_file(&version_file);
|
||||
}
|
||||
let _ = std::fs::create_dir_all(&vyla_files);
|
||||
|
||||
let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into()))
|
||||
.title("Vyla Home")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Vyla Home",
|
||||
"version": "1.1.31",
|
||||
"version": "1.1.33",
|
||||
"identifier": "com.vyla.home",
|
||||
"build": {
|
||||
"frontendDist": "../ui"
|
||||
@@ -45,4 +45,4 @@
|
||||
"../node_modules/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,15 +569,14 @@ void main() {
|
||||
if (progressTrack) progressTrack.style.display = "block";
|
||||
const percent = data.percent || 0;
|
||||
if (progressFill) progressFill.style.width = percent + "%";
|
||||
if (!currentStatusText.startsWith("Downloading update...")) {
|
||||
setStatus("Downloading update...");
|
||||
}
|
||||
setStatus(`Downloading update (${percent}%)...`);
|
||||
}
|
||||
});
|
||||
|
||||
eventApi.listen("update-available", (e) => {
|
||||
const data = e.payload || e;
|
||||
if (data) {
|
||||
if (progressTrack) progressTrack.style.display = "none";
|
||||
setStatus(`Update Available: ${data.latest} (Current: ${data.current})`);
|
||||
if (actionsEl) actionsEl.style.display = "flex";
|
||||
}
|
||||
@@ -586,6 +585,8 @@ void main() {
|
||||
eventApi.listen("ready", (e) => {
|
||||
const data = e.payload || e;
|
||||
if (data && data.url) {
|
||||
if (progressTrack) progressTrack.style.display = "none";
|
||||
if (actionsEl) actionsEl.style.display = "none";
|
||||
setStatus("Launching...");
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user