Sample Projects¶
Two reference clients are released under the same license as the SDKs. Use them as a starting point, copy-paste what you need, or bundle them as-is with your own branding.
| Project | Stack | Repository |
|---|---|---|
| leaf-android | Kotlin + Jetpack Compose | github.com/shiroedev2024/leaf-android |
| leaf-desktop | Tauri 2 + Vue 3 + TypeScript | github.com/shiroedev2024/leaf-desktop |
leaf-android¶
A full-featured demo that embeds leaf-sdk-android and showcases every SDK entry point.
Features¶
- Connect / disconnect VPN from a Compose dashboard.
- Outbound group selector with live ping (via the Runtime HTTP API).
- Memory log viewer (
api.getLogs). - Quick Settings tile (
LeafVPNTileService). leafvpn://install?clientId=...deep-link handler that auto-provisions a profile.- Import / view
.leafsuboffline bundles throughACTION_VIEW. - 8-language localization (English, Persian, Spanish, Arabic, Indonesian, Russian, Turkish, Uzbek).
- In-app updater that reads the latest
version.propertiesfrom the release artifact.
File map¶
| Path | Responsibility |
|---|---|
app/src/main/java/.../MainApplication.kt |
Binds ServiceManagement, pushes a LeafConfig. |
app/src/main/java/.../activity/BaseActivity.kt |
Binds / unbinds the :VPNService process on onStart / onStop. |
app/src/main/java/.../activity/MainActivity.kt |
Permission requests, VPN consent intent, first-run setup, updateAssets. |
app/src/main/java/.../viewmodel/LeafViewModel.kt |
All state + async calls; mirrors SDK listeners into LiveData. |
app/src/main/java/.../VpnEventReceiver.kt |
Receives secure broadcasts sent from LeafVPNService. |
app/src/main/java/.../component/*.kt |
Compose UI screens (Dashboard, Outbounds, Settings, UpdateScreen). |
app/src/main/AndroidManifest.xml |
Permissions + QS_TILE + leafvpn:// + .leafsub handlers. |
Building¶
git clone https://github.com/shiroedev2024/leaf-android.git
cd leaf-android
# Dockerized build (recommended):
./build.sh apk
# OR Gradle directly:
./gradlew :app:assembleRelease
The build.sh script mounts your local config.toml with a Kellnr token so the Rust side of leaf-sdk-android can be rebuilt from source if you clone the SDK submodule.
Integration patterns worth copying¶
- Separate processes, separate listeners — binding is done from
BaseActivity, but the VPN service lives in its own:VPNServiceprocess.MainApplication.onCreate()checksisMainProcess(context)before binding so the VPN process does not bind to itself, andLeafViewModelre-subscribes on everyServiceListener.onConnect(). LeafConfigset once on bind —MainApplicationpushes aLeafConfig.Builder()with the session name, notification strings, and a URI-serializedIntentso tapping the VPN notification opensMainActivity.- State-based UI — every SDK callback is converted to a sealed class (
LeafState.Started,SubscriptionState.Fetching, …) exposed throughLiveData/StateFlow, so UI composables stay dumb. - Three-level outbound picker —
LeafViewModel.getOutboundList(tag)queriesOUTby default,_selectedSubgroupTagdrills into a country subgroup, andchangeSelectedOutboundalso updatesOUT → <country>so routing follows. See Proxy Groups & Naming for the tag convention. - Parallel pings —
LeafViewModel.refreshPings()spawns one coroutine per outbound againstapi.getOutboundHealth(name); fall-through fromtcpMstoudpMs. - Memory log tail —
LeafViewModel.startLogger()pollsapi.getLogs(200, offset)every second whenmemory_logger=true, feedingMemoryLoggerActivity. - Offline
.leafsubimports —importOfflineSubscription(path, passphrase, keyIds, verifyingKeys, cb)is wired toIntent.ACTION_VIEW(see the<data android:pathPattern=".*\\.leafsub"/>filters in the manifest). The sample ships with a pre-installed Ed25519 verifying key so subscriptions signed by the panel pass offline. leafvpn://install?profile=<base64>deep link —AutoProfileActivitybase64-decodes theprofilequery parameter (a UUID) and callssm.updateSubscription(uuid, cb). The same scheme is used by desktop and any QR-code flow you design.- Quick Settings Tile —
LeafVPNTileServicebinds insideonStartListening, flips the tile toSTATE_ACTIVEonLeafListener.onStartSuccess, and toggles viaServiceManagement.isLeafRunning. - Auto-update channel —
UpdateChecker.fetchUpdate(arch, currentVersion)hits<base>/downloads/android/<arch>/<currentVersion>with retries and surfaces a JSONUpdateResponse. The sample setsBASE_URL_RELEASEto your panel — see UpdateConstants.kt. - 8-language localisation —
LocaleAwareApplication+MainApplication.languagesmap (en,fa,ar,es,in,ru,tr,uz). User changesdocument.cookie-style via the settings screen.
leaf-desktop¶
A Tauri 2 application that embeds leaf_sdk_desktop. Ships signed installers for Windows (MSI), macOS (DMG), and Linux (deb / AppImage).
Features¶
- Dashboard with connection toggle, usage graph, ping, and log tail.
- Subscription management UI (enter client ID, auto/manual update, import
.leafsub). - Outbound group selector with live failover status.
- System tray icon with start/stop and a "show main window" action.
- Auto-updater (tauri-plugin-updater) and single-instance guard (
leafvpn://deep links focus the existing window). - Window-state persistence and dark/light theme.
File map¶
| Path | Responsibility |
|---|---|
src-tauri/Cargo.toml |
Depends on leaf_sdk_desktop from Kellnr. |
src-tauri/build.rs |
Uses leaf-build-deps to fetch the matching leaf-ipc binary and copies it into sidecar_cache/. |
src-tauri/src/main.rs |
Wraps every SDK function as a #[tauri::command]; registers tray + deep-link handlers. |
src-tauri/src/window_manager.rs |
Show/hide/toggle main window with persistent state. |
src-tauri/src/tray.rs / tray_icon_manager.rs |
Tray icon updates that follow LATEST_LEAF_STATE. |
src/ |
Vue 3 + Pinia UI, listens for core-event / leaf-event / subscription-event. |
Building¶
git clone https://github.com/shiroedev2024/leaf-desktop.git
cd leaf-desktop
yarn install
yarn tauri dev # dev run
yarn tauri build # platform installers in src-tauri/target/release/bundle
Requires Rust, Node 16+, and platform build dependencies for Tauri (see the Tauri docs for your OS). Provide a Kellnr token via CARGO_REGISTRIES_KELLNR_TOKEN so the SDK can be fetched.
Integration patterns worth copying¶
- Build-time sidecar fetching —
src-tauri/build.rscallsLeafBuilder::new().version("1.4.1").build()to download, verify, and unzip the rightleaf-ipcfromstatic-public;tauri.conf.jsonthen registers it asexternalBin. See Distribution & Registries. - Single source of truth for state —
LATEST_CORE_STATE,LATEST_LEAF_STATE,LATEST_SUBSCRIPTION_STATE,PENDING_LEAFSUB_PATHSall live inonce_cell::Lazy<Mutex<Option<...>>>. Tray icon and frontend both read from these globals, so a core restart survives hot-reloads. - Event bus — every callback emits a named Tauri event (
core-event,leaf-event,subscription-event,file-watch-event,file-opened).src/store/leaf.tsis the canonical Pinia store that subscribes. - Liveness ping loop —
useLeafStore.startPingTimer()callsinvoke('ping')every 5 s afterCoreState::STARTED; two consecutive failures triggerresetLeafState()so the UI recovers when the core is killed externally. leafvpn://install?profile=<base64>deep link —src/store/deeplink.tslistens viatauri-plugin-deep-link::onOpenUrland falls back togetCurrent()for cold-starts from OS launchers. Supports both standard and URL-safe base64 (auto-pads)..leafsubfile associations —bundle.fileAssociationsintauri.conf.jsonregisters the extension, andtauri-plugin-single-instancefunnels dropped paths intoPENDING_LEAFSUB_PATHS→file-openedevent →import_offline_subscription.- Quit-safety — the tray
Quitaction refuses to exit while the core is running; it shows a native notification viatauri-plugin-notificationinstead. - Windows Wintun management —
leaf_sdk_desktop::setup_wintunwriteswintun.dllnext toleaf-ipcon startup, andremove_wintun_dll()runs onRunEvent::ExitRequested. - Window state —
tauri-plugin-window-statepersists position / size / maximise / visibility towindow_state.json. - File watcher —
start_file_watcherwatches theleaf-ipcbinary path; when the app's auto-updater replaces the sidecar the frontend getsfile-watch-eventand can show a "restart required" banner. - Built-in auto-updater —
tauri-plugin-updatersigns artefacts with minisign (pubkey intauri.conf.json) and pollshttps://litevpn.top/downloads/tauri/{{target}}/{{arch}}/{{current_version}}; replace with your own URL / pubkey when forking. - Linux distro detection —
helper::detect_linux_system_infosurfacesapt/rpm/pacmanso the UI can show the correct install instruction for the right distro.
Using the samples as a template¶
Both projects are released under the Apache 2.0 license. Recommended workflow to bootstrap your own client:
- Fork the matching sample repo (
leaf-androidorleaf-desktop). - Replace icons, strings, package names, and branded URLs.
- Point the app at your own panel (
updateSubscription("YOUR_CLIENT_UUID")or deep-link host). - Keep the version passed to
updateAssetsin sync with the SDK version you depend on — see Architecture — Asset Versioning Rule. - Sign & distribute.
Issues / questions¶
Contributions and issues are tracked in the GitHub repositories: