fix: Android full tunnel mode requires credentials + deployment IDs UI refactor (#124)

Real bug I introduced in #94: Full mode was skipping the credential check that apps_script mode enforces, but Full mode does talk to CodeFull.gs on Apps Script and needs the same auth_key + deployment ID. Users flipping to Full mode with empty fields would silently fail.

Two sites fixed:
- MhrvVpnService.kt — changed `mode == APPS_SCRIPT` gate to `mode != GOOGLE_ONLY`
- HomeScreen.kt — removed the `cfg.mode == Mode.FULL` bypass in the Start button's enabled-state

Also includes a UX improvement for the Deployment IDs editor (per-row field with add/remove buttons instead of raw newline-separated text), which makes multi-deployment setups easier to manage on Android.

Rust-side 75 tests still green, Kotlin compiles clean. Android-only diff so no Rust CI impact.
This commit is contained in:
vahidlazio
2026-04-24 16:15:12 +02:00
committed by GitHub
parent 9e2b8e5f3e
commit f9f4845567
4 changed files with 82 additions and 28 deletions
@@ -93,11 +93,13 @@ class MhrvVpnService : VpnService() {
// the instant I tap Start". See issue #73.
startForeground(NOTIF_ID, buildNotif(cfg.listenPort))
// Deployment ID + auth key are only required in apps_script mode.
// google_only (bootstrap) and full (tunnel) modes run without them.
val needsAppsScriptCreds = cfg.mode == Mode.APPS_SCRIPT
if (needsAppsScriptCreds && (!cfg.hasDeploymentId || cfg.authKey.isBlank())) {
Log.e(TAG, "Config is incomplete — can't start proxy in apps_script mode")
// Deployment ID + auth key are required for apps_script and full
// modes — both talk to Apps Script. Only google_only (bootstrap)
// runs without them. Closes #73 regression where google_only
// users hit this branch and crashed on startForeground timeout.
val needsCreds = cfg.mode != Mode.GOOGLE_ONLY
if (needsCreds && (!cfg.hasDeploymentId || cfg.authKey.isBlank())) {
Log.e(TAG, "Config is incomplete — deployment ID + auth key required for ${cfg.mode}")
try { stopForeground(STOP_FOREGROUND_REMOVE) } catch (_: Throwable) {}
stopSelf()
return