Mobile builds¶
The Android and iOS apps are the web console wrapped in Capacitor 7, plus a native call plugin that gives incoming calls a real OS call experience.
App identity¶
From frontend/capacitor.config.ts:
appId:com.towit.linksappName:TowitLinkswebDir:build— the static output ofpnpm build:app- A SplashScreen configuration, and a BackgroundRunner job
(
runners/calls.service.js) that repeats every 15 seconds withautoStart(:12-19)
The version lives in frontend/android/version.properties, not in package.json.
The native call plugin¶
CallPlugin (frontend/src/lib/plugins/call_plugin.ts:33) is the bridge. Its Android
implementation is Kotlin under
frontend/android/app/src/main/java/com/towit/links/plugins/:
| File | Role |
|---|---|
CallPlugin.kt |
The Capacitor plugin surface |
services/CallService.kt |
Foreground service keeping the call alive |
call/CallListener.kt |
Twilio call events |
CallBroadcastReceiver.kt |
System intents — answer, reject, hang up from the notification |
sound/SoundPoolManager.kt |
Ringtone and in-call tones |
The iOS counterpart is under frontend/ios/App/App/plugin. Call hold is implemented here
and only here; the web client returns false from _toggleCallOnHold().
Local device builds¶
cd frontend
pnpm build:android # vite build && cap sync android && cap open android
For hot reload on a device, uncomment the server.url block in capacitor.config.ts:21-28:
use http://10.0.2.2:5001 for the Android emulator, or your workstation's LAN IP for a
physical device or iOS. frontend/android.md has the longer walkthrough, including the
network_security_config.xml cleartext exception and
npx capacitor-assets generate for icons and splash screens.
Gradle shortcuts exist as npm scripts: assemble:debug, assemble:release and
bundle:release.
Release builds¶
Releases are produced on a dedicated build host (154.53.40.104, repository at
~/build_apk/VoIP), triggered manually through .github/workflows/android.yml, which SSHes
in and runs rebuild_android.sh.
What the script does:
git pull, then readfrontend/android/version.properties- Build the image
voip_apk_builderfromfrontend/Dockerfile.build_apk - Run it with the repository bind-mounted at
/app. Inside the container (Dockerfile.build_apk:49-66):npm install --legacy-peer-deps,npm run build:app,npx cap sync android, thengradlew assembleDebug,assembleRelease,bundleReleaseandincrementSubversion - Collect the APK, AAB, mapping file and debug symbols into
android_outputs/output_<name>_<code>/, zipping the stripped symbols - Commit and push the bumped
version.properties(rebuild_android.sh:67-69) - Prune Docker
Step 5 means the build host writes to the repository. A release therefore produces a commit you did not make locally; pull before starting new work after a build.
Artifacts are served from the staging host at staging.towitlinks.com/android/.
Signing¶
The release keystore is towitlink.jks. It is no longer in the repository — it was
removed because a signing key in version control is a signing key anyone with repository
access can use. Keep a copy outside the repository and place it on the build host manually.
The keystore password is still documented in plain text
frontend/android.md:22 contains the keystore password in the example command. An
Android signing key cannot be rotated for an app already published without Play App
Signing — the key is the app's identity. Treat both the keystore and that password as
compromised and decide, with the team, what the remediation is. See
Known issues.