Skip to documentation
OrbitMeshDocs
Browse documentation
Docs/Engineering Specs

ENGINEERING SPECS

OrbitMesh Client Architecture

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.
View source

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 SubscriptionBinding in 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:

  1. User saves a Clash subscription URL.
  2. Client downloads the OrbitMesh Clash target profile.
  3. Client writes a pending profile.
  4. Client injects or verifies safe local controller settings.
  5. Client atomically promotes pending profile to active profile.
  6. Client starts Mihomo with the active profile.
  7. Client polls controller health and streams logs.
  8. Optional system proxy is enabled after Mihomo reports healthy.

Disconnect flow:

  1. Restore system proxy settings if the client changed them.
  2. Stop Mihomo gracefully.
  3. Kill Mihomo only after timeout.
  4. 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 :core process and launches Mihomo itself via ProcessBuilder; 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 is START_STICKY, persists its config, treats onTaskRemoved as a no-op, and the Dart layer re-attaches by probing the loopback controller on next launch. Always-on VPN is supported.
  • iOS (later): NetworkExtension packet 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 SubscriptionBinding in 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.
OrbitMesh DocumentationContent follows the repository's main branch.