mirror of
https://gitlab.com/vyla-entertainment/home.git
synced 2026-11-12 13:17:21 -05:00
239 lines
9.1 KiB
JavaScript
239 lines
9.1 KiB
JavaScript
const path = require('path');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const express = require('express');
|
|
const ServiceManager = require('./service-manager');
|
|
const Patcher = require('./patcher');
|
|
const UpdateClient = require('./update-client');
|
|
|
|
const debugLogPath = path.join(os.homedir(), 'Downloads', 'vyla-node-debug.log');
|
|
function debugLog(msg) {
|
|
try { fs.appendFileSync(debugLogPath, `[NODE] ${msg}\n`, 'utf8'); } catch (e) { }
|
|
}
|
|
|
|
debugLog("--- Node.js Process Started ---");
|
|
|
|
let serviceManager;
|
|
let expressApp;
|
|
let expressServer;
|
|
let playerExpressApp;
|
|
let playerExpressServer;
|
|
|
|
const APPDATA_DIR = process.env.APPDATA_PATH || path.join(os.homedir(), 'AppData', 'Roaming', 'vyla-home', 'vyla-home-files');
|
|
fs.mkdirSync(APPDATA_DIR, { recursive: true });
|
|
debugLog(`AppData Dir: ${APPDATA_DIR}`);
|
|
|
|
const envConfig = JSON.parse(fs.readFileSync(path.join(__dirname, 'config/env.json'), 'utf8'));
|
|
const replacementsConfig = JSON.parse(fs.readFileSync(path.join(__dirname, 'config/replacements.json'), 'utf8'));
|
|
|
|
const updateClient = new UpdateClient(APPDATA_DIR);
|
|
|
|
function sendToTauri(event, data) {
|
|
debugLog(`Emitting to Tauri: [${event}] -> ${JSON.stringify(data)}`);
|
|
process.stdout.write(JSON.stringify({ event, data }) + '\n');
|
|
}
|
|
|
|
process.on('unhandledRejection', (reason) => {
|
|
const message = reason && reason.message ? reason.message : String(reason);
|
|
debugLog(`UNHANDLED REJECTION: ${message}`);
|
|
sendToTauri('error', { message });
|
|
});
|
|
|
|
process.on('uncaughtException', (err) => {
|
|
debugLog(`UNCAUGHT EXCEPTION: ${err.message}`);
|
|
sendToTauri('error', { message: err.message });
|
|
});
|
|
|
|
async function start() {
|
|
debugLog("Inside start() function");
|
|
sendToTauri('status', { message: 'Initializing patcher...' });
|
|
const patcher = new Patcher(APPDATA_DIR);
|
|
serviceManager = new ServiceManager(APPDATA_DIR, envConfig);
|
|
|
|
const installBuild = async (release) => {
|
|
const tempZipPath = path.join(APPDATA_DIR, 'download-temp.zip');
|
|
let attempts = 0;
|
|
let downloadSuccess = false;
|
|
|
|
while (attempts < 3 && !downloadSuccess) {
|
|
attempts++;
|
|
try {
|
|
debugLog(`Attempting to download update (Attempt ${attempts})...`);
|
|
sendToTauri('status', { message: `Downloading update (Attempt ${attempts}/3)...` });
|
|
|
|
await updateClient.downloadUpdate(release, tempZipPath, (downloaded, total) => {
|
|
const percent = total > 0 ? Math.round((downloaded / total) * 100) : 0;
|
|
sendToTauri('download-progress', { downloaded, total, percent });
|
|
});
|
|
|
|
debugLog('Download complete. Verifying checksum...');
|
|
sendToTauri('status', { message: 'Verifying payload checksum...' });
|
|
if (!patcher.verifyChecksum(tempZipPath, release.sha256)) {
|
|
throw new Error('SHA-256 checksum verification failed.');
|
|
}
|
|
|
|
debugLog('Checksum verified. Extracting...');
|
|
sendToTauri('status', { message: 'Extracting update files...' });
|
|
patcher.purgeOldCode();
|
|
patcher.extractZipPayload(tempZipPath);
|
|
|
|
if (!patcher.verifyExtraction()) {
|
|
throw new Error('Extraction verification failed: critical startup files missing.');
|
|
}
|
|
|
|
debugLog('Extraction complete. Applying patches...');
|
|
sendToTauri('status', { message: 'Applying patches & configuring...' });
|
|
patcher.applyPatches(replacementsConfig);
|
|
patcher.bridgeCredentials(serviceManager.credentialManager);
|
|
patcher.saveCurrentVersion(release.version, release.sha256);
|
|
|
|
if (fs.existsSync(tempZipPath)) {
|
|
fs.unlinkSync(tempZipPath);
|
|
}
|
|
downloadSuccess = true;
|
|
debugLog('Update installed successfully!');
|
|
} catch (err) {
|
|
debugLog(`Download/Install error on attempt ${attempts}: ${err.message}`);
|
|
if (fs.existsSync(tempZipPath)) {
|
|
try { fs.unlinkSync(tempZipPath); } catch (e) { }
|
|
}
|
|
if (attempts >= 3) {
|
|
throw new Error(`Failed to download and verify build after 3 attempts: ${err.message}`);
|
|
}
|
|
await new Promise(r => setTimeout(r, 2000));
|
|
}
|
|
}
|
|
};
|
|
|
|
try {
|
|
const isFirstRun = !patcher.verifyExtraction();
|
|
const localVersion = patcher.getCurrentVersion();
|
|
const localSha = patcher.getCurrentSha();
|
|
debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}`);
|
|
|
|
sendToTauri('status', { message: 'Checking for updates...' });
|
|
let release = null;
|
|
try {
|
|
debugLog("Fetching updates from updateClient...");
|
|
release = await updateClient.checkForUpdates();
|
|
debugLog(`Update check finished. Found: ${release ? release.version : 'None'}`);
|
|
} catch (e) {
|
|
debugLog(`Update check failed (offline?): ${e.message}`);
|
|
}
|
|
|
|
if (release) {
|
|
const hasUpdate = release.version !== localVersion || (release.sha256 && release.sha256 !== localSha);
|
|
if (isFirstRun) {
|
|
debugLog("First run. Installing remote build...");
|
|
await installBuild(release);
|
|
} else if (hasUpdate) {
|
|
debugLog("Update available. Prompting user...");
|
|
const tempApp = express();
|
|
let userDecision = null;
|
|
tempApp.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();
|
|
});
|
|
tempApp.get('/update/info', (req, res) => {
|
|
res.json({ available: true, current: localVersion, latest: release.version });
|
|
});
|
|
tempApp.post('/update/yes', (req, res) => { userDecision = true; res.sendStatus(200); });
|
|
tempApp.post('/update/no', (req, res) => { userDecision = false; res.sendStatus(200); });
|
|
const tempServer = tempApp.listen(4891, '127.0.0.1');
|
|
|
|
sendToTauri('update-available', { current: localVersion, latest: release.version });
|
|
|
|
while (userDecision === null) {
|
|
await new Promise(r => setTimeout(r, 500));
|
|
}
|
|
tempServer.close();
|
|
|
|
if (userDecision) {
|
|
debugLog("User accepted update.");
|
|
await installBuild(release);
|
|
} else {
|
|
debugLog("User skipped update.");
|
|
}
|
|
} else {
|
|
debugLog("Already on the latest version.");
|
|
}
|
|
} else if (isFirstRun) {
|
|
debugLog("No remote release found, but it's the first run. Looking for local update-payload.zip...");
|
|
const localZip = path.join(__dirname, 'update-payload.zip');
|
|
if (fs.existsSync(localZip)) {
|
|
sendToTauri('status', { message: 'Extracting initial local payload...' });
|
|
patcher.purgeOldCode();
|
|
patcher.extractZipPayload(localZip);
|
|
patcher.applyPatches(replacementsConfig);
|
|
patcher.bridgeCredentials(serviceManager.credentialManager);
|
|
patcher.saveCurrentVersion("1.0.0");
|
|
debugLog("Local payload extracted successfully.");
|
|
} else {
|
|
debugLog("No local payload found!");
|
|
}
|
|
}
|
|
|
|
if (!patcher.verifyExtraction()) {
|
|
throw new Error('No application files found. Check your internet connection and try again.');
|
|
}
|
|
|
|
sendToTauri('status', { message: 'Loading credentials...' });
|
|
serviceManager.credentialManager.loadCredentials();
|
|
|
|
sendToTauri('status', { message: 'Starting backend services...' });
|
|
await serviceManager.startServices([
|
|
{ name: 'auth-proxy', relativeDir: 'auth-proxy' },
|
|
{ name: 'stream-api', relativeDir: 'stream-api' },
|
|
]);
|
|
debugLog("Backend services spawn requested.");
|
|
|
|
const playerDir = path.join(APPDATA_DIR, 'player');
|
|
playerExpressApp = express();
|
|
playerExpressApp.use(express.static(playerDir, { maxAge: '1d', etag: true }));
|
|
playerExpressApp.get('*', (req, res) => { res.sendFile(path.join(playerDir, 'index.html')); });
|
|
playerExpressServer = playerExpressApp.listen(3001, '0.0.0.0');
|
|
debugLog("Player Express running on 3001");
|
|
|
|
sendToTauri('status', { message: 'Waiting for ports 3000, 3001, 7860...' });
|
|
await serviceManager.waitForPorts([3000, 3001, 7860]);
|
|
debugLog("Ports 3000, 3001, 7860 are ready.");
|
|
|
|
const frontendDir = path.join(APPDATA_DIR, 'frontend');
|
|
expressApp = express();
|
|
expressApp.use(express.static(frontendDir, { maxAge: '1d', etag: true }));
|
|
expressApp.get('*', (req, res) => { res.sendFile(path.join(frontendDir, 'index.html')); });
|
|
|
|
const FRONTEND_PORT = 4890;
|
|
expressServer = expressApp.listen(FRONTEND_PORT, '0.0.0.0', () => {
|
|
debugLog("Frontend Express running on 4890. Sending READY to Tauri.");
|
|
sendToTauri('ready', { url: `http://localhost:${FRONTEND_PORT}` });
|
|
});
|
|
|
|
} catch (error) {
|
|
debugLog(`Fatal Start Error: ${error.message}`);
|
|
sendToTauri('error', { message: error.message });
|
|
}
|
|
}
|
|
|
|
process.on('exit', () => {
|
|
if (serviceManager) {
|
|
for (const name of Object.keys(serviceManager.processes)) {
|
|
const proc = serviceManager.processes[name];
|
|
if (proc && proc.child) {
|
|
try { proc.child.kill('SIGKILL'); } catch (e) { }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
process.on('SIGINT', async () => {
|
|
if (expressServer) expressServer.close();
|
|
if (playerExpressServer) playerExpressServer.close();
|
|
if (serviceManager) await serviceManager.stopAll();
|
|
process.exit(0);
|
|
});
|
|
|
|
start(); |