6 Commits

Author SHA1 Message Date
c869101923 chore: release v1.0.6 2026-09-09 14:48:05 -07:00
feccab9aa1 update 2026-09-09 14:39:54 -07:00
82c91334f6 fixed security issues, ty zstrike 2026-09-09 14:36:37 -07:00
a0de7b4bca chore: release v1.0.3 2026-09-08 18:07:48 -07:00
119b0ad28f removed the weird player logo on streams 2026-09-08 18:06:10 -07:00
6d38d9c335 v1.0.2 2026-09-08 16:58:04 -07:00
9 changed files with 201 additions and 32 deletions

View File

@@ -1,3 +1,6 @@
## Development & Build
Install dependencies: Install dependencies:
```bash ```bash
npm install npm install
@@ -13,11 +16,18 @@ Compile and assemble the release APK:
npm run build:apk npm run build:apk
``` ```
Release to GitLab: ## Release
```bash
Set credentials once per session, then run release:
```powershell
$env:ANDROID_HOME = "$env:LOCALAPPDATA\Android\Sdk"
$env:NDK_HOME = "$env:LOCALAPPDATA\Android\Sdk\ndk\29.0.13846066"
$env:GITLAB_TOKEN = "your-token-here"
npm run release npm run release
``` ```
This builds the payload, compiles the APK for arm64 and x86_64, commits and tags the version, uploads all artifacts, and creates the GitLab release automatically.
## Logs ## Logs
Stream live device logs: Stream live device logs:
@@ -35,7 +45,7 @@ Dump logs to file:
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d -s VylaGo > vyla-go.log & "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d -s VylaGo > vyla-go.log
``` ```
Show all crash/fatal logs: Show all crash and fatal logs:
```powershell ```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d | Select-String "VylaGo|AndroidRuntime|FATAL" & "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d | Select-String "VylaGo|AndroidRuntime|FATAL"
``` ```

View File

@@ -3,31 +3,17 @@
Vyla Go is the native Android mobile launcher for the Vyla media ecosystem. Vyla Go is the native Android mobile launcher for the Vyla media ecosystem.
## Features ## Features
- **Native Android APK Architecture**: Built on Tauri v2 with native hardware acceleration. - **Native Android APK Architecture**: Built on Tauri v2 with native hardware acceleration.
- **Automated Resource Management**: Downloads, unpacks, and updates core services directly from official releases. - **Automated Resource Management**: Downloads, unpacks, and updates core services directly from official releases.
- **Hardware Shader Interface**: Real-time WebGL shader lighting and glassmorphism elements. - **Hardware Shader Interface**: Real-time WebGL shader lighting and glassmorphism elements.
## Prerequisites ## Prerequisites
- Node.js 20+ - Node.js 20+
- Rust & Cargo - Rust & Cargo
- Android Studio with Android SDK & NDK 29+ - Android Studio with Android SDK & NDK 29+
- Toolchain targets: - Toolchain targets:
```bash
rustup target add aarch64-linux-android
```
## Development & Build
Install dependencies:
```bash ```bash
npm install rustup target add aarch64-linux-android x86_64-linux-android
```
Compile and assemble the release APK:
```bash
npm run build:apk
```
Release packages to GitLab:
```bash
npm run release
``` ```

View File

