Files
Home/main.js

179 lines
7.0 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');
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 });
console.log(`[DEBUG] AppData directory: ${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);
process.on('unhandledRejection', (reason) => {
const message = reason && reason.message ? reason.message : String(reason);
console.error(`[ERROR] Unhandled Rejection: ${message}`);
fs.appendFileSync(path.join(APPDATA_DIR, 'crash.log'), `[${new Date().toISOString()}] UNHANDLED REJECTION: ${message}\n`, 'utf8');
});
process.on('uncaughtException', (err) => {
console.error(`[ERROR] Uncaught Exception: ${err.message}`);
fs.appendFileSync(path.join(APPDATA_DIR, 'crash.log'), `[${new Date().toISOString()}] UNCAUGHT EXCEPTION: ${err.message}\n`, 'utf8');
});
function sendToTauri(event, data) {
process.stdout.write(JSON.stringify({ event, data }) + '\n');
}
async function start() {
console.log('[DEBUG] Initializing patcher and service manager...');
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 {
console.log(`[DEBUG] Downloading update, attempt ${attempts}...`);
await updateClient.downloadUpdate(release, tempZipPath, (downloaded, total) => {
const percent = Math.round((downloaded / total) * 100);
sendToTauri('download-progress', { downloaded, total, percent });
});
if (!patcher.verifyChecksum(tempZipPath, release.sha256)) {
throw new Error('SHA-256 checksum verification failed.');
}
console.log('[DEBUG] Extracting payload and applying patches...');
patcher.purgeOldCode();
patcher.extractZipPayload(tempZipPath);
if (!patcher.verifyExtraction()) {
throw new Error('Extraction verification failed: critical startup files missing.');
}
patcher.applyPatches(replacementsConfig);
patcher.bridgeCredentials(serviceManager.credentialManager);
patcher.saveCurrentVersion(release.version, release.sha256);
fs.unlinkSync(tempZipPath);
downloadSuccess = true;
} catch (err) {
if (fs.existsSync(tempZipPath)) fs.unlinkSync(tempZipPath);
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();
console.log(`[DEBUG] Local version: ${localVersion}, isFirstRun: ${isFirstRun}`);
let release = null;
try {
release = await updateClient.checkForUpdates();
} catch (e) {
console.warn('[DEBUG] Could not check for remote updates:', e.message);
}
if (release) {
const hasUpdate = release.version !== localVersion || (release.sha256 && release.sha256 !== localSha);
if (isFirstRun || hasUpdate) {
console.log('[DEBUG] Performing update install from release...');
await installBuild(release);
}
} else if (isFirstRun) {
const localZip = path.join(__dirname, 'update-payload.zip');
console.log(`[DEBUG] Checking local zip at: ${localZip} (exists: ${fs.existsSync(localZip)})`);
if (fs.existsSync(path.join(__dirname, 'update-payload.zip'))) {
const localZip = path.join(__dirname, 'update-payload.zip');
console.log('[DEBUG] Syncing local update-payload.zip to AppData...');
patcher.purgeOldCode();
patcher.extractZipPayload(localZip);
patcher.applyPatches(replacementsConfig);
patcher.bridgeCredentials(serviceManager.credentialManager);
patcher.saveCurrentVersion("1.0.0");
}
}
const verified = patcher.verifyExtraction();
console.log(`[DEBUG] Payload verification status: ${verified}`);
if (!verified) {
throw new Error('No application files found. Build the payload first using "npm run build:payload" or check your internet connection.');
}
console.log('[DEBUG] Loading credentials...');
serviceManager.credentialManager.loadCredentials();
console.log('[DEBUG] Starting auth-proxy and stream-api...');
await serviceManager.startServices([
{ name: 'auth-proxy', relativeDir: 'auth-proxy' },
{ name: 'stream-api', relativeDir: 'stream-api' },
]);
const playerDir = path.join(APPDATA_DIR, 'player');
console.log(`[DEBUG] Starting Player Express server on port 3001 from: ${playerDir}`);
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', () => {
console.log('[DEBUG] Player Express server listening on 3001');
});
console.log('[DEBUG] Waiting for ports [3000, 3001, 7860]...');
await serviceManager.waitForPorts([3000, 3001, 7860]);
console.log('[DEBUG] All ports ready!');
const frontendDir = path.join(APPDATA_DIR, 'frontend');
console.log(`[DEBUG] Starting Frontend Express server on port 4890 from: ${frontendDir}`);
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', () => {
console.log(`[DEBUG] Frontend Express server active at http://localhost:${FRONTEND_PORT}`);
sendToTauri('ready', { url: `http://localhost:${FRONTEND_PORT}` });
});
} catch (error) {
if (serviceManager) {
await serviceManager.stopAll().catch(() => { });
}
console.error('[DEBUG] Fatal error caught:', error.message);
sendToTauri('error', { message: error.message });
}
}
process.on('SIGINT', async () => {
if (expressServer) expressServer.close();
if (playerExpressServer) playerExpressServer.close();
if (serviceManager) await serviceManager.stopAll();
process.exit(0);
});
start();