ENGINEERING SPECS
OrbitMesh Client Architecture
Goal
Build one cross-platform OrbitMesh client with Flutter for shared UI and product logic, while isolating proxy, VPN, and operating-system integration behind platform adapters.
The first implementation target was a desktop MVP; the Android VPN and an
account-first product shell have since shipped on top of it (see
docs/specs/client-implementation-plan.md for phase status). This document
describes the enduring architecture; per-capability detail lives in
apps/client/README.md.
High-Level Architecture
Flutter App Shell
|
| uses
v
Dart Services
|-- SubscriptionService
|-- ProfileCache
|-- MihomoControllerClient
|-- ConnectionCoordinator
|-- SystemProxyController interface
|-- VpnController interface
|
| MethodChannel / process API
v
Platform Adapters
|-- macOS helper / process runner
|-- Windows process runner / proxy helper
|-- Linux process runner / proxy helper
|-- Android VpnService
|-- iOS NetworkExtension
|
v
Mihomo / Clash Meta Runtime
Shared Dart Responsibilities
- Authenticate the account (access + refresh tokens) and select the tenant.
- Provision the subscription invisibly (create/rotate) and persist it as a
scoped
SubscriptionBindingin secure storage; persist user preferences. - Fetch OrbitMesh Clash profile from
/subscription/{token}/clash. - Validate that the response is non-empty YAML-like text before activating it.
- Store active and pending profiles atomically.
- Generate local Mihomo controller secret and loopback controller address.
- Coordinate connection state transitions.
- Read Mihomo controller API for version, proxies, traffic, and connections.
- Present logs and diagnostics in a platform-neutral model.
Desktop Runtime Model
Desktop uses Mihomo as a local child process for the MVP.
The client owns a profile directory:
<app-support>/orbitmesh/profiles/default/
config.yaml
controller.json
logs/
Connection flow:
- User saves a Clash subscription URL.
- Client downloads the OrbitMesh Clash target profile.
- Client writes a pending profile.
- Client injects or verifies safe local controller settings.
- Client atomically promotes pending profile to active profile.
- Client starts Mihomo with the active profile.
- Client polls controller health and streams logs.
- Optional system proxy is enabled after Mihomo reports healthy.
Disconnect flow:
- Restore system proxy settings if the client changed them.
- Stop Mihomo gracefully.
- Kill Mihomo only after timeout.
- Mark connection state disconnected.
Mobile Runtime Model
Mobile shares the same subscription and profile cache flow, but runtime is platform VPN-based:
- Android (implemented): a foreground
VpnService(OrbitMeshVpnService) runs in a separate:coreprocess and launches Mihomo itself viaProcessBuilder; its TUN is forwarded by hev-socks5-tunnel into the Mihomo SOCKS listener, and the app is excluded from its own VPN. Because the tunnel lives in:core, the VPN survives the UI process being killed — the service isSTART_STICKY, persists its config, treatsonTaskRemovedas a no-op, and the Dart layer re-attaches by probing the loopback controller on next launch. Always-on VPN is supported. - iOS (later):
NetworkExtensionpacket tunnel plus app-extension profile sharing.
The shared Dart layer must never depend on Android or iOS classes directly.
Because a subscription's own inbound settings cannot be trusted, the shared
layer normalizes the Clash inbound (external-controller/mixed-port to loopback,
tun: block stripped) before activation so every platform's traffic path can
reach the core.
Trust And Security
- Subscription URLs may contain bearer-like tokens and must be treated as
secrets — managed subscriptions are stored as an account/tenant/device-scoped
SubscriptionBindingin secure storage (like access/refresh tokens), cleared on sign-out and re-resolved on tenant switch; manual URLs are a separate account-independent binding. - Controller API must bind to loopback; a subscription's own inbound settings are untrusted and normalized to loopback before use.
- Controller secret must not be logged.
- Logs must redact subscription URLs and tokens.
- System proxy changes must be reversible.
- TUN/global routing modes must require explicit user confirmation.
Package Boundaries
Recommended Dart boundaries:
app: composition, routing, app theme, top-level state providers.core: config, errors, logging, platform detection, security helpers.features: UI and feature-level view models.models: plain value objects.services: side effects and external integrations.platform_channels: MethodChannel/EventChannel bindings only.shared: reusable UI widgets.
Non-Goals For Desktop MVP
- No mobile VPN implementation.
- No privileged TUN mode.
- No account login flow unless required by subscription delivery.
- No custom Clash YAML compiler in the client. The control-plane owns profile rendering.