@@ -20,33 +20,91 @@ function cleanBuildDir() {
fs.mkdirSync(BUILD_DIR, { recursive: true }); fs.mkdirSync(BUILD_DIR, { recursive: true });
} }
function removeSensitiveFiles(dir) {
if (!fs.existsSync(dir)) return;
const gitDir = path.join(dir, '.git');
if (fs.existsSync(gitDir)) {
fs.rmSync(gitDir, { recursive: true, force: true });
}
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === '.git') {
fs.rmSync(fullPath, { recursive: true, force: true });
} else {
removeSensitiveFiles(fullPath);
}
} else {
if (
entry.name.startsWith('.env') ||
entry.name.endsWith('.key') ||
entry.name.endsWith('.pem') ||
entry.name === 'credentials.json' ||
entry.name === '.gitlab-ci.yml'
) {
fs.rmSync(fullPath, { force: true });
}
}
}
}
function clonePrivateRepo(repoConfig, targetDir) { function clonePrivateRepo(repoConfig, targetDir) {
if (fs.existsSync(targetDir)) { if (fs.existsSync(targetDir)) {
fs.rmSync(targetDir, { recursive: true, force: true }); 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 repoUrl = `https://gitlab.com/${GITLAB_GROUP}/${repoConfig.name}.git`;
const repoUrl = `https://${auth}gitlab.com/${GITLAB_GROUP}/${repoConfig.name}.git`;
execSync(`git clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} ${targetDir}`, { stdio: 'inherit' }); try {
if (token) {
const authHeader = Buffer.from(`oauth2:${token.trim()}`).toString('base64');
execSync(
`git -c "http.extraHeader=Authorization: Basic ${authHeader}" clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} "${targetDir}"`,
{ stdio: 'inherit' }
);
} else {
execSync(
`git clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} "${targetDir}"`,
{ stdio: 'inherit' }
);
}
} finally {
const gitFolder = path.join(targetDir, '.git');
if (fs.existsSync(gitFolder)) {
fs.rmSync(gitFolder, { recursive: true, force: true });
}
}
} }
async function build() { async function build() {
cleanBuildDir(); cleanBuildDir();
for (const [key, config] of Object.entries(REPO_CONFIG)) { for (const [key, config] of Object.entries(REPO_CONFIG)) {
const targetDir = path.join(__dirname, key); const targetDir = path.join(__dirname, key);
console.log(`Cloning ${config.name}...`);
clonePrivateRepo(config, targetDir); clonePrivateRepo(config, targetDir);
console.log(`Installing production dependencies for ${config.name}...`);
execSync('npm install --omit=dev', { cwd: targetDir, stdio: 'inherit' }); execSync('npm install --omit=dev', { cwd: targetDir, stdio: 'inherit' });
removeSensitiveFiles(targetDir);
} }
const zip = new AdmZip(); const zip = new AdmZip();
const configDir = path.join(__dirname, 'config'); const configDir = path.join(__dirname, 'config');
if (fs.existsSync(configDir)) { if (fs.existsSync(configDir)) {
removeSensitiveFiles(configDir);
zip.addLocalFolder(configDir, 'config'); zip.addLocalFolder(configDir, 'config');
} }
for (const repo of REPOS) { for (const repo of REPOS) {
const srcRepo = path.join(__dirname, repo); const srcRepo = path.join(__dirname, repo);
removeSensitiveFiles(srcRepo);
zip.addLocalFolder(srcRepo, repo); zip.addLocalFolder(srcRepo, repo);
} }
@@ -54,6 +112,20 @@ async function build() {
if (fs.existsSync(zipPath)) fs.rmSync(zipPath, { force: true }); if (fs.existsSync(zipPath)) fs.rmSync(zipPath, { force: true });
zip.writeZip(zipPath); zip.writeZip(zipPath);
const verificationZip = new AdmZip(zipPath);
const leakedEntries = verificationZip.getEntries().filter(e =>
e.entryName.includes('.git/') ||
e.entryName.includes('.git\\') ||
e.entryName.startsWith('.env') ||
e.entryName.includes('/.env') ||
e.entryName.endsWith('config/encrypted-credentials.json')
);
if (leakedEntries.length > 0) {
fs.rmSync(zipPath, { force: true });
throw new Error(`SECURITY ABORT: Archive contains forbidden files:\n${leakedEntries.map(e => e.entryName).join('\n')}`);
}
const hash = crypto.createHash('sha256'); const hash = crypto.createHash('sha256');
hash.update(fs.readFileSync(zipPath)); hash.update(fs.readFileSync(zipPath));
const checksum = hash.digest('hex'); const checksum = hash.digest('hex');
@@ -68,6 +140,8 @@ async function build() {
for (const repo of REPOS) { for (const repo of REPOS) {
fs.rmSync(path.join(__dirname, repo), { recursive: true, force: true }); fs.rmSync(path.join(__dirname, repo), { recursive: true, force: true });
} }
console.log('Build complete and verified clean.');
} }
build().catch(err => { build().catch(err => {

View File

@@ -1,6 +1,6 @@
{ {
"name": "vyla-go", "name": "vyla-go",
"version": "1.0.2", "version": "1.0.6",
"description": "Vyla Go Mobile Launcher", "description": "Vyla Go Mobile Launcher",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@@ -32,11 +32,21 @@ function incrementVersion() {
function gitCommitAndTag(version) { function gitCommitAndTag(version) {
try { try {
execSync('git add package.json src-tauri/tauri.conf.json', { stdio: 'inherit' }); execSync('git add package.json src-tauri/tauri.conf.json', { stdio: 'inherit' });
execSync(`git commit -m "chore: release ${version}"`, { stdio: 'inherit' }); try {
execSync(`git tag -a ${version} -m "Release ${version}"`, { stdio: 'inherit' }); execSync(`git commit -m "chore: release ${version}"`, { stdio: 'inherit' });
} catch (_) {
console.log('Working tree clean, skipping commit.');
}
try {
execSync(`git tag -a ${version} -m "Release ${version}"`, { stdio: 'inherit' });
} catch (_) {
console.log(`Tag ${version} already exists locally.`);
}
execSync('git push origin HEAD --tags', { stdio: 'inherit' }); execSync('git push origin HEAD --tags', { stdio: 'inherit' });
} catch (e) { } catch (e) {
console.warn('Git operations failed (non-fatal):', e.message); console.warn('Git push failed (will fallback to GitLab API ref):', e.message);
} }
} }
@@ -94,6 +104,11 @@ async function createRelease(version, assets) {
const payload = assets.payload; const payload = assets.payload;
const manifest = assets.manifest; const manifest = assets.manifest;
let branch = 'main';
try {
branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim() || 'main';
} catch (_) { }
const description = [ const description = [
`## Automated Release ${version}`, `## Automated Release ${version}`,
'', '',
@@ -110,6 +125,7 @@ async function createRelease(version, assets) {
const body = JSON.stringify({ const body = JSON.stringify({
name: `Vyla Go Release ${version}`, name: `Vyla Go Release ${version}`,
tag_name: tag, tag_name: tag,
ref: branch,
description, description,
assets: { assets: {
links: [ links: [
@@ -137,11 +153,22 @@ async function main() {
const version = `v${rawVersion}`; const version = `v${rawVersion}`;
console.log(`Releasing ${version}...`); console.log(`Releasing ${version}...`);
const androidHome = process.env.ANDROID_HOME || `${process.env.LOCALAPPDATA}\\Android\\Sdk`;
const ndkHome = process.env.NDK_HOME || `${androidHome}\\ndk\\29.0.13846066`;
const buildEnv = {
...process.env,
ANDROID_HOME: androidHome,
NDK_HOME: ndkHome,
GITLAB_TOKEN: TOKEN,
VYLA_GITLAB_TOKEN: TOKEN,
};
console.log('Building payload...'); console.log('Building payload...');
execSync('npm run build:payload', { stdio: 'inherit' }); execSync('npm run build:payload', { stdio: 'inherit', env: buildEnv });
console.log('Building APK...'); console.log('Building APK...');
execSync('npm run build:apk', { stdio: 'inherit' }); execSync('npm run build:apk', { stdio: 'inherit', env: buildEnv });
const apkSearchDirs = [ const apkSearchDirs = [
path.join(__dirname, 'src-tauri', 'gen', 'android', 'app', 'build', 'outputs', 'apk', 'universal', 'release'), path.join(__dirname, 'src-tauri', 'gen', 'android', 'app', 'build', 'outputs', 'apk', 'universal', 'release'),

View File

@@ -1,9 +1,36 @@
package com.vyla.go package com.vyla.go
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.webkit.WebChromeClient
import android.webkit.WebView
class MainActivity : TauriActivity() { class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val root = findViewById<ViewGroup>(android.R.id.content)
root.viewTreeObserver.addOnGlobalLayoutListener {
findAndHookWebView(root)
}
}
private fun findAndHookWebView(view: View) {
if (view is WebView) {
view.webChromeClient = object : WebChromeClient() {
override fun getDefaultVideoPoster(): Bitmap {
return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
}
}
return
}
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
findAndHookWebView(view.getChildAt(i))
}
}
} }
} }

View File

@@ -21,7 +21,6 @@ open class BuildTask : DefaultTask() {
runTauriCli(executable) runTauriCli(executable)
} catch (e: Exception) { } catch (e: Exception) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
// Try different Windows-specific extensions
val fallbacks = listOf( val fallbacks = listOf(
"$executable.exe", "$executable.exe",
"$executable.cmd", "$executable.cmd",

View File

@@ -323,6 +323,45 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
Ok(base_dir) Ok(base_dir)
} }
const ANDROID_VIDEO_FIX_SCRIPT: &str = r#"
(function() {
function suppressNativeOverlay() {
const styleId = '__vyla_native_video_fix';
if (!document.getElementById(styleId)) {
const s = document.createElement('style');
s.id = styleId;
s.textContent = `
video::-webkit-media-controls,
video::-webkit-media-controls-enclosure,
video::-webkit-media-controls-panel,
video::-webkit-media-controls-overlay-play-button,
video::-webkit-media-controls-start-playback-button {
display: none !important;
-webkit-appearance: none !important;
opacity: 0 !important;
pointer-events: none !important;
}
`;
(document.head || document.documentElement).appendChild(s);
}
document.querySelectorAll('video').forEach(v => {
v.setAttribute('playsinline', '');
v.setAttribute('webkit-playsinline', '');
v.removeAttribute('poster');
});
}
suppressNativeOverlay();
new MutationObserver(suppressNativeOverlay).observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['poster']
});
})();
"#;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
@@ -389,6 +428,10 @@ pub fn run() {
.setup(|app| { .setup(|app| {
let handle = app.handle().clone(); let handle = app.handle().clone();
if let Some(window) = handle.get_webview_window("main") {
let _ = window.eval(ANDROID_VIDEO_FIX_SCRIPT);
}
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
std::thread::sleep(std::time::Duration::from_millis(400)); std::thread::sleep(std::time::Duration::from_millis(400));
@@ -398,7 +441,10 @@ pub fn run() {
match download_and_extract_payload(handle.clone(), window.clone()).await { match download_and_extract_payload(handle.clone(), window.clone()).await {
Ok(_) => { Ok(_) => {
std::thread::sleep(std::time::Duration::from_millis(400)); std::thread::sleep(std::time::Duration::from_millis(400));
let _ = window.eval("window.location.replace('http://vyla.localhost/frontend/index.html');"); let _ = window.eval(&format!(
"{}; window.location.replace('http://vyla.localhost/frontend/index.html');",
ANDROID_VIDEO_FIX_SCRIPT
));
} }
Err(err) => { Err(err) => {
update_ui( update_ui(

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Vyla Go", "productName": "Vyla Go",
"version": "1.0.2", "version": "1.0.6",
"identifier": "com.vyla.go", "identifier": "com.vyla.go",
"build": { "build": {
"beforeDevCommand": "", "beforeDevCommand": "",