herdr-web includes a Capacitor Android shell that bundles the React/Vite app from
web/dist and connects only API/WebSocket traffic to a configured Herdr bridge.
The Android app does not run Herdr and does not fetch the web UI from a bridge. A bridge must already be running on another machine or on the same network.
- Capacitor config:
capacitor.config.ts - Android project:
android/ - Android application id:
dev.herdr.web - Bundled web assets source:
web/dist - Runtime profile storage: Capacitor Preferences on Android, browser
localStorageelsewhere
The generated native project is committed, but generated sync/build outputs remain ignored:
android/app/src/main/assets/publicandroid/app/src/main/assets/capacitor.config.jsonandroid/app/src/main/assets/capacitor.plugins.jsonandroid/app/src/main/res/xml/config.xmlandroid/capacitor-cordova-android-pluginsandroid/app/buildandroid/build
Run npm run android:sync before opening or building Android from a fresh checkout.
The browser-served web app still defaults to the bridge that served the page. The bundled Android app has no serving bridge origin, so it starts disconnected until the user adds and activates a saved bridge in the Bridge area of Settings.
Supported backend examples:
http://192.168.1.20:4000
http://10.0.0.42:8787
http://herdr-host.local:4000
https://herdr.example.test
The web app validates backend URLs before saving them:
- accepted URLs must be origin-style HTTP or HTTPS URLs;
- credentials, paths, query strings, and fragments are rejected;
- HTTP and HTTPS URLs may use any valid hostname or IP literal;
- hostnames are syntax-validated in the app and still must be accepted by the bridge Host policy.
The Android WebView app origin is http://localhost. A LAN bridge must allow that origin before
the Android app can call /api/* or /ws/* endpoints:
HOST=0.0.0.0 PORT=4000 scripts/run-bridge.sh --allow-origin http://localhostIf the Android backend URL uses a DNS hostname instead of an IP literal, also allow the exact hostname in the bridge Host policy:
HOST=0.0.0.0 PORT=4000 scripts/run-bridge.sh \
--allow-origin http://localhost \
--allow-host herdr-host.local--allow-origin is the Android-relevant setting because the bundled WebView does not load its page
from a bridge. Browser-served multi-bridge pages have an additional Content Security Policy gate on
the bridge serving the page; use --allow-connect-origin http://other-bridge:8787 there when that
page should connect to another bridge.
The Android shell currently enables Capacitor cleartext support:
server: {
androidScheme: "http",
cleartext: true,
},This is intentional for the current Herdr bridge workflow because user-managed bridge URLs are often
plain http://host:port, including LAN and mesh-network addresses. The app does not enable
mixed-content mode because its WebView origin is also HTTP. Treat cleartext bridge URLs as a user
trust decision: only point the app at Herdr bridges on networks you trust. For a public or
store-distributed build, prefer HTTPS bridge URLs and revisit whether cleartext should remain enabled.
Android cloud auto-backup is disabled for the shell, so saved bridge profiles are not copied into device backup storage.
The Android shell uses the same area-based Settings UI described in the project README. Android starts disconnected, so the Bridge area is required for selecting a backend. On narrow screens the area selector appears as horizontal tabs. Android-specific touch behavior lives in the Mobile area.
- Node.js 22 or newer
- npm
- JDK 21
- Android SDK command-line tools
- Android SDK Platform 36
- Android SDK Build Tools 36.0.0
- Android SDK Platform Tools
Set these environment variables when using command-line SDK tools:
export ANDROID_HOME="$HOME/.local/android-sdk"
export ANDROID_SDK_ROOT="$ANDROID_HOME"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"If Gradle is run without those environment variables, create the ignored local SDK pointer instead:
# android/local.properties
sdk.dir=/absolute/path/to/android-sdkKnown verified versions:
- OpenJDK 21
- Android SDK command-line tools 20.0
- Android SDK Platform 36
- Android SDK Build Tools 36.0.0
- Android SDK Platform Tools 37.0.0
Install dependencies:
npm install
npm install --prefix webSync the web build into Android:
npm run android:syncBuild a debug APK:
npm run android:build:debugThe debug APK is written to:
android/app/build/outputs/apk/debug/app-debug.apk
Open in Android Studio after syncing:
npm run android:openThis branch was command-line verified with:
HERDR_SRC=/path/to/herdr scripts/check-vendor.sh
npm run check
npm run android:build:debugThe debug APK is produced at android/app/build/outputs/apk/debug/app-debug.apk. Install that
artifact on a device or emulator for the manual smoke checklist below.
On a trusted LAN:
-
Start the bridge, for example:
HOST=0.0.0.0 PORT=4000 scripts/run-bridge.sh --allow-origin http://localhost
-
Install the debug APK on an Android device.
-
Open the app and confirm the shell loads without network access.
-
Open Settings and select the Bridge area.
-
Add a backend such as
http://192.168.1.20:4000. -
Use
Testand confirm it reports reachable. -
Use
Save, enable the saved bridge with its Enable toggle, and confirm the bridge chip appears in the sidebar. -
Confirm snapshot, event updates, terminal attach, text input, stage-only input, uploads, and pane controls work.
-
Open the Terminal area, change input transport and batching delay, and confirm terminal input still works.
-
Open the Mobile area, toggle the mobile terminal tap setting, and confirm terminal taps can focus either the text input or terminal.
-
Force-close and reopen the app; confirm the active backend and settings persist.
-
Test Android back behavior from the mobile sidebar/detail views.
-
Test an unreachable backend and confirm the app stays usable enough to edit settings.
The generated debug APK is unsigned for distribution. A production release still needs:
- final Android application id decision;
- app icon and splash assets;
- signing key and release signing config;
- release build command/checklist;
- device/emulator smoke testing;
- release validation for
--allow-origin http://localhostand any documented--allow-hostnames; - backup policy review before any secret or pairing-token storage is added;
- a decision on whether HTTP cleartext remains enabled for production builds.