Skip to main content

Crate frust_devtools

Crate frust_devtools 

Source
Expand description

frust-devtools — the in-app debug service a Frust app hosts for external tooling.

It listens on an ephemeral loopback TCP port, speaks the NDJSON JSON-RPC protocol defined by frust_devtools_protocol, and answers every request through one trait, DevtoolsBackend, which a shell implements. The tooling side (frust-drive/frust-tui) never depends on this crate — the two sides meet at the protocol crate and nowhere else (docs/ARCHITECTURE.md’s Tooling isolation rule), which is also why this crate depends on no framework crate: it is a service, not a framework layer, and it learns about the app only through the backend it is handed.

use frust_devtools::{AppInfo, Service};
let devtools = Service::start(ShellBackend, AppInfo::new("my-app", "0.1.0"))?;
// ...each frame, from the frame hook — never blocks:
// devtools.publish_frame_stats(stats);
devtools.shutdown();

§Trust model

The listener binds 127.0.0.1:0 and only 127.0.0.1 — never 0.0.0.0, not configurably. Loopback alone is not the boundary, though: on a device every co-resident app can reach 127.0.0.1:<port> too, and the protocol carries input_* methods that drive the real UI plus a widget-tree dump that is user data. So the service also mints a random per-process token, prints it on its discovery line, and requires it at handshake before dispatching any other method — the same shape the Dart VM service’s auth code has. That works because only a privileged reader sees the line: another Android app cannot read this app’s logcat (READ_LOGS is a privileged permission), and a desktop app’s stderr reaches only the tooling process that launched it.

Two layers still sit above it, and neither is optional: a shell gates starting the service on a debug/profile build via a cargo feature (a release build compiles the listener out entirely), and ServiceConfig::require_token — default on — is the only way to run without auth, meant for in-process tests, never a shipped build. See crate::token for the token’s entropy source, stated with its limits.

§Threading & blocking model

Three threads are involved, and only one of them is the app’s:

ThreadOwnsBlocking rule
the shell’s frame/UI threadcalls ServiceHandle::publish_frame_statsnever blocks: a bounded, drop-oldest, sync send
the service threadthe internal current-thread tokio runtime, the listener, every connectionblocks only on IO it owns
the backend threadthe DevtoolsBackend valueruns one sync trait call at a time, in arrival order

A backend method that needs UI-thread state hops there itself; the service caps every call with ServiceConfig::backend_timeout, so a hop that never returns costs that client one error response and nothing else. handshake bypasses the backend entirely (its answer is captured at startup), so a client can always identify even a wedged app. crate::hop’s module doc carries the full contract, including what happens to a call that completes after its client gave up.

§Discovery

On start the service logs one line built by frust_devtools_protocol::format_discovery_line at info level, carrying the port and (with auth on) the token; tooling recovers both from a log/logcat stream with frust_devtools_protocol::parse_discovery_line. ServiceHandle::port/ServiceHandle::token are the in-process equivalents.

Structs§

AppInfo
The app identity a service announces at handshake, supplied by whoever starts the service (the shell knows its own app name and framework version; the backend does not have to).
Service
Starts the in-app devtools service. A namespace, not a value — the running service is owned through its ServiceHandle.
ServiceConfig
Tuning knobs. ServiceConfig::default is what Service::start uses; Service::start_with_config exists for tests and for a shell with an unusual budget.
ServiceHandle
The running service. Dropping it shuts the service down, so a shell can simply hold it for as long as devtools should be available.

Enums§

BackendError
Why a backend call could not be satisfied. Each variant maps to exactly one JSON-RPC error code (see BackendError::to_rpc_error), so a backend picks the variant and never has to know the code.

Traits§

DevtoolsBackend
What the devtools service asks the app for.