mirror of
https://gitlab.com/vyla-entertainment/home.git
synced 2026-11-14 14:17:25 -05:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d799da3d2a | |||
| f4aaf2fa7c | |||
| 8ce4edc251 | |||
| bef6fd2326 | |||
| 774f9a5e03 | |||
| 250e7972f2 | |||
| 5082d51ebc | |||
| ed66b894c1 | |||
| 11d6c346e8 | |||
| c986194e64 | |||
| 33e285953c | |||
| f46869f967 | |||
| b19e6b21c9 | |||
| c8ea2598b9 | |||
| e3257b8a3a | |||
| fbb5a4437d | |||
| dbaab9a00e | |||
| fc49bb93a7 | |||
| 9229f0c2dd | |||
| 843abf7df4 | |||
| 27fda0430b | |||
| 8b49d679bd | |||
| 62883b3180 | |||
| 3c6ae0125b | |||
| be0810ea6d | |||
| dff55060e7 | |||
| df767b5ffc | |||
| 1b70c6784a | |||
| e25e1c93f9 | |||
| 4c708ad97c | |||
| b81a4e044b | |||
| f6aa2daa01 | |||
| e03eb38adc | |||
| 2b690b47c1 | |||
| dffbdb92a5 | |||
| 45d6515f2e | |||
| a3e6a278fe | |||
| 7ffc20ee48 | |||
| ddf3abb314 | |||
| ec50c4e07f | |||
| 49b2a7622b | |||
| b8e474cb92 | |||
| 8b29c9e3c7 | |||
| 5dab61b359 | |||
| eab843aa16 | |||
| b757f741b7 | |||
| 5f4c47a07c | |||
| 32677c9fb2 | |||
| 7eab57cfd9 | |||
| c8fc7da64d | |||
| 88c1824bf7 | |||
| a50cb2b6b8 | |||
| 971eae4af1 | |||
| 1400d538f3 | |||
| b1d01d94da | |||
| 9d7e218ff6 | |||
| c19e1e2cf7 | |||
| 6b06bc8170 | |||
| 769c0dadcd | |||
| 414ef73e1d |
1
DOCUEMENTATION/CLEAR.bash
Normal file
1
DOCUEMENTATION/CLEAR.bash
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bash -c 'pkill -9 -x node 2>/dev/null; pkill -9 -x "Vyla Home" 2>/dev/null; pkill -9 -x "vyla-home" 2>/dev/null; rm -rf "$HOME/Library/Application Support/com.vyla.home" "$HOME/Library/Application Support/vyla-home" "$HOME/Library/Application Support/Vyla Home" "$HOME/Library/Caches/com.vyla.home" "$HOME/Library/Caches/vyla-home" "$HOME/Library/Caches/Vyla Home" "$HOME/Library/WebKit/com.vyla.home" "$HOME/Library/WebKit/vyla-home" "$HOME/Library/WebKit/Vyla Home" "$HOME/Library/Logs/Vyla Home" "$HOME/Library/Preferences/com.vyla.home.plist" "$HOME/.vyla-home" "/Applications/Vyla Home.app" "/Applications/vyla-home.app" "$HOME/Applications/Vyla Home.app" "$HOME/Applications/vyla-home.app" "src-tauri/target" "dist-build" "temp-validation-extract" "dist" "frontend" "player" "auth-proxy" "stream-api" "logs" "*.log" "download-temp.zip" "update-payload.zip" "manifest.json" "validation-report.json" "config/encrypted-credentials.json" "config/credentials.json"; echo "Vyla Home cleanup complete."'
|
||||||
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.
|
- **Runs locally** - All services run on your computer for maximum privacy and speed
|
||||||
- **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.
|
- **Manages streaming services** - Handles authentication and streaming APIs automatically
|
||||||
- **Cryptographic Obfuscation**: Client configurations and credentials are secured on-disk via hardware-derived AES-256-GCM encryption.
|
- **Updates automatically** - Keeps itself up to date with the latest features and fixes
|
||||||
- **Automated Integrity Patching**: Payloads are dynamically verified using SHA-256 checksums and patched in-memory via C-level buffer scans.
|
- **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)
|
When you launch Vyla Home, it:
|
||||||
- [Node.js](https://nodejs.org/) (v20+ LTS)
|
|
||||||
- Tauri CLI (`npm install -g @tauri-apps/cli`)
|
|
||||||
|
|
||||||
## 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.
|
Everything happens on your machine - your data never leaves your computer except when communicating directly with streaming services.
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
|
|
||||||
npm run build:payload
|
|
||||||
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
@@ -5,7 +5,7 @@ const AdmZip = require('adm-zip');
|
|||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
const BUILD_DIR = path.join(__dirname, 'dist-build');
|
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');
|
const TEMP_EXTRACT_DIR = path.join(__dirname, 'temp-validation-extract');
|
||||||
|
|
||||||
@@ -14,24 +14,25 @@ const GITLAB_GROUP = 'vyla-entertainment';
|
|||||||
const REPO_CONFIG = {
|
const REPO_CONFIG = {
|
||||||
'frontend': { name: 'frontend', branch: 'main' },
|
'frontend': { name: 'frontend', branch: 'main' },
|
||||||
'player': { name: 'player', 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();
|
||||||
|
|
||||||
function clonePrivateRepo(repoConfig, targetDir) {
|
function clonePrivateRepo(repoConfig, targetDir) {
|
||||||
|
if (fs.existsSync(targetDir)) {
|
||||||
|
console.log(`Removing stale ${repoConfig.name} before fresh clone...`);
|
||||||
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
const token = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN;
|
const token = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN;
|
||||||
const auth = token ? `oauth2:${token}@` : '';
|
const auth = token ? `oauth2:${token}@` : '';
|
||||||
const repoUrl = `https://${auth}gitlab.com/${GITLAB_GROUP}/${repoConfig.name}.git`;
|
const repoUrl = `https://${auth}gitlab.com/${GITLAB_GROUP}/${repoConfig.name}.git`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(targetDir)) {
|
|
||||||
fs.rmSync(targetDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
||||||
}
|
|
||||||
|
|
||||||
execSync(`git clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} ${targetDir}`, {
|
execSync(`git clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} ${targetDir}`, {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit'
|
||||||
});
|
});
|
||||||
|
clonedRepos.add(repoConfig.name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to clone ${repoConfig.name}: ${error.message}`);
|
throw new Error(`Failed to clone ${repoConfig.name}: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -171,12 +172,10 @@ function validatePayload(zipPath) {
|
|||||||
zip.extractAllTo(TEMP_EXTRACT_DIR, true);
|
zip.extractAllTo(TEMP_EXTRACT_DIR, true);
|
||||||
|
|
||||||
const requiredFiles = [
|
const requiredFiles = [
|
||||||
'player/index.html',
|
'player/server.js',
|
||||||
'auth-proxy/server.js',
|
|
||||||
'stream-api/server.js',
|
|
||||||
'frontend/index.html'
|
'frontend/index.html'
|
||||||
];
|
];
|
||||||
|
|
||||||
const validationResults = {
|
const validationResults = {
|
||||||
valid: true,
|
valid: true,
|
||||||
errors: [],
|
errors: [],
|
||||||
@@ -332,9 +331,11 @@ async function build() {
|
|||||||
fs.writeFileSync(path.join(__dirname, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
fs.writeFileSync(path.join(__dirname, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
||||||
|
|
||||||
for (const repo of REPOS) {
|
for (const repo of REPOS) {
|
||||||
const repoPath = path.join(__dirname, repo);
|
if (clonedRepos.has(repo)) {
|
||||||
if (fs.existsSync(repoPath)) {
|
const repoPath = path.join(__dirname, repo);
|
||||||
fs.rmSync(repoPath, { recursive: true, force: true });
|
if (fs.existsSync(repoPath)) {
|
||||||
|
fs.rmSync(repoPath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
codemagic.yaml
Normal file
46
codemagic.yaml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
workflows:
|
||||||
|
macos-release:
|
||||||
|
name: Build macOS Release
|
||||||
|
max_build_duration: 60
|
||||||
|
instance_type: mac_mini_m2
|
||||||
|
environment:
|
||||||
|
groups:
|
||||||
|
- gitlab_credentials
|
||||||
|
node: 22.14.0
|
||||||
|
cache:
|
||||||
|
cache_paths:
|
||||||
|
- $HOME/.cargo/registry
|
||||||
|
- $HOME/.cargo/git
|
||||||
|
- src-tauri/target
|
||||||
|
scripts:
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
script: |
|
||||||
|
if ! command -v cargo &> /dev/null; then
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
fi
|
||||||
|
- name: Install dependencies
|
||||||
|
script: |
|
||||||
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
npm install
|
||||||
|
- name: Build & Publish macOS Release
|
||||||
|
script: |
|
||||||
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
||||||
|
node release.js --installer --tag="v$PACKAGE_VERSION"
|
||||||
|
- name: Verify bundled node permissions
|
||||||
|
script: |
|
||||||
|
if [ -f "bin/node" ]; then
|
||||||
|
chmod +x bin/node
|
||||||
|
xattr -cr bin/node || true
|
||||||
|
ls -la bin/node
|
||||||
|
file bin/node
|
||||||
|
fi
|
||||||
|
APP_NODE=$(find src-tauri/target/release/bundle -name "node" -path "*bin*" 2>/dev/null | head -1)
|
||||||
|
if [ -n "$APP_NODE" ]; then
|
||||||
|
chmod +x "$APP_NODE"
|
||||||
|
xattr -cr "$APP_NODE" || true
|
||||||
|
ls -la "$APP_NODE"
|
||||||
|
fi
|
||||||
|
artifacts:
|
||||||
|
- src-tauri/target/release/bundle/dmg/*.dmg
|
||||||
|
- src-tauri/target/release/bundle/macos/*.app
|
||||||
@@ -1,16 +1,5 @@
|
|||||||
{
|
{
|
||||||
"player": {
|
"player": {
|
||||||
"TMDB_KEY": "YOUR_TMDB_KEY_HERE",
|
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,5 @@
|
|||||||
{
|
{
|
||||||
"player": {
|
"player": {
|
||||||
"PORT": "3001",
|
"PORT": "3000"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,40 +2,17 @@
|
|||||||
"frontend": [
|
"frontend": [
|
||||||
{
|
{
|
||||||
"find": "https://player.vyla.cc",
|
"find": "https://player.vyla.cc",
|
||||||
"replace": "http://localhost:3001"
|
"replace": "http://localhost:3000"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"find": "http://player.vyla.cc",
|
"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"
|
"replace": "http://localhost:3000"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"player": [
|
"player": [
|
||||||
{
|
|
||||||
"find": "https://api.vyla.cc",
|
|
||||||
"replace": "http://localhost:7860"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"find": "https://auth.vyla.cc",
|
|
||||||
"replace": "http://localhost:3000"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"find": "https://player.vyla.cc",
|
"find": "https://player.vyla.cc",
|
||||||
"replace": "http://localhost:4890"
|
"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 = {
|
const REPOS = {
|
||||||
frontend: { branch: 'main' },
|
frontend: { branch: 'main' },
|
||||||
player: { branch: 'main' },
|
player: { branch: 'main' }
|
||||||
'stream-api': { branch: 'main' },
|
|
||||||
'auth-proxy': { branch: 'main' }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const GITLAB_GROUP = 'vyla-entertainment';
|
const GITLAB_GROUP = 'vyla-entertainment';
|
||||||
|
|||||||
202
main.js
202
main.js
@@ -6,10 +6,19 @@ const ServiceManager = require('./service-manager');
|
|||||||
const Patcher = require('./patcher');
|
const Patcher = require('./patcher');
|
||||||
const UpdateClient = require('./update-client');
|
const UpdateClient = require('./update-client');
|
||||||
|
|
||||||
const defaultAppData = path.join(os.homedir(), 'AppData', 'Roaming', 'vyla-home', 'vyla-home-files');
|
function getDefaultAppData() {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
return path.join(os.homedir(), 'Library', 'Application Support', 'vyla-home', 'vyla-home-files');
|
||||||
|
}
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
return path.join(os.homedir(), '.local', 'share', 'vyla-home', 'vyla-home-files');
|
||||||
|
}
|
||||||
|
return path.join(os.homedir(), 'AppData', 'Roaming', 'vyla-home', 'vyla-home-files');
|
||||||
|
}
|
||||||
|
|
||||||
const APPDATA_DIR = (process.env.APPDATA_PATH && !process.env.APPDATA_PATH.includes('com.vyla.home'))
|
const APPDATA_DIR = (process.env.APPDATA_PATH && !process.env.APPDATA_PATH.includes('com.vyla.home'))
|
||||||
? process.env.APPDATA_PATH
|
? process.env.APPDATA_PATH
|
||||||
: defaultAppData;
|
: getDefaultAppData();
|
||||||
|
|
||||||
const LOGS_DIR = path.join(APPDATA_DIR, 'logs');
|
const LOGS_DIR = path.join(APPDATA_DIR, 'logs');
|
||||||
try { fs.mkdirSync(LOGS_DIR, { recursive: true }); } catch (e) { }
|
try { fs.mkdirSync(LOGS_DIR, { recursive: true }); } catch (e) { }
|
||||||
@@ -25,8 +34,6 @@ debugLog("--- Node.js Process Started ---");
|
|||||||
let serviceManager;
|
let serviceManager;
|
||||||
let expressApp;
|
let expressApp;
|
||||||
let expressServer;
|
let expressServer;
|
||||||
let playerExpressApp;
|
|
||||||
let playerExpressServer;
|
|
||||||
|
|
||||||
debugLog(`AppData Dir: ${APPDATA_DIR}`);
|
debugLog(`AppData Dir: ${APPDATA_DIR}`);
|
||||||
|
|
||||||
@@ -84,11 +91,20 @@ cmdApp.get('/__tauri_cmd/:cmd', (req, res) => {
|
|||||||
});
|
});
|
||||||
cmdApp.listen(4892, '127.0.0.1', () => debugLog("[CMD-SERVER] Dedicated command server listening on 127.0.0.1:4892"));
|
cmdApp.listen(4892, '127.0.0.1', () => debugLog("[CMD-SERVER] Dedicated command server listening on 127.0.0.1:4892"));
|
||||||
|
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('end', () => {
|
||||||
|
debugLog('stdin closed - parent died. Self-terminating...');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
process.stdin.on('error', () => {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
debugLog(`CRITICAL UNCAUGHT EXCEPTION: ${err.stack || err.message}`);
|
debugLog(`CRITICAL UNCAUGHT EXCEPTION: ${err.stack || err.message}`);
|
||||||
if (err.code === 'EPIPE') {
|
if (err.code === 'EPIPE' || err.code === 'EOF') {
|
||||||
debugLog('Pipe to parent closed - parent process gone, exiting');
|
debugLog('Pipe to parent closed - parent process gone, exiting');
|
||||||
process.exit(1);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
try { sendToTauri('error', { message: err.message }); } catch (e) { }
|
try { sendToTauri('error', { message: err.message }); } catch (e) { }
|
||||||
});
|
});
|
||||||
@@ -126,6 +142,9 @@ async function start() {
|
|||||||
|
|
||||||
const installBuild = async (release) => {
|
const installBuild = async (release) => {
|
||||||
const tempZipPath = path.join(APPDATA_DIR, 'download-temp.zip');
|
const tempZipPath = path.join(APPDATA_DIR, 'download-temp.zip');
|
||||||
|
if (fs.existsSync(tempZipPath)) {
|
||||||
|
try { fs.unlinkSync(tempZipPath); } catch (e) { }
|
||||||
|
}
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
let downloadSuccess = false;
|
let downloadSuccess = false;
|
||||||
|
|
||||||
@@ -135,9 +154,13 @@ async function start() {
|
|||||||
debugLog(`Attempting to download update (Attempt ${attempts})...`);
|
debugLog(`Attempting to download update (Attempt ${attempts})...`);
|
||||||
sendToTauri('status', { message: `Connecting to download servers (Attempt ${attempts}/3)...` });
|
sendToTauri('status', { message: `Connecting to download servers (Attempt ${attempts}/3)...` });
|
||||||
|
|
||||||
|
let lastPercent = -1;
|
||||||
await updateClient.downloadUpdate(release, tempZipPath, (downloaded, total) => {
|
await updateClient.downloadUpdate(release, tempZipPath, (downloaded, total) => {
|
||||||
const percent = total > 0 ? Math.round((downloaded / total) * 100) : 0;
|
const percent = total > 0 ? Math.round((downloaded / total) * 100) : 0;
|
||||||
sendToTauri('download-progress', { downloaded, total, percent });
|
if (percent !== lastPercent) {
|
||||||
|
lastPercent = percent;
|
||||||
|
sendToTauri('download-progress', { downloaded, total, percent });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
debugLog('Download complete. Verifying checksum...');
|
debugLog('Download complete. Verifying checksum...');
|
||||||
@@ -187,10 +210,58 @@ async function start() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
sendToTauri('status', { message: 'Inspecting local workspace and installed files...' });
|
sendToTauri('status', { message: 'Inspecting local workspace and installed files...' });
|
||||||
const isFirstRun = !patcher.verifyExtraction();
|
let isFirstRun = !patcher.verifyExtraction();
|
||||||
const localVersion = patcher.getCurrentVersion();
|
let localVersion = patcher.getCurrentVersion();
|
||||||
const localSha = patcher.getCurrentSha();
|
let localSha = patcher.getCurrentSha();
|
||||||
debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}`);
|
debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}, Local SHA: ${localSha}`);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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...' });
|
sendToTauri('status', { message: 'Connecting to update servers...' });
|
||||||
let release = null;
|
let release = null;
|
||||||
@@ -198,18 +269,43 @@ async function start() {
|
|||||||
debugLog("Fetching updates from updateClient...");
|
debugLog("Fetching updates from updateClient...");
|
||||||
release = await updateClient.checkForUpdates();
|
release = await updateClient.checkForUpdates();
|
||||||
debugLog(`Update check finished. Found: ${release ? release.version : 'None'}`);
|
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) {
|
} catch (e) {
|
||||||
debugLog(`Update check failed (offline?): ${e.message}`);
|
debugLog(`Update check failed (offline?): ${e.message}`);
|
||||||
sendToTauri('status', { message: `Update server check skipped (${e.message})` });
|
sendToTauri('status', { message: `Update server check skipped (${e.message})` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeVer = (v) => (v || '').toString().replace(/^v/, '').trim();
|
||||||
|
|
||||||
if (release) {
|
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) {
|
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...' });
|
sendToTauri('status', { message: 'First run detected. Preparing initial installation...' });
|
||||||
await installBuild(release);
|
await installBuild(release);
|
||||||
} else if (hasUpdate) {
|
} else if (hasUpdate) {
|
||||||
|
|
||||||
debugLog("Update available. Prompting user...");
|
debugLog("Update available. Prompting user...");
|
||||||
sendToTauri('status', { message: `New update available (${release.version}). Awaiting confirmation...` });
|
sendToTauri('status', { message: `New update available (${release.version}). Awaiting confirmation...` });
|
||||||
|
|
||||||
@@ -261,7 +357,14 @@ async function start() {
|
|||||||
patcher.extractZipPayload(localZip);
|
patcher.extractZipPayload(localZip);
|
||||||
patcher.applyPatches(getReplacementsConfig());
|
patcher.applyPatches(getReplacementsConfig());
|
||||||
patcher.bridgeCredentials(serviceManager.credentialManager);
|
patcher.bridgeCredentials(serviceManager.credentialManager);
|
||||||
patcher.saveCurrentVersion("1.0.0");
|
|
||||||
|
let ver = "1.0.0";
|
||||||
|
try {
|
||||||
|
const man = JSON.parse(fs.readFileSync(path.join(__dirname, 'manifest.json'), 'utf8'));
|
||||||
|
ver = man.version || ver;
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
patcher.saveCurrentVersion(ver);
|
||||||
serviceManager.envConfig = getEnvConfig();
|
serviceManager.envConfig = getEnvConfig();
|
||||||
debugLog("Local payload extracted successfully.");
|
debugLog("Local payload extracted successfully.");
|
||||||
} else {
|
} else {
|
||||||
@@ -271,51 +374,21 @@ async function start() {
|
|||||||
|
|
||||||
sendToTauri('status', { message: 'Validating critical components...' });
|
sendToTauri('status', { message: 'Validating critical components...' });
|
||||||
if (!patcher.verifyExtraction()) {
|
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...' });
|
sendToTauri('status', { message: 'Loading encrypted credentials...' });
|
||||||
serviceManager.credentialManager.loadCredentials();
|
serviceManager.credentialManager.loadCredentials();
|
||||||
|
|
||||||
sendToTauri('status', { message: 'Starting background services (auth-proxy, stream-api)...' });
|
sendToTauri('status', { message: 'Starting background services (player)...' });
|
||||||
await serviceManager.startServices([
|
await serviceManager.startServices([
|
||||||
{ name: 'auth-proxy', relativeDir: 'auth-proxy' },
|
{ name: 'player', relativeDir: 'player', startScript: 'server.js' }
|
||||||
{ name: 'stream-api', relativeDir: 'stream-api' },
|
|
||||||
]);
|
]);
|
||||||
debugLog("Backend services spawn requested.");
|
debugLog("Backend services spawn requested.");
|
||||||
|
|
||||||
sendToTauri('status', { message: 'Binding player web server on port 3001...' });
|
sendToTauri('status', { message: 'Waiting for microservices to bind port 3000...' });
|
||||||
const playerDir = path.join(APPDATA_DIR, 'player');
|
await serviceManager.waitForPorts([3000]);
|
||||||
playerExpressApp = express();
|
debugLog("Port 3000 is ready.");
|
||||||
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: 'Binding UI server on port 4890...' });
|
sendToTauri('status', { message: 'Binding UI server on port 4890...' });
|
||||||
const frontendDir = path.join(APPDATA_DIR, 'frontend');
|
const frontendDir = path.join(APPDATA_DIR, 'frontend');
|
||||||
@@ -340,6 +413,12 @@ async function start() {
|
|||||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
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) => {
|
expressApp.get('*', (req, res) => {
|
||||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
||||||
res.sendFile(path.join(frontendDir, 'index.html'));
|
res.sendFile(path.join(frontendDir, 'index.html'));
|
||||||
@@ -347,11 +426,8 @@ async function start() {
|
|||||||
|
|
||||||
const FRONTEND_PORT = 4890;
|
const FRONTEND_PORT = 4890;
|
||||||
expressApp.use('/build', express.static(path.join(__dirname, 'build')));
|
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, '127.0.0.1', () => {
|
expressServer = expressApp.listen(FRONTEND_PORT, () => {
|
||||||
sendToTauri('status', { message: 'All backend services running and healthy.' });
|
sendToTauri('status', { message: 'All backend services running and healthy.' });
|
||||||
debugLog("Frontend Express running on 4890. Sending READY to Tauri.");
|
debugLog("Frontend Express running on 4890. Sending READY to Tauri.");
|
||||||
sendToTauri('ready', { url: `http://localhost:${FRONTEND_PORT}` });
|
sendToTauri('ready', { url: `http://localhost:${FRONTEND_PORT}` });
|
||||||
@@ -367,17 +443,23 @@ process.on('exit', () => {
|
|||||||
if (serviceManager) {
|
if (serviceManager) {
|
||||||
for (const name of Object.keys(serviceManager.processes)) {
|
for (const name of Object.keys(serviceManager.processes)) {
|
||||||
const proc = serviceManager.processes[name];
|
const proc = serviceManager.processes[name];
|
||||||
if (proc && proc.child) {
|
if (proc && proc.child && proc.child.pid) {
|
||||||
try { proc.child.kill('SIGKILL'); } catch (e) { }
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
require('child_process').spawnSync('taskkill', ['/F', '/T', '/PID', proc.child.pid.toString()], { stdio: 'ignore', windowsHide: true });
|
||||||
|
} else {
|
||||||
|
process.kill(-proc.child.pid, 'SIGKILL');
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGINT', async () => {
|
process.on('SIGINT', () => {
|
||||||
if (expressServer) expressServer.close();
|
process.exit(0);
|
||||||
if (playerExpressServer) playerExpressServer.close();
|
});
|
||||||
if (serviceManager) await serviceManager.stopAll();
|
process.on('SIGTERM', () => {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vyla-home",
|
"name": "vyla-home",
|
||||||
"version": "1.1.9",
|
"version": "1.1.33",
|
||||||
"description": "Vyla Home Desktop App Launcher",
|
"description": "Vyla Home Desktop App Launcher",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -54,4 +54,4 @@
|
|||||||
"installerHeaderIcon": "build/icon.ico"
|
"installerHeaderIcon": "build/icon.ico"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
patcher.js
25
patcher.js
@@ -63,17 +63,24 @@ class Patcher {
|
|||||||
const fullPath = path.join(this.appDataPath, dir);
|
const fullPath = path.join(this.appDataPath, dir);
|
||||||
if (fs.existsSync(fullPath)) {
|
if (fs.existsSync(fullPath)) {
|
||||||
try {
|
try {
|
||||||
fs.rmSync(fullPath, { recursive: true, force: true });
|
const trashPath = path.join(this.appDataPath, `.trash_${dir}_${Date.now()}`);
|
||||||
|
fs.renameSync(fullPath, trashPath);
|
||||||
|
fs.rmSync(trashPath, { recursive: true, force: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
const trashPath = fullPath + '.trash.' + Date.now();
|
fs.rmSync(fullPath, { recursive: true, force: true });
|
||||||
fs.renameSync(fullPath, trashPath);
|
} catch (err2) { }
|
||||||
} catch (renameErr) {
|
|
||||||
console.warn(`Could not remove or rename ${fullPath}. It will be overwritten.`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const files = fs.readdirSync(this.appDataPath);
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.startsWith('.trash_')) {
|
||||||
|
fs.rmSync(path.join(this.appDataPath, file), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
extractZipPayload(zipPath) {
|
extractZipPayload(zipPath) {
|
||||||
@@ -92,9 +99,7 @@ class Patcher {
|
|||||||
|
|
||||||
verifyExtraction() {
|
verifyExtraction() {
|
||||||
const requiredFiles = [
|
const requiredFiles = [
|
||||||
'player/index.html',
|
'player/server.js',
|
||||||
'auth-proxy/server.js',
|
|
||||||
'stream-api/server.js',
|
|
||||||
'frontend/index.html'
|
'frontend/index.html'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -148,7 +153,7 @@ class Patcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyPatches(replacements) {
|
applyPatches(replacements) {
|
||||||
const targetDirs = ['frontend', 'player', 'auth-proxy', 'stream-api'];
|
const targetDirs = ['frontend', 'player'];
|
||||||
for (const dirName of targetDirs) {
|
for (const dirName of targetDirs) {
|
||||||
const dirPath = path.join(this.appDataPath, dirName);
|
const dirPath = path.join(this.appDataPath, dirName);
|
||||||
if (!fs.existsSync(dirPath)) continue;
|
if (!fs.existsSync(dirPath)) continue;
|
||||||
|
|||||||
169
release.js
169
release.js
@@ -78,12 +78,12 @@ function uploadFileToGitLabPackage(filePath, packageName, version, fileName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createGitLabRelease(tag, releaseLinks) {
|
function createGitLabRelease(tag, releaseLinks) {
|
||||||
const sendRequest = (method, path, data = null) => {
|
const sendRequest = (method, reqPath, data = null) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const options = {
|
const options = {
|
||||||
hostname: 'gitlab.com',
|
hostname: 'gitlab.com',
|
||||||
port: 443,
|
port: 443,
|
||||||
path: path,
|
path: reqPath,
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
'PRIVATE-TOKEN': TOKEN,
|
'PRIVATE-TOKEN': TOKEN,
|
||||||
@@ -101,19 +101,9 @@ function createGitLabRelease(tag, releaseLinks) {
|
|||||||
res.on('data', chunk => body += chunk);
|
res.on('data', chunk => body += chunk);
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
try {
|
try { resolve(body ? JSON.parse(body) : {}); } catch (e) { resolve({}); }
|
||||||
resolve(body ? JSON.parse(body) : {});
|
|
||||||
} catch (e) {
|
|
||||||
resolve({});
|
|
||||||
}
|
|
||||||
} else if (res.statusCode === 409 && method === 'POST') {
|
|
||||||
console.log(`Release ${tag} already exists, deleting and recreating...`);
|
|
||||||
sendRequest('DELETE', `/api/v4/projects/${PROJECT_ID}/releases/${encodeURIComponent(tag)}`)
|
|
||||||
.then(() => sendRequest('POST', `/api/v4/projects/${PROJECT_ID}/releases`, data))
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`Failed to publish release (HTTP ${res.statusCode}): ${body}`));
|
reject(new Error(`HTTP ${res.statusCode}: ${body}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -133,13 +123,37 @@ function createGitLabRelease(tag, releaseLinks) {
|
|||||||
name: `Vyla Home Release ${tag}`,
|
name: `Vyla Home Release ${tag}`,
|
||||||
tag_name: tag,
|
tag_name: tag,
|
||||||
ref: currentRef,
|
ref: currentRef,
|
||||||
description: `Automated local release ${tag}`,
|
description: `Automated Multi-Platform Release ${tag}`,
|
||||||
assets: {
|
assets: {
|
||||||
links: releaseLinks
|
links: releaseLinks
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return sendRequest('POST', `/api/v4/projects/${PROJECT_ID}/releases`, releaseData);
|
return sendRequest('POST', `/api/v4/projects/${PROJECT_ID}/releases`, releaseData)
|
||||||
|
.catch(err => {
|
||||||
|
if (err.message.includes('HTTP 409')) {
|
||||||
|
console.log(`[CI Sync] Release ${tag} already exists. Appending our OS-specific files to it...`);
|
||||||
|
let promiseChain = Promise.resolve();
|
||||||
|
|
||||||
|
for (const link of releaseLinks) {
|
||||||
|
promiseChain = promiseChain.then(() => {
|
||||||
|
return sendRequest('POST', `/api/v4/projects/${PROJECT_ID}/releases/${encodeURIComponent(tag)}/assets/links`, {
|
||||||
|
name: link.name,
|
||||||
|
url: link.url,
|
||||||
|
link_type: link.link_type
|
||||||
|
}).catch(e => {
|
||||||
|
if (e.message.includes('already been taken') || e.message.includes('HTTP 409') || e.message.includes('HTTP 400')) {
|
||||||
|
console.log(`[CI Sync] Asset link '${link.name}' is already attached. Skipping.`);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return promiseChain.then(() => ({ name: `Vyla Home Release ${tag}`, tag_name: tag }));
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -166,7 +180,6 @@ async function main() {
|
|||||||
if (isFullBuild) {
|
if (isFullBuild) {
|
||||||
console.log('Ensuring bundled Node.js runtime...');
|
console.log('Ensuring bundled Node.js runtime...');
|
||||||
await ensureBundledNode();
|
await ensureBundledNode();
|
||||||
|
|
||||||
await ensureWebView2Loader();
|
await ensureWebView2Loader();
|
||||||
|
|
||||||
const bundleDir = path.join(__dirname, 'src-tauri', 'target', 'release', 'bundle');
|
const bundleDir = path.join(__dirname, 'src-tauri', 'target', 'release', 'bundle');
|
||||||
@@ -174,20 +187,32 @@ async function main() {
|
|||||||
fs.rmSync(bundleDir, { recursive: true, force: true });
|
fs.rmSync(bundleDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Compiling Windows Tauri installer locally...');
|
console.log('Compiling native Tauri application...');
|
||||||
execSync('npm run build', {
|
execSync('npm run build', { stdio: 'inherit' });
|
||||||
stdio: 'inherit'
|
|
||||||
});
|
|
||||||
|
|
||||||
const nsisDir = path.join(__dirname, 'src-tauri/target/release/bundle/nsis');
|
const targetVersion = version.startsWith('v') ? version.substring(1) : version;
|
||||||
if (fs.existsSync(nsisDir)) {
|
|
||||||
const targetVersion = version.startsWith('v') ? version.substring(1) : version;
|
|
||||||
const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe') && f.includes(targetVersion));
|
|
||||||
|
|
||||||
if (files.length > 0) {
|
if (process.platform === 'win32') {
|
||||||
const originalName = files[0];
|
const nsisDir = path.join(__dirname, 'src-tauri', 'target', 'release', 'bundle', 'nsis');
|
||||||
installerPath = path.join(nsisDir, originalName);
|
if (fs.existsSync(nsisDir)) {
|
||||||
installerUploadName = originalName.replace(/\s+/g, '-');
|
const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe') && f.includes(targetVersion));
|
||||||
|
if (files.length > 0) {
|
||||||
|
installerPath = path.join(nsisDir, files[0]);
|
||||||
|
installerUploadName = files[0].replace(/\s+/g, '-');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const dmgDir = path.join(__dirname, 'src-tauri', 'target', 'release', 'bundle', 'dmg');
|
||||||
|
console.log(`Checking for Mac installer in: ${dmgDir}`);
|
||||||
|
if (fs.existsSync(dmgDir)) {
|
||||||
|
const files = fs.readdirSync(dmgDir).filter(f => f.endsWith('.dmg') && f.includes(targetVersion));
|
||||||
|
if (files.length > 0) {
|
||||||
|
installerPath = path.join(dmgDir, files[0]);
|
||||||
|
installerUploadName = files[0].replace(/\s+/g, '-');
|
||||||
|
console.log(`Found Mac installer: ${installerUploadName}`);
|
||||||
|
} else {
|
||||||
|
console.log(`[WARNING] No .dmg found containing version ${targetVersion}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,19 +233,25 @@ async function main() {
|
|||||||
|
|
||||||
if (installerPath && installerUploadName) {
|
if (installerPath && installerUploadName) {
|
||||||
console.log(`Uploading installer ${installerUploadName}...`);
|
console.log(`Uploading installer ${installerUploadName}...`);
|
||||||
const installerUnique = installerUploadName.replace('.exe', `-${timestamp}.exe`);
|
const ext = path.extname(installerUploadName);
|
||||||
|
const installerUnique = installerUploadName.replace(ext, `-${timestamp}${ext}`);
|
||||||
const installerUrl = await uploadFileToGitLabPackage(installerPath, 'vyla-home', version, installerUnique);
|
const installerUrl = await uploadFileToGitLabPackage(installerPath, 'vyla-home', version, installerUnique);
|
||||||
releaseLinks.push({ name: installerUploadName, url: installerUrl, link_type: 'package' });
|
releaseLinks.push({ name: installerUploadName, url: installerUrl, link_type: 'package' });
|
||||||
|
} else {
|
||||||
|
console.log(`[WARNING] No installer was selected for upload! (isFullBuild: ${isFullBuild})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[3/4] Publishing GitLab Release...');
|
console.log('[3/4] Publishing GitLab Release...');
|
||||||
const release = await createGitLabRelease(version, releaseLinks);
|
await createGitLabRelease(version, releaseLinks);
|
||||||
console.log(`[4/4] Release successfully created: ${release.name} (${release.tag_name})`);
|
console.log(`[4/4] Release process successfully completed for ${version}!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureBundledNode() {
|
function ensureBundledNode() {
|
||||||
|
const os = require('os');
|
||||||
const binDir = path.join(__dirname, 'bin');
|
const binDir = path.join(__dirname, 'bin');
|
||||||
const nodeExePath = path.join(binDir, 'node.exe');
|
const isWin = os.platform() === 'win32';
|
||||||
|
const nodeExeName = isWin ? 'node.exe' : 'node';
|
||||||
|
const nodeExePath = path.join(binDir, nodeExeName);
|
||||||
|
|
||||||
const nodeVersion = 'v22.14.0';
|
const nodeVersion = 'v22.14.0';
|
||||||
const versionMarker = path.join(binDir, `node-${nodeVersion}.marker`);
|
const versionMarker = path.join(binDir, `node-${nodeVersion}.marker`);
|
||||||
@@ -232,35 +263,65 @@ function ensureBundledNode() {
|
|||||||
fs.mkdirSync(binDir, { recursive: true });
|
fs.mkdirSync(binDir, { recursive: true });
|
||||||
console.log(`Downloading standalone Node.js ${nodeVersion} runtime for bundling...`);
|
console.log(`Downloading standalone Node.js ${nodeVersion} runtime for bundling...`);
|
||||||
|
|
||||||
const nodeUrl = `https://nodejs.org/dist/${nodeVersion}/win-x64/node.exe`;
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const fileStream = fs.createWriteStream(nodeExePath);
|
const download = (url, dest, cb) => {
|
||||||
https.get(nodeUrl, (res) => {
|
https.get(url, (res) => {
|
||||||
if (res.statusCode === 302 || res.statusCode === 301) {
|
if (res.statusCode === 302 || res.statusCode === 301) {
|
||||||
https.get(res.headers.location, (redirectRes) => {
|
return download(res.headers.location, dest, cb);
|
||||||
redirectRes.pipe(fileStream);
|
}
|
||||||
fileStream.on('finish', () => {
|
const fileStream = fs.createWriteStream(dest);
|
||||||
fileStream.close();
|
res.pipe(fileStream);
|
||||||
fs.writeFileSync(versionMarker, nodeVersion, 'utf8');
|
fileStream.on('finish', () => {
|
||||||
resolve();
|
fileStream.close();
|
||||||
});
|
cb();
|
||||||
}).on('error', reject);
|
});
|
||||||
return;
|
}).on('error', reject);
|
||||||
}
|
};
|
||||||
res.pipe(fileStream);
|
|
||||||
fileStream.on('finish', () => {
|
if (isWin) {
|
||||||
fileStream.close();
|
const nodeUrl = `https://nodejs.org/dist/${nodeVersion}/win-x64/node.exe`;
|
||||||
|
download(nodeUrl, nodeExePath, () => {
|
||||||
fs.writeFileSync(versionMarker, nodeVersion, 'utf8');
|
fs.writeFileSync(versionMarker, nodeVersion, 'utf8');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}).on('error', (err) => {
|
} else {
|
||||||
try { fs.unlinkSync(nodeExePath); } catch (e) { }
|
const arch = os.arch() === 'arm64' ? 'arm64' : 'x64';
|
||||||
reject(err);
|
const platform = os.platform() === 'darwin' ? 'darwin' : 'linux';
|
||||||
});
|
const tarName = `node-${nodeVersion}-${platform}-${arch}`;
|
||||||
|
const nodeUrl = `https://nodejs.org/dist/${nodeVersion}/${tarName}.tar.gz`;
|
||||||
|
const tarPath = path.join(binDir, 'node.tar.gz');
|
||||||
|
|
||||||
|
download(nodeUrl, tarPath, () => {
|
||||||
|
try {
|
||||||
|
execSync(`tar -xzf node.tar.gz ${tarName}/bin/node`, { cwd: binDir });
|
||||||
|
|
||||||
|
const extractedNode = path.join(binDir, tarName, 'bin', 'node');
|
||||||
|
const finalNode = path.join(binDir, 'node');
|
||||||
|
|
||||||
|
fs.renameSync(extractedNode, finalNode);
|
||||||
|
fs.rmSync(path.join(binDir, tarName), { recursive: true, force: true });
|
||||||
|
fs.unlinkSync(tarPath);
|
||||||
|
|
||||||
|
fs.chmodSync(finalNode, 0o755);
|
||||||
|
|
||||||
|
try {
|
||||||
|
execSync(`xattr -d com.apple.quarantine "${finalNode}"`);
|
||||||
|
} catch (_) { }
|
||||||
|
|
||||||
|
fs.writeFileSync(versionMarker, nodeVersion, 'utf8');
|
||||||
|
resolve();
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureWebView2Loader() {
|
function ensureWebView2Loader() {
|
||||||
|
const os = require('os');
|
||||||
|
if (os.platform() !== 'win32') return Promise.resolve();
|
||||||
|
|
||||||
const releaseDir = path.join(__dirname, 'src-tauri', 'target', 'release');
|
const releaseDir = path.join(__dirname, 'src-tauri', 'target', 'release');
|
||||||
const rootDllPath = path.join(__dirname, 'src-tauri', 'WebView2Loader.dll');
|
const rootDllPath = path.join(__dirname, 'src-tauri', 'WebView2Loader.dll');
|
||||||
fs.mkdirSync(releaseDir, { recursive: true });
|
fs.mkdirSync(releaseDir, { recursive: true });
|
||||||
|
|||||||
@@ -40,18 +40,16 @@ class ServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tokenSecret = process.env.TOKEN_SECRET ||
|
let tokenSecret = process.env.TOKEN_SECRET ||
|
||||||
process.env.STREAM_API_TOKEN_SECRET ||
|
this.credentialManager.getCredential('player', 'TOKEN_SECRET');
|
||||||
this.credentialManager.getCredential('stream-api', 'TOKEN_SECRET');
|
|
||||||
|
|
||||||
if (!tokenSecret || tokenSecret.length < 32) {
|
if (!tokenSecret || tokenSecret.length < 32) {
|
||||||
tokenSecret = require('crypto').randomBytes(32).toString('hex');
|
tokenSecret = require('crypto').randomBytes(32).toString('hex');
|
||||||
this.credentialManager.setCredential('stream-api', 'TOKEN_SECRET', tokenSecret);
|
this.credentialManager.setCredential('player', 'TOKEN_SECRET', tokenSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
const env = {
|
const env = {
|
||||||
...process.env,
|
...process.env,
|
||||||
TOKEN_SECRET: tokenSecret,
|
TOKEN_SECRET: tokenSecret,
|
||||||
STREAM_API_TOKEN_SECRET: tokenSecret,
|
|
||||||
...(this.envConfig[name] || {}),
|
...(this.envConfig[name] || {}),
|
||||||
...(this.credentialManager.getServiceCredentials(name) || {})
|
...(this.credentialManager.getServiceCredentials(name) || {})
|
||||||
};
|
};
|
||||||
|
|||||||
69
src-tauri/Cargo.lock
generated
69
src-tauri/Cargo.lock
generated
@@ -270,7 +270,7 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -762,7 +762,7 @@ dependencies = [
|
|||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"block2",
|
"block2",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2079,7 +2079,7 @@ dependencies = [
|
|||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
@@ -2157,6 +2157,22 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc-sys"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
|
||||||
|
dependencies = [
|
||||||
|
"objc-sys",
|
||||||
|
"objc2-encode",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2"
|
name = "objc2"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
@@ -2175,7 +2191,7 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"block2",
|
"block2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
@@ -2187,7 +2203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
|
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2197,7 +2213,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
|
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2209,7 +2225,7 @@ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2220,7 +2236,7 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-io-surface",
|
"objc2-io-surface",
|
||||||
]
|
]
|
||||||
@@ -2231,7 +2247,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
|
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2241,7 +2257,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
|
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2252,7 +2268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
|
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
]
|
]
|
||||||
@@ -2280,7 +2296,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"block2",
|
"block2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2291,7 +2307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2302,7 +2318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
@@ -2315,7 +2331,7 @@ checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"block2",
|
"block2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-cloud-kit",
|
"objc2-cloud-kit",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
@@ -2334,7 +2350,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
|
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2346,7 +2362,7 @@ checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"block2",
|
"block2",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
@@ -3211,7 +3227,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"ndk",
|
"ndk",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
@@ -3385,7 +3401,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"ndk",
|
"ndk",
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
@@ -3441,7 +3457,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
"muda",
|
"muda",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
@@ -3612,7 +3628,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"objc2-web-kit",
|
"objc2-web-kit",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@@ -3636,7 +3652,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -4051,7 +4067,7 @@ dependencies = [
|
|||||||
"dirs",
|
"dirs",
|
||||||
"libappindicator",
|
"libappindicator",
|
||||||
"muda",
|
"muda",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
@@ -4222,9 +4238,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vyla-home"
|
name = "vyla-home"
|
||||||
version = "1.1.9"
|
version = "1.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
"objc2 0.5.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
@@ -4475,7 +4492,7 @@ version = "0.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
|
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
@@ -4930,7 +4947,7 @@ dependencies = [
|
|||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"ndk",
|
"ndk",
|
||||||
"objc2",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "vyla-home"
|
name = "vyla-home"
|
||||||
version = "1.1.9"
|
version = "1.1.33"
|
||||||
description = "Vyla Home Desktop App Launcher"
|
description = "Vyla Home Desktop App Launcher"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,6 +16,9 @@ serde = { version = "1", features = ["derive"] }
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
objc2 = "0.5"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
13
src-tauri/Info.plist
Normal file
13
src-tauri/Info.plist
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -1,5 +1,32 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=src/main.rs");
|
println!("cargo:rerun-if-changed=src/main.rs");
|
||||||
println!("cargo:rerun-if-changed=tauri.conf.json");
|
println!("cargo:rerun-if-changed=tauri.conf.json");
|
||||||
|
|
||||||
|
if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" {
|
||||||
|
if let (Ok(manifest_dir), Ok(out_dir)) = (std::env::var("CARGO_MANIFEST_DIR"), std::env::var("OUT_DIR")) {
|
||||||
|
let mut target_dir = std::path::PathBuf::from(out_dir);
|
||||||
|
target_dir.pop();
|
||||||
|
target_dir.pop();
|
||||||
|
target_dir.pop();
|
||||||
|
let dll_src = std::path::Path::new(&manifest_dir).join("WebView2Loader.dll");
|
||||||
|
let dll_dest = target_dir.join("WebView2Loader.dll");
|
||||||
|
if dll_src.exists() {
|
||||||
|
let _ = std::fs::copy(dll_src, dll_dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let node_path = std::path::Path::new("../bin/node");
|
||||||
|
if node_path.exists() {
|
||||||
|
let _ = std::fs::set_permissions(
|
||||||
|
node_path,
|
||||||
|
std::fs::Permissions::from_mode(0o755),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tauri_build::build();
|
tauri_build::build();
|
||||||
}
|
}
|
||||||
@@ -11,12 +11,17 @@ fn debug_log(msg: &str) {
|
|||||||
.join("vyla-home")
|
.join("vyla-home")
|
||||||
.join("vyla-home-files")
|
.join("vyla-home-files")
|
||||||
.join("logs")
|
.join("logs")
|
||||||
|
} else if let Ok(home) = std::env::var("HOME") {
|
||||||
|
std::path::PathBuf::from(home)
|
||||||
|
.join("Library")
|
||||||
|
.join("Logs")
|
||||||
|
.join("VylaHome")
|
||||||
} else if let Ok(user_profile) = std::env::var("USERPROFILE") {
|
} else if let Ok(user_profile) = std::env::var("USERPROFILE") {
|
||||||
std::path::PathBuf::from(user_profile)
|
std::path::PathBuf::from(user_profile)
|
||||||
.join(".vyla-home")
|
.join(".vyla-home")
|
||||||
.join("logs")
|
.join("logs")
|
||||||
} else {
|
} else {
|
||||||
std::path::PathBuf::from("logs")
|
std::env::temp_dir().join("vyla-home-logs")
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = std::fs::create_dir_all(&log_dir);
|
let _ = std::fs::create_dir_all(&log_dir);
|
||||||
@@ -50,17 +55,38 @@ fn clean_path(path: &std::path::Path) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn kill_child_process(child: &mut Child) {
|
fn kill_child_process(child: &mut Child) {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
|
let _ = Command::new("taskkill")
|
||||||
|
.args(&["/F", "/T", "/PID", &child.id().to_string()])
|
||||||
|
.creation_flags(CREATE_NO_WINDOW)
|
||||||
|
.output();
|
||||||
|
}
|
||||||
let _ = child.kill();
|
let _ = child.kill();
|
||||||
let _ = child.wait();
|
let _ = child.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_node_binary(app: &tauri::App) -> Option<String> {
|
fn find_node_binary(app: &tauri::App) -> Option<String> {
|
||||||
if let Ok(resource_dir) = app.path().resource_dir() {
|
if let Ok(resource_dir) = app.path().resource_dir() {
|
||||||
|
#[cfg(windows)]
|
||||||
let candidates = [
|
let candidates = [
|
||||||
resource_dir.join("bin").join("node.exe"),
|
resource_dir.join("bin").join("node.exe"),
|
||||||
resource_dir.join("node.exe"),
|
resource_dir.join("node.exe"),
|
||||||
resource_dir.join("_up_").join("bin").join("node.exe"),
|
resource_dir.join("_up_").join("bin").join("node.exe"),
|
||||||
resource_dir.join("_up_").join("node.exe"),
|
resource_dir.join("_up_").join("node.exe"),
|
||||||
|
resource_dir.join("up").join("bin").join("node.exe"),
|
||||||
|
resource_dir.join("up").join("node.exe"),
|
||||||
|
];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let candidates = [
|
||||||
|
resource_dir.join("bin").join("node"),
|
||||||
|
resource_dir.join("node"),
|
||||||
|
resource_dir.join("_up_").join("bin").join("node"),
|
||||||
|
resource_dir.join("_up_").join("node"),
|
||||||
|
resource_dir.join("up").join("bin").join("node"),
|
||||||
|
resource_dir.join("up").join("node"),
|
||||||
];
|
];
|
||||||
for candidate in candidates {
|
for candidate in candidates {
|
||||||
if candidate.exists() {
|
if candidate.exists() {
|
||||||
@@ -73,9 +99,13 @@ fn find_node_binary(app: &tauri::App) -> Option<String> {
|
|||||||
if let Some(parent) = exe_path.parent() {
|
if let Some(parent) = exe_path.parent() {
|
||||||
let candidates = [
|
let candidates = [
|
||||||
parent.join("bin").join("node.exe"),
|
parent.join("bin").join("node.exe"),
|
||||||
|
parent.join("bin").join("node"),
|
||||||
parent.join("node.exe"),
|
parent.join("node.exe"),
|
||||||
|
parent.join("node"),
|
||||||
parent.join("resources").join("bin").join("node.exe"),
|
parent.join("resources").join("bin").join("node.exe"),
|
||||||
|
parent.join("resources").join("bin").join("node"),
|
||||||
parent.join("resources").join("node.exe"),
|
parent.join("resources").join("node.exe"),
|
||||||
|
parent.join("resources").join("node"),
|
||||||
];
|
];
|
||||||
for candidate in candidates {
|
for candidate in candidates {
|
||||||
if candidate.exists() {
|
if candidate.exists() {
|
||||||
@@ -87,9 +117,14 @@ fn find_node_binary(app: &tauri::App) -> Option<String> {
|
|||||||
|
|
||||||
if let Ok(path_var) = std::env::var("PATH") {
|
if let Ok(path_var) = std::env::var("PATH") {
|
||||||
for p in std::env::split_paths(&path_var) {
|
for p in std::env::split_paths(&path_var) {
|
||||||
let candidate = p.join("node.exe");
|
let node_exe = p.join("node.exe");
|
||||||
if candidate.exists() {
|
if node_exe.exists() {
|
||||||
return Some(clean_path(&candidate));
|
return Some(clean_path(&node_exe));
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = p.join("node");
|
||||||
|
if node.exists() {
|
||||||
|
return Some(clean_path(&node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,6 +153,22 @@ fn find_node_binary(app: &tauri::App) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
let default_paths = [
|
||||||
|
"/usr/local/bin/node",
|
||||||
|
"/usr/bin/node",
|
||||||
|
"/opt/homebrew/bin/node",
|
||||||
|
"/opt/local/bin/node",
|
||||||
|
];
|
||||||
|
for path in default_paths {
|
||||||
|
let candidate = std::path::Path::new(path);
|
||||||
|
if candidate.exists() {
|
||||||
|
return Some(clean_path(candidate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,8 +177,8 @@ fn resolve_script_and_root(app: &tauri::App) -> (String, String) {
|
|||||||
let candidates = vec![
|
let candidates = vec![
|
||||||
resource_dir.join("main.js"),
|
resource_dir.join("main.js"),
|
||||||
resource_dir.join("_up_").join("main.js"),
|
resource_dir.join("_up_").join("main.js"),
|
||||||
|
resource_dir.join("up").join("main.js"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for candidate in candidates {
|
for candidate in candidates {
|
||||||
if candidate.exists() {
|
if candidate.exists() {
|
||||||
let root_dir = candidate.parent().unwrap();
|
let root_dir = candidate.parent().unwrap();
|
||||||
@@ -217,12 +268,12 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
-webkit-app-region: no-drag !important;
|
-webkit-app-region: no-drag !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#vyla-floating-nav:hover {
|
#vyla-floating-nav:hover {
|
||||||
width: 82px !important;
|
width: 82px !important;
|
||||||
padding-right: 6px !important;
|
padding-right: 6px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vyla-nav-logo-wrap {
|
.vyla-nav-logo-wrap {
|
||||||
width: 42px !important;
|
width: 42px !important;
|
||||||
min-width: 42px !important;
|
min-width: 42px !important;
|
||||||
@@ -237,7 +288,7 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vyla-nav-logo-wrap img {
|
.vyla-nav-logo-wrap img {
|
||||||
width: 26px !important;
|
width: 26px !important;
|
||||||
height: 26px !important;
|
height: 26px !important;
|
||||||
@@ -247,7 +298,7 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vyla-nav-links {
|
.vyla-nav-links {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
@@ -260,13 +311,13 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
z-index: 2 !important;
|
z-index: 2 !important;
|
||||||
margin-left: 2px !important;
|
margin-left: 2px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#vyla-floating-nav:hover .vyla-nav-links {
|
#vyla-floating-nav:hover .vyla-nav-links {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
transform: translateX(0) !important;
|
transform: translateX(0) !important;
|
||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vyla-nav-btn {
|
.vyla-nav-btn {
|
||||||
width: 32px !important;
|
width: 32px !important;
|
||||||
min-width: 32px !important;
|
min-width: 32px !important;
|
||||||
@@ -285,11 +336,8 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
opacity: 0.85 !important;
|
opacity: 0.85 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vyla-nav-btn:hover {
|
.vyla-nav-btn:hover { opacity: 1 !important; }
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vyla-nav-btn svg {
|
.vyla-nav-btn svg {
|
||||||
width: 20px !important;
|
width: 20px !important;
|
||||||
height: 20px !important;
|
height: 20px !important;
|
||||||
@@ -297,7 +345,7 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tauri-custom-titlebar {
|
#tauri-custom-titlebar {
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
@@ -315,7 +363,7 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tauri-drag-region {
|
.tauri-drag-region {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
inset: 0 !important;
|
inset: 0 !important;
|
||||||
@@ -323,7 +371,7 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tauri-window-controls {
|
.tauri-window-controls {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
height: 32px !important;
|
height: 32px !important;
|
||||||
@@ -332,7 +380,7 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
z-index: 2147483648 !important;
|
z-index: 2147483648 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.tauri-btn {
|
button.tauri-btn {
|
||||||
width: 46px !important;
|
width: 46px !important;
|
||||||
min-width: 46px !important;
|
min-width: 46px !important;
|
||||||
@@ -347,7 +395,7 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.tauri-btn:hover { background: rgba(255, 255, 255, 0.2) !important; }
|
button.tauri-btn:hover { background: rgba(255, 255, 255, 0.2) !important; }
|
||||||
button#tauri-btn-close:hover { background: #e81123 !important; color: #ffffff !important; }
|
button#tauri-btn-close:hover { background: #e81123 !important; color: #ffffff !important; }
|
||||||
button.tauri-btn svg { width: 10px !important; height: 10px !important; fill: currentColor !important; pointer-events: none !important; }
|
button.tauri-btn svg { width: 10px !important; height: 10px !important; fill: currentColor !important; pointer-events: none !important; }
|
||||||
@@ -416,11 +464,12 @@ const LINK_HANDLER_SCRIPT: &str = r##"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', injectLiquidNavBar);
|
document.addEventListener('DOMContentLoaded', injectLiquidNavBar);
|
||||||
} else {
|
} else {
|
||||||
injectLiquidNavBar();
|
injectLiquidNavBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
const navObserver = new MutationObserver(() => {
|
const navObserver = new MutationObserver(() => {
|
||||||
if (!document.getElementById('vyla-floating-nav')) {
|
if (!document.getElementById('vyla-floating-nav')) {
|
||||||
injectLiquidNavBar();
|
injectLiquidNavBar();
|
||||||
@@ -464,9 +513,9 @@ fn open_new_window(app: tauri::AppHandle, url: String) -> Result<(), String> {
|
|||||||
let final_url = if url.starts_with("http") || url.starts_with("tauri://") {
|
let final_url = if url.starts_with("http") || url.starts_with("tauri://") {
|
||||||
url.clone()
|
url.clone()
|
||||||
} else if url.starts_with('/') {
|
} else if url.starts_with('/') {
|
||||||
format!("http://localhost:4890{}", url)
|
format!("http://127.0.0.1:4890{}", url)
|
||||||
} else {
|
} else {
|
||||||
format!("http://localhost:4890/{}", url)
|
format!("http://127.0.0.1:4890/{}", url)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
@@ -482,6 +531,30 @@ fn open_new_window(app: tauri::AppHandle, url: String) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn kill_orphans() -> Result<(), String> {
|
||||||
|
debug_log("[KILL] User requested orphan process kill");
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
|
let _ = std::process::Command::new("taskkill")
|
||||||
|
.args(&["/F", "/T", "/IM", "node.exe"])
|
||||||
|
.creation_flags(CREATE_NO_WINDOW)
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
let _ = std::process::Command::new("pkill")
|
||||||
|
.args(&["-9", "-f", "node"])
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_logo_base64(app: tauri::AppHandle) -> Result<String, String> {
|
fn get_logo_base64(app: tauri::AppHandle) -> Result<String, String> {
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
@@ -515,6 +588,123 @@ fn get_logo_base64(app: tauri::AppHandle) -> Result<String, String> {
|
|||||||
Err("icon_white.png not found".to_string())
|
Err("icon_white.png not found".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_tauri_cmd(w: &tauri::WebviewWindow, msg: &BackendMessage) {
|
||||||
|
let window_label = msg
|
||||||
|
.data
|
||||||
|
.get("window")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("main");
|
||||||
|
|
||||||
|
debug_log(&format!("[MAIN-DEBUG] Finding window by label: {}", window_label));
|
||||||
|
|
||||||
|
let cmd_window = match w.app_handle().get_webview_window(window_label) {
|
||||||
|
Some(win) => win,
|
||||||
|
None => {
|
||||||
|
debug_log(&format!("[TAURI-CMD] Window '{}' NOT FOUND!", window_label));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cmd = match msg.data.get("cmd").and_then(|c| c.as_str()) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_log(&format!("[TAURI-CMD] Executing action: {}", cmd));
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
"win_minimize" => {
|
||||||
|
let res = cmd_window.minimize();
|
||||||
|
debug_log(&format!("[TAURI-CMD] Minimize result: {:?}", res));
|
||||||
|
}
|
||||||
|
"win_maximize" => {
|
||||||
|
let is_max = cmd_window.is_maximized().unwrap_or(false);
|
||||||
|
debug_log(&format!("[TAURI-CMD] Currently maximized: {}", is_max));
|
||||||
|
let res = if is_max {
|
||||||
|
cmd_window.unmaximize()
|
||||||
|
} else {
|
||||||
|
cmd_window.maximize()
|
||||||
|
};
|
||||||
|
debug_log(&format!("[TAURI-CMD] Maximize/Unmaximize result: {:?}", res));
|
||||||
|
}
|
||||||
|
"win_close" => {
|
||||||
|
let res = cmd_window.close();
|
||||||
|
debug_log(&format!("[TAURI-CMD] Close result: {:?}", res));
|
||||||
|
}
|
||||||
|
"win_drag" => {
|
||||||
|
let res = cmd_window.start_dragging();
|
||||||
|
debug_log(&format!("[TAURI-CMD] Drag result: {:?}", res));
|
||||||
|
}
|
||||||
|
"open_new_window" => {
|
||||||
|
debug_log("[TAURI-CMD-DEBUG] open_new_window command received");
|
||||||
|
debug_log(&format!(
|
||||||
|
"[TAURI-CMD-DEBUG] Full message data: {}",
|
||||||
|
serde_json::to_string(&msg.data).unwrap_or_default()
|
||||||
|
));
|
||||||
|
|
||||||
|
let url = match msg.data.get("url").and_then(|u| u.as_str()) {
|
||||||
|
Some(u) => u.to_string(),
|
||||||
|
None => {
|
||||||
|
debug_log("[TAURI-CMD-DEBUG] ERROR: No URL found in message data");
|
||||||
|
debug_log(&format!(
|
||||||
|
"[TAURI-CMD-DEBUG] Available keys: {:?}",
|
||||||
|
msg.data.as_object().map(|o| o.keys().collect::<Vec<_>>())
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_log(&format!("[TAURI-CMD-DEBUG] URL extracted: {}", url));
|
||||||
|
debug_log(&format!("[TAURI-CMD-DEBUG] URL length: {}", url.len()));
|
||||||
|
debug_log("[TAURI-CMD-DEBUG] Attempting to dispatch to main thread");
|
||||||
|
|
||||||
|
let app_handle = w.app_handle().clone();
|
||||||
|
let closure_handle = app_handle.clone();
|
||||||
|
|
||||||
|
match app_handle.run_on_main_thread(move || {
|
||||||
|
debug_log("[TAURI-CMD-DEBUG] Now on main thread, calling open_new_window");
|
||||||
|
match open_new_window(closure_handle, url) {
|
||||||
|
Ok(_) => debug_log("[TAURI-CMD-DEBUG] open_new_window succeeded"),
|
||||||
|
Err(e) => debug_log(&format!("[TAURI-CMD-DEBUG] open_new_window error: {}", e)),
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Ok(_) => debug_log("[TAURI-CMD-DEBUG] Main thread dispatch succeeded"),
|
||||||
|
Err(e) => debug_log(&format!("[TAURI-CMD-DEBUG] Main thread dispatch failed: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_ready(w: &tauri::WebviewWindow, msg: &BackendMessage) {
|
||||||
|
let url_str = match msg.data.get("url").and_then(|u| u.as_str()) {
|
||||||
|
Some(u) => u,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let url_parsed = match url_str.parse::<tauri::Url>() {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
use objc2::msg_send;
|
||||||
|
use objc2::runtime::AnyObject;
|
||||||
|
debug_log("Attempting with_webview on ready event");
|
||||||
|
let result = w.with_webview(|webview| unsafe {
|
||||||
|
debug_log("Inside with_webview closure");
|
||||||
|
let wk_webview = webview.inner() as *mut AnyObject;
|
||||||
|
let config: *mut AnyObject = msg_send![wk_webview, configuration];
|
||||||
|
let _: () = msg_send![config, setUpgradeKnownHostsToHTTPS: false];
|
||||||
|
debug_log("setUpgradeKnownHostsToHTTPS set to false");
|
||||||
|
});
|
||||||
|
debug_log(&format!("with_webview result: {:?}", result));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = w.navigate(url_parsed);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
debug_log("=== Vyla Home Executable Started ===");
|
debug_log("=== Vyla Home Executable Started ===");
|
||||||
|
|
||||||
@@ -528,44 +718,72 @@ fn main() {
|
|||||||
win_close,
|
win_close,
|
||||||
win_drag,
|
win_drag,
|
||||||
open_new_window,
|
open_new_window,
|
||||||
get_logo_base64
|
get_logo_base64,
|
||||||
|
kill_orphans
|
||||||
])
|
])
|
||||||
.manage(NodeServices(Mutex::new(None)))
|
.manage(NodeServices(Mutex::new(None)))
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
debug_log("Tauri setup started");
|
debug_log("Tauri setup started");
|
||||||
let app_data = app.path().app_data_dir().unwrap_or_default();
|
let app_data = app.path().app_data_dir().unwrap_or_default();
|
||||||
let app_data_str = clean_path(&app_data);
|
|
||||||
|
let vyla_files = if let Ok(appdata_env) = std::env::var("APPDATA") {
|
||||||
|
std::path::PathBuf::from(appdata_env)
|
||||||
|
.join("vyla-home")
|
||||||
|
.join("vyla-home-files")
|
||||||
|
} else if let Ok(home) = std::env::var("HOME") {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let base = std::path::PathBuf::from(&home)
|
||||||
|
.join("Library")
|
||||||
|
.join("Application Support");
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let base = std::path::PathBuf::from(&home)
|
||||||
|
.join(".local")
|
||||||
|
.join("share");
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||||
|
let base = app_data.clone();
|
||||||
|
base.join("vyla-home").join("vyla-home-files")
|
||||||
|
} else {
|
||||||
|
app_data.join("vyla-home-files")
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = std::fs::create_dir_all(&vyla_files);
|
||||||
|
|
||||||
let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into()))
|
let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into()))
|
||||||
.title("Vyla Home")
|
.title("Vyla Home")
|
||||||
.inner_size(1280.0, 800.0)
|
.inner_size(1280.0, 800.0)
|
||||||
.decorations(false)
|
.decorations(false)
|
||||||
.transparent(false)
|
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.devtools(cfg!(debug_assertions))
|
.devtools(true)
|
||||||
.initialization_script("window.__VYLA_WINDOW_LABEL = 'main';")
|
.initialization_script("window.__VYLA_WINDOW_LABEL = 'main';")
|
||||||
.initialization_script(LINK_HANDLER_SCRIPT)
|
.initialization_script(LINK_HANDLER_SCRIPT)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let (main_js, root_dir) = resolve_script_and_root(app);
|
let (main_js, root_dir) = resolve_script_and_root(app);
|
||||||
|
debug_log("Searching for node binary...");
|
||||||
|
|
||||||
let node_bin = match find_node_binary(app) {
|
let node_bin = match find_node_binary(app) {
|
||||||
Some(bin) => bin,
|
Some(bin) => {
|
||||||
|
debug_log(&format!("Found node binary: {}", bin));
|
||||||
|
bin
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
let _ = window.emit(
|
debug_log("FATAL: No node binary found in bundle or PATH.");
|
||||||
"error",
|
std::thread::sleep(std::time::Duration::from_millis(600));
|
||||||
serde_json::json!({
|
let _ = window.eval("document.getElementById('status-message').textContent='Error: Node runtime missing. Please reinstall.';");
|
||||||
"message": "Node.js runtime not found. Please ensure Node.js is installed or bundled correctly."
|
let _ = window.eval("var b=document.getElementById('debug-log-box');b.style.display='block';b.textContent='Node binary not found. bin/node missing from app bundle or not executable.';");
|
||||||
}),
|
|
||||||
);
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let vyla_files_str = clean_path(&vyla_files);
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(400));
|
||||||
|
|
||||||
let mut cmd = Command::new(&node_bin);
|
let mut cmd = Command::new(&node_bin);
|
||||||
cmd.arg(&main_js)
|
cmd.arg(&main_js)
|
||||||
.current_dir(&root_dir)
|
.current_dir(&root_dir)
|
||||||
.env("APPDATA_PATH", &app_data_str)
|
.env("APPDATA_PATH", &vyla_files_str)
|
||||||
.env("NODE_EXE", &node_bin)
|
.env("NODE_EXE", &node_bin)
|
||||||
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped());
|
.stderr(Stdio::piped());
|
||||||
|
|
||||||
@@ -582,12 +800,23 @@ fn main() {
|
|||||||
"Node child process spawned successfully, PID: {}",
|
"Node child process spawned successfully, PID: {}",
|
||||||
child.id()
|
child.id()
|
||||||
));
|
));
|
||||||
|
|
||||||
if let Some(stderr) = child.stderr.take() {
|
if let Some(stderr) = child.stderr.take() {
|
||||||
|
let w = window.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
let reader = BufReader::new(stderr);
|
let reader = BufReader::new(stderr);
|
||||||
for line in reader.lines().flatten() {
|
for line in reader.lines().flatten() {
|
||||||
debug_log(&format!("[NODE STDERR] {}", line));
|
debug_log(&format!("[NODE STDERR] {}", line));
|
||||||
|
if line.contains("Error")
|
||||||
|
|| line.contains("Exception")
|
||||||
|
|| line.contains("MODULE_NOT_FOUND")
|
||||||
|
{
|
||||||
|
let _ = w.emit(
|
||||||
|
"error",
|
||||||
|
serde_json::json!({ "message": format!("Backend failed: {}", line) }),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -599,144 +828,22 @@ fn main() {
|
|||||||
let reader = BufReader::new(stdout);
|
let reader = BufReader::new(stdout);
|
||||||
for line in reader.lines().flatten() {
|
for line in reader.lines().flatten() {
|
||||||
debug_log(&format!("[NODE STDOUT] {}", line));
|
debug_log(&format!("[NODE STDOUT] {}", line));
|
||||||
if let Ok(msg) = serde_json::from_str::<BackendMessage>(&line) {
|
|
||||||
debug_log(&format!("[MAIN-DEBUG] Received event: {}", msg.event));
|
|
||||||
debug_log(&format!(
|
|
||||||
"[MAIN-DEBUG] Event data: {}",
|
|
||||||
serde_json::to_string(&msg.data).unwrap_or_default()
|
|
||||||
));
|
|
||||||
if msg.event == "tauri-cmd" {
|
|
||||||
let window_label = msg
|
|
||||||
.data
|
|
||||||
.get("window")
|
|
||||||
.and_then(|val| val.as_str())
|
|
||||||
.unwrap_or("main");
|
|
||||||
debug_log(&format!(
|
|
||||||
"[MAIN-DEBUG] Finding window by label: {}",
|
|
||||||
window_label
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(cmd_window) =
|
let msg = match serde_json::from_str::<BackendMessage>(&line) {
|
||||||
w.app_handle().get_webview_window(window_label)
|
Ok(m) => m,
|
||||||
{
|
Err(_) => continue,
|
||||||
if let Some(cmd) =
|
};
|
||||||
msg.data.get("cmd").and_then(|c| c.as_str())
|
|
||||||
{
|
debug_log(&format!("[MAIN-DEBUG] Received event: {}", msg.event));
|
||||||
debug_log(&format!(
|
debug_log(&format!(
|
||||||
"[TAURI-CMD] Executing action: {}",
|
"[MAIN-DEBUG] Event data: {}",
|
||||||
cmd
|
serde_json::to_string(&msg.data).unwrap_or_default()
|
||||||
));
|
));
|
||||||
match cmd {
|
|
||||||
"win_minimize" => {
|
match msg.event.as_str() {
|
||||||
let res = cmd_window.minimize();
|
"tauri-cmd" => handle_tauri_cmd(&w, &msg),
|
||||||
debug_log(&format!(
|
"ready" => handle_ready(&w, &msg),
|
||||||
"[TAURI-CMD] Minimize result: {:?}",
|
_ => {
|
||||||
res
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"win_maximize" => {
|
|
||||||
let is_max = cmd_window
|
|
||||||
.is_maximized()
|
|
||||||
.unwrap_or(false);
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD] Currently maximized: {}",
|
|
||||||
is_max
|
|
||||||
));
|
|
||||||
let res = if is_max {
|
|
||||||
cmd_window.unmaximize()
|
|
||||||
} else {
|
|
||||||
cmd_window.maximize()
|
|
||||||
};
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD] Maximize/Unmaximize result: {:?}",
|
|
||||||
res
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"win_close" => {
|
|
||||||
let res = cmd_window.close();
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD] Close result: {:?}",
|
|
||||||
res
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"win_drag" => {
|
|
||||||
let res = cmd_window.start_dragging();
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD] Drag result: {:?}",
|
|
||||||
res
|
|
||||||
));
|
|
||||||
}
|
|
||||||
"open_new_window" => {
|
|
||||||
debug_log("[TAURI-CMD-DEBUG] open_new_window command received");
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD-DEBUG] Full message data: {}",
|
|
||||||
serde_json::to_string(&msg.data)
|
|
||||||
.unwrap_or_default()
|
|
||||||
));
|
|
||||||
if let Some(url) = msg
|
|
||||||
.data
|
|
||||||
.get("url")
|
|
||||||
.and_then(|u| u.as_str())
|
|
||||||
{
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD-DEBUG] URL extracted: {}",
|
|
||||||
url
|
|
||||||
));
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD-DEBUG] URL length: {}",
|
|
||||||
url.len()
|
|
||||||
));
|
|
||||||
debug_log("[TAURI-CMD-DEBUG] Attempting to dispatch to main thread");
|
|
||||||
let app_handle = w.app_handle().clone();
|
|
||||||
let closure_handle = app_handle.clone();
|
|
||||||
let url_owned = url.to_string();
|
|
||||||
let dispatch_result = app_handle
|
|
||||||
.run_on_main_thread(move || {
|
|
||||||
debug_log("[TAURI-CMD-DEBUG] Now on main thread, calling open_new_window");
|
|
||||||
match open_new_window(
|
|
||||||
closure_handle,
|
|
||||||
url_owned,
|
|
||||||
) {
|
|
||||||
Ok(_) => {
|
|
||||||
debug_log("[TAURI-CMD-DEBUG] open_new_window succeeded");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD-DEBUG] open_new_window error: {}",
|
|
||||||
e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
match dispatch_result {
|
|
||||||
Ok(_) => debug_log(
|
|
||||||
"[TAURI-CMD-DEBUG] Main thread dispatch succeeded",
|
|
||||||
),
|
|
||||||
Err(e) => debug_log(&format!(
|
|
||||||
"[TAURI-CMD-DEBUG] Main thread dispatch failed: {}",
|
|
||||||
e
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug_log("[TAURI-CMD-DEBUG] ERROR: No URL found in message data");
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD-DEBUG] Available keys: {:?}",
|
|
||||||
msg.data
|
|
||||||
.as_object()
|
|
||||||
.map(|o| o.keys().collect::<Vec<_>>())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug_log(&format!(
|
|
||||||
"[TAURI-CMD] Window '{}' NOT FOUND!",
|
|
||||||
window_label
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = w.emit(&msg.event, msg.data.clone());
|
let _ = w.emit(&msg.event, msg.data.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,18 +858,18 @@ fn main() {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("Failed to spawn node: {}", e));
|
debug_log(&format!("Failed to spawn node: {}", e));
|
||||||
let _ = window.emit(
|
let emsg = format!("Failed to spawn node: {}", e);
|
||||||
"error",
|
let js = format!(
|
||||||
serde_json::json!({
|
"document.getElementById('status-message').textContent='Spawn Error';var b=document.getElementById('debug-log-box');b.style.display='block';b.textContent={};",
|
||||||
"message": format!("Failed to launch backend runtime: {}", e)
|
serde_json::to_string(&emsg).unwrap_or_default()
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
let _ = window.eval(&js);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
if let tauri::WindowEvent::Destroyed = event {
|
if let tauri::WindowEvent::Destroyed = event {
|
||||||
if window.label() == "main" {
|
if window.label() == "main" {
|
||||||
let state: State<NodeServices> = window.state();
|
let state: State<NodeServices> = window.state();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Vyla Home",
|
"productName": "Vyla Home",
|
||||||
"version": "1.1.9",
|
"version": "1.1.33",
|
||||||
"identifier": "com.vyla.home",
|
"identifier": "com.vyla.home",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../ui"
|
"frontendDist": "../ui"
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": "default-src 'self' http://localhost:4890 http://127.0.0.1:4890; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:4890 http://127.0.0.1:4890 https://esm.sh; style-src 'self' 'unsafe-inline' http://localhost:4890 http://127.0.0.1:4890; img-src 'self' data: https: http://localhost:4890 http://127.0.0.1:4890; font-src 'self' data: http://localhost:4890 http://127.0.0.1:4890; connect-src 'self' http://localhost: http://127.0.0.1:"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
@@ -22,6 +22,10 @@
|
|||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
|
"macOS": {
|
||||||
|
"exceptionDomain": "localhost",
|
||||||
|
"infoPlist": "Info.plist"
|
||||||
|
},
|
||||||
"windows": {
|
"windows": {
|
||||||
"webviewInstallMode": {
|
"webviewInstallMode": {
|
||||||
"type": "downloadBootstrapper"
|
"type": "downloadBootstrapper"
|
||||||
@@ -29,8 +33,9 @@
|
|||||||
},
|
},
|
||||||
"resources": [
|
"resources": [
|
||||||
"WebView2Loader.dll",
|
"WebView2Loader.dll",
|
||||||
"../bin/*",
|
"../bin/**/*",
|
||||||
"../main.js",
|
"../main.js",
|
||||||
|
"../preload.js",
|
||||||
"../service-manager.js",
|
"../service-manager.js",
|
||||||
"../patcher.js",
|
"../patcher.js",
|
||||||
"../crypto-utils.js",
|
"../crypto-utils.js",
|
||||||
|
|||||||
311
ui/index.html
311
ui/index.html
@@ -195,6 +195,9 @@
|
|||||||
<div class="loader-container">
|
<div class="loader-container">
|
||||||
<div class="loader-card">
|
<div class="loader-card">
|
||||||
<div class="status-text" id="status-message">Starting runtime engine...</div>
|
<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;">
|
||||||
|
</div>
|
||||||
<div class="progress-track" id="progress-track" style="display: none;">
|
<div class="progress-track" id="progress-track" style="display: none;">
|
||||||
<div class="progress-fill" id="progress-fill"></div>
|
<div class="progress-fill" id="progress-fill"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,23 +259,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="view-error" class="view-state">
|
|
||||||
<div class="container">
|
|
||||||
<svg class="icon icon-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="10" />
|
|
||||||
<line x1="12" y1="7" x2="12" y2="13" />
|
|
||||||
<circle cx="12" cy="17" r="1" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<h1>Couldn't start Vyla Home</h1>
|
|
||||||
<p id="error-msg"></p>
|
|
||||||
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="primary" onclick="location.reload()">Try Again</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -284,61 +271,66 @@
|
|||||||
console.warn("OGL Shader background failed to load (offline):", err);
|
console.warn("OGL Shader background failed to load (offline):", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.__OGL_LOADED__) {
|
window.dispatchEvent(new Event('ogl-ready'));
|
||||||
document.body.style.background = "radial-gradient(circle at 80% 20%, #1e1b4b 0%, #050505 70%)";
|
</script>
|
||||||
} else {
|
|
||||||
const { Renderer, Program, Triangle, Mesh } = window.__OGL_LOADED__;
|
|
||||||
const rayContainer = document.getElementById("side-rays");
|
|
||||||
const settings = {
|
|
||||||
speed: 2.5,
|
|
||||||
rayColor1: "#EAB308",
|
|
||||||
rayColor2: "#96c8ff",
|
|
||||||
intensity: 2,
|
|
||||||
spread: 2,
|
|
||||||
origin: "top-right",
|
|
||||||
tilt: 0,
|
|
||||||
saturation: 1.5,
|
|
||||||
blend: 0.75,
|
|
||||||
falloff: 1.6,
|
|
||||||
opacity: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
function hexToRgb(hex) {
|
<script>
|
||||||
const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
window.addEventListener('ogl-ready', () => {
|
||||||
if (!match) return [1, 1, 1];
|
if (!window.__OGL_LOADED__) {
|
||||||
return [
|
document.body.style.background = "radial-gradient(circle at 80% 20%, #1e1b4b 0%, #050505 70%)";
|
||||||
parseInt(match[1], 16) / 255,
|
} else {
|
||||||
parseInt(match[2], 16) / 255,
|
const { Renderer, Program, Triangle, Mesh } = window.__OGL_LOADED__;
|
||||||
parseInt(match[3], 16) / 255
|
const rayContainer = document.getElementById("side-rays");
|
||||||
];
|
const settings = {
|
||||||
}
|
speed: 2.5,
|
||||||
|
rayColor1: "#EAB308",
|
||||||
|
rayColor2: "#96c8ff",
|
||||||
|
intensity: 2,
|
||||||
|
spread: 2,
|
||||||
|
origin: "top-right",
|
||||||
|
tilt: 0,
|
||||||
|
saturation: 1.5,
|
||||||
|
blend: 0.75,
|
||||||
|
falloff: 1.6,
|
||||||
|
opacity: 1
|
||||||
|
};
|
||||||
|
|
||||||
function originToFlip(origin) {
|
function hexToRgb(hex) {
|
||||||
switch (origin) {
|
const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
case "top-left": return [1, 0];
|
if (!match) return [1, 1, 1];
|
||||||
case "bottom-right": return [0, 1];
|
return [
|
||||||
case "bottom-left": return [1, 1];
|
parseInt(match[1], 16) / 255,
|
||||||
default: return [0, 0];
|
parseInt(match[2], 16) / 255,
|
||||||
|
parseInt(match[3], 16) / 255
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const renderer = new Renderer({
|
function originToFlip(origin) {
|
||||||
dpr: Math.min(window.devicePixelRatio, 2),
|
switch (origin) {
|
||||||
alpha: true
|
case "top-left": return [1, 0];
|
||||||
});
|
case "bottom-right": return [0, 1];
|
||||||
const gl = renderer.gl;
|
case "bottom-left": return [1, 1];
|
||||||
gl.canvas.style.width = "100%";
|
default: return [0, 0];
|
||||||
gl.canvas.style.height = "100%";
|
}
|
||||||
rayContainer.appendChild(gl.canvas);
|
}
|
||||||
|
|
||||||
const vertexShader = `
|
const renderer = new Renderer({
|
||||||
|
dpr: Math.min(window.devicePixelRatio, 2),
|
||||||
|
alpha: true
|
||||||
|
});
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.canvas.style.width = "100%";
|
||||||
|
gl.canvas.style.height = "100%";
|
||||||
|
rayContainer.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
attribute vec2 position;
|
attribute vec2 position;
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(position, 0.0, 1.0);
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const fragmentShader = `
|
const fragmentShader = `
|
||||||
precision highp float;
|
precision highp float;
|
||||||
uniform float iTime;
|
uniform float iTime;
|
||||||
uniform vec2 iResolution;
|
uniform vec2 iResolution;
|
||||||
@@ -399,77 +391,77 @@ void main() {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const [flipX, flipY] = originToFlip(settings.origin);
|
const [flipX, flipY] = originToFlip(settings.origin);
|
||||||
const uniforms = {
|
const uniforms = {
|
||||||
iTime: { value: 0 },
|
iTime: { value: 0 },
|
||||||
iResolution: { value: [1, 1] },
|
iResolution: { value: [1, 1] },
|
||||||
iSpeed: { value: settings.speed },
|
iSpeed: { value: settings.speed },
|
||||||
iRayColor1: { value: hexToRgb(settings.rayColor1) },
|
iRayColor1: { value: hexToRgb(settings.rayColor1) },
|
||||||
iRayColor2: { value: hexToRgb(settings.rayColor2) },
|
iRayColor2: { value: hexToRgb(settings.rayColor2) },
|
||||||
iIntensity: { value: settings.intensity },
|
iIntensity: { value: settings.intensity },
|
||||||
iSpread: { value: settings.spread },
|
iSpread: { value: settings.spread },
|
||||||
iFlipX: { value: flipX },
|
iFlipX: { value: flipX },
|
||||||
iFlipY: { value: flipY },
|
iFlipY: { value: flipY },
|
||||||
iTilt: { value: settings.tilt },
|
iTilt: { value: settings.tilt },
|
||||||
iSaturation: { value: settings.saturation },
|
iSaturation: { value: settings.saturation },
|
||||||
iBlend: { value: settings.blend },
|
iBlend: { value: settings.blend },
|
||||||
iFalloff: { value: settings.falloff },
|
iFalloff: { value: settings.falloff },
|
||||||
iOpacity: { value: settings.opacity }
|
iOpacity: { value: settings.opacity }
|
||||||
};
|
};
|
||||||
|
|
||||||
const geometry = new Triangle(gl);
|
const geometry = new Triangle(gl);
|
||||||
const program = new Program(gl, { vertex: vertexShader, fragment: fragmentShader, uniforms });
|
const program = new Program(gl, { vertex: vertexShader, fragment: fragmentShader, uniforms });
|
||||||
const mesh = new Mesh(gl, { geometry, program });
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
const width = rayContainer.clientWidth;
|
const width = rayContainer.clientWidth;
|
||||||
const height = rayContainer.clientHeight;
|
const height = rayContainer.clientHeight;
|
||||||
renderer.dpr = Math.min(window.devicePixelRatio, 2);
|
renderer.dpr = Math.min(window.devicePixelRatio, 2);
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
uniforms.iResolution.value = [width * renderer.dpr, height * renderer.dpr];
|
uniforms.iResolution.value = [width * renderer.dpr, height * renderer.dpr];
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", resize);
|
window.addEventListener("resize", resize);
|
||||||
resize();
|
resize();
|
||||||
|
|
||||||
function animate(time) {
|
function animate(time) {
|
||||||
uniforms.iTime.value = time * 0.001;
|
uniforms.iTime.value = time * 0.001;
|
||||||
renderer.render({ scene: mesh });
|
renderer.render({ scene: mesh });
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
}
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
|
|
||||||
function initLiquidButtons() {
|
function initLiquidButtons() {
|
||||||
const borderWidth = 0.16;
|
const borderWidth = 0.16;
|
||||||
const brightness = 35;
|
const brightness = 35;
|
||||||
const opacity = 0.55;
|
const opacity = 0.55;
|
||||||
const blur = 4;
|
const blur = 4;
|
||||||
const displace = 1.2;
|
const displace = 1.2;
|
||||||
const borderRadius = 9999;
|
const borderRadius = 9999;
|
||||||
const distortionScale = -380;
|
const distortionScale = -380;
|
||||||
const redOffset = -25;
|
const redOffset = -25;
|
||||||
const greenOffset = 0;
|
const greenOffset = 0;
|
||||||
const blueOffset = 25;
|
const blueOffset = 25;
|
||||||
const xChannel = "R";
|
const xChannel = "R";
|
||||||
const yChannel = "G";
|
const yChannel = "G";
|
||||||
const mixBlendMode = "screen";
|
const mixBlendMode = "screen";
|
||||||
|
|
||||||
document.querySelectorAll(".liquid-glass-btn").forEach((btn, index) => {
|
document.querySelectorAll(".liquid-glass-btn").forEach((btn, index) => {
|
||||||
const filterId = `glass-filter-${index + 1}`;
|
const filterId = `glass-filter-${index + 1}`;
|
||||||
btn.style.setProperty("--filter-id", `url(#${filterId})`);
|
btn.style.setProperty("--filter-id", `url(#${filterId})`);
|
||||||
|
|
||||||
const feImage = btn.querySelector(".fe-image");
|
const feImage = btn.querySelector(".fe-image");
|
||||||
const redChannel = btn.querySelector(".redchannel");
|
const redChannel = btn.querySelector(".redchannel");
|
||||||
const greenChannel = btn.querySelector(".greenchannel");
|
const greenChannel = btn.querySelector(".greenchannel");
|
||||||
const blueChannel = btn.querySelector(".bluechannel");
|
const blueChannel = btn.querySelector(".bluechannel");
|
||||||
const gaussianBlur = btn.querySelector(".gaussianBlur");
|
const gaussianBlur = btn.querySelector(".gaussianBlur");
|
||||||
|
|
||||||
function generateDisplacementMap() {
|
function generateDisplacementMap() {
|
||||||
const rect = btn.getBoundingClientRect();
|
const rect = btn.getBoundingClientRect();
|
||||||
const actualWidth = rect.width || 140;
|
const actualWidth = rect.width || 140;
|
||||||
const actualHeight = rect.height || 48;
|
const actualHeight = rect.height || 48;
|
||||||
|
|
||||||
const svgContent = `
|
const svgContent = `
|
||||||
<svg viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="red-grad-${index}" x1="100%" y1="0%" x2="0%" y2="0%">
|
<linearGradient id="red-grad-${index}" x1="100%" y1="0%" x2="0%" y2="0%">
|
||||||
@@ -486,36 +478,38 @@ void main() {
|
|||||||
<rect width="${actualWidth}" height="${actualHeight}" rx="${borderRadius}" fill="url(#blue-grad-${index})" style="mix-blend-mode:${mixBlendMode}"/>
|
<rect width="${actualWidth}" height="${actualHeight}" rx="${borderRadius}" fill="url(#blue-grad-${index})" style="mix-blend-mode:${mixBlendMode}"/>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
return "data:image/svg+xml," + encodeURIComponent(svgContent);
|
return "data:image/svg+xml," + encodeURIComponent(svgContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
feImage.setAttribute("href", generateDisplacementMap());
|
feImage.setAttribute("href", generateDisplacementMap());
|
||||||
redChannel.setAttribute("scale", distortionScale + redOffset);
|
redChannel.setAttribute("scale", distortionScale + redOffset);
|
||||||
greenChannel.setAttribute("scale", distortionScale + greenOffset);
|
greenChannel.setAttribute("scale", distortionScale + greenOffset);
|
||||||
blueChannel.setAttribute("scale", distortionScale + blueOffset);
|
blueChannel.setAttribute("scale", distortionScale + blueOffset);
|
||||||
[redChannel, greenChannel, blueChannel].forEach((ch) => {
|
[redChannel, greenChannel, blueChannel].forEach((ch) => {
|
||||||
ch.setAttribute("xChannelSelector", xChannel);
|
ch.setAttribute("xChannelSelector", xChannel);
|
||||||
ch.setAttribute("yChannelSelector", yChannel);
|
ch.setAttribute("yChannelSelector", yChannel);
|
||||||
});
|
});
|
||||||
gaussianBlur.setAttribute("stdDeviation", displace);
|
gaussianBlur.setAttribute("stdDeviation", displace);
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
btn.classList.remove("glass-surface--fallback");
|
btn.classList.remove("glass-surface--fallback");
|
||||||
btn.classList.add("glass-surface--svg");
|
btn.classList.add("glass-surface--svg");
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => setTimeout(update, 0));
|
const observer = new ResizeObserver(() => setTimeout(update, 0));
|
||||||
observer.observe(btn);
|
observer.observe(btn);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initLiquidButtons();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
initLiquidButtons();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const statusEl = document.getElementById("status-message");
|
const statusEl = document.getElementById("status-message");
|
||||||
|
const debugBox = document.getElementById("debug-log-box");
|
||||||
const progressTrack = document.getElementById("progress-track");
|
const progressTrack = document.getElementById("progress-track");
|
||||||
const progressFill = document.getElementById("progress-fill");
|
const progressFill = document.getElementById("progress-fill");
|
||||||
const actionsEl = document.getElementById("update-actions");
|
const actionsEl = document.getElementById("update-actions");
|
||||||
@@ -540,9 +534,24 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showDebug(msg) {
|
||||||
|
if (!debugBox) return;
|
||||||
|
debugBox.style.display = "block";
|
||||||
|
debugBox.textContent = msg;
|
||||||
|
}
|
||||||
|
|
||||||
async function initTauriEvents() {
|
async function initTauriEvents() {
|
||||||
const eventApi = window.__TAURI__?.event || window.__TAURI_INTERNALS__;
|
let eventApi = null;
|
||||||
if (!eventApi || typeof eventApi.listen !== 'function') {
|
|
||||||
|
if (window.__TAURI__?.event && typeof window.__TAURI__.event.listen === 'function') {
|
||||||
|
eventApi = window.__TAURI__.event;
|
||||||
|
} else if (window.__TAURI_INTERNALS__ && typeof window.__TAURI_INTERNALS__.listen === 'function') {
|
||||||
|
eventApi = window.__TAURI_INTERNALS__;
|
||||||
|
} else if (window.__TAURI__ && typeof window.__TAURI__.listen === 'function') {
|
||||||
|
eventApi = window.__TAURI__;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventApi) {
|
||||||
setTimeout(initTauriEvents, 50);
|
setTimeout(initTauriEvents, 50);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -560,13 +569,14 @@ void main() {
|
|||||||
if (progressTrack) progressTrack.style.display = "block";
|
if (progressTrack) progressTrack.style.display = "block";
|
||||||
const percent = data.percent || 0;
|
const percent = data.percent || 0;
|
||||||
if (progressFill) progressFill.style.width = percent + "%";
|
if (progressFill) progressFill.style.width = percent + "%";
|
||||||
setStatus(`Downloading update... ${percent}%`);
|
setStatus(`Downloading update (${percent}%)...`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventApi.listen("update-available", (e) => {
|
eventApi.listen("update-available", (e) => {
|
||||||
const data = e.payload || e;
|
const data = e.payload || e;
|
||||||
if (data) {
|
if (data) {
|
||||||
|
if (progressTrack) progressTrack.style.display = "none";
|
||||||
setStatus(`Update Available: ${data.latest} (Current: ${data.current})`);
|
setStatus(`Update Available: ${data.latest} (Current: ${data.current})`);
|
||||||
if (actionsEl) actionsEl.style.display = "flex";
|
if (actionsEl) actionsEl.style.display = "flex";
|
||||||
}
|
}
|
||||||
@@ -575,16 +585,17 @@ void main() {
|
|||||||
eventApi.listen("ready", (e) => {
|
eventApi.listen("ready", (e) => {
|
||||||
const data = e.payload || e;
|
const data = e.payload || e;
|
||||||
if (data && data.url) {
|
if (data && data.url) {
|
||||||
|
if (progressTrack) progressTrack.style.display = "none";
|
||||||
|
if (actionsEl) actionsEl.style.display = "none";
|
||||||
setStatus("Launching...");
|
setStatus("Launching...");
|
||||||
window.location.href = data.url;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventApi.listen("error", (e) => {
|
eventApi.listen("error", (e) => {
|
||||||
const data = e.payload || e;
|
const data = e.payload || e;
|
||||||
if (data && data.message) {
|
const msg = data && data.message ? data.message : JSON.stringify(data);
|
||||||
setStatus(`Error: ${data.message}`, "#ef4444");
|
setStatus("Runtime Error", "#fca5a5");
|
||||||
}
|
showDebug(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,7 +228,8 @@ class UpdateClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkForUpdates() {
|
async checkForUpdates() {
|
||||||
const releases = await this.fetchJson(RELEASES_API);
|
const cacheBuster = `?cb=${Date.now()}`;
|
||||||
|
const releases = await this.fetchJson(RELEASES_API + cacheBuster);
|
||||||
if (!releases || releases.length === 0) {
|
if (!releases || releases.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user