-
-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
AstorisTheBrave edited this page Jul 4, 2026
·
3 revisions
URSA is a thin, reactive client. A network layer parses Uptime Kuma's wire formats into clean domain models, repositories expose those as flows, and Compose renders them. Nothing is stored server-side, and the app holds as little as possible.
Uptime Kuma --Socket.IO--> core.network (adapter: parse/normalize wire -> domain)
--REST (Ktor)-> |
v
data.repository (StateFlow<DomainModel>, single source of truth)
v
ui.* (Compose, collectAsStateWithLifecycle)
core.storage (DataStore + Tink) --credentials/JWT--> core.network (login/loginByToken)
distributor (ntfy) --push--> core.push (UnifiedPush service) --> system notification
- core.network - one Socket.IO connection per active server (auth, live monitor status, heartbeats, certificates, pause/resume) plus a Ktor REST client for public status pages. All of Kuma's wire quirks are isolated here. See Network and Protocol.
- core.storage - DataStore Preferences with Tink AES-256-GCM encryption at rest; the master key lives in the Android Keystore. Only the session token is persisted.
- core.push - the UnifiedPush receive path and notification rendering. See Push Internals.
- data.model - the domain types (monitors, heartbeats, certificates, status pages).
- data.repository - owns the active network client and exposes StateFlows as the single source of truth.
-
ui - one
UrsaViewModel; a top-levelwheninUrsaApppicks the screen from state (no navigation library for the current, small screen set). System back is handled per screen.
- Verify before you model. Uptime Kuma's socket API is internal and unstable, so every wire shape URSA relies on was captured from a live instance.
- One adapter owns the quirks. Positional socket events, snake_case vs camelCase fields, and double-encoded JSON are all handled in the network layer and nowhere else.
- State-driven UI. Routing is a function of ViewModel state. Reconnects keep the list visible with a status banner rather than bouncing to the login screen.
For the code map and deeper notes, see the docs/ folder in the repository.