solari-browser (Rust)
Rust binding for the Solari Browser platform — the control plane: acquire
and release pooled stealth Chromium sessions, manage stored browser profiles,
pull session replays, and inspect managed-proxy egress. It speaks the same wire
contract as the reference @solarisdk/browser TypeScript package
(sdk/src/index.ts).
reqwest (rustls) for REST, serde for the wire types, thiserror for
SolariError. Optionally chromiumoxide for driving the browser.
The crate is solari-browser; the root module is solari_browser. (Distinct
from the desktop/sandbox crate, which is solari-sdk / solari.)
Scope: no launch(), and why
The TypeScript SDK's Solari.launch() returns a live Playwright Browser.
Playwright has no Rust binding, so there is no Rust port of it, and there won't
be one.
Rust drives sessions over the raw CDP endpoint instead, via
chromiumoxide — enable the connect feature and use
solari_browser::connect(). Note the distinction on every Session:
| field | protocol | use from Rust |
|---|---|---|
ws_endpoint |
Playwright wire protocol | ❌ chromiumoxide can't speak this |
cdp_endpoint |
raw CDP | ✅ this is the one — Puppeteer-shaped |
One other deliberate divergence: the TS SDK proxies both endpoints through a
Node-side loopback LocalProxy. This crate returns the upstream gateway
endpoints verbatim — there is nothing to route through.
Install
Control plane only (dependency-light — reqwest, serde, thiserror, url):
[]
= { = "sdk/rust" }
= { = "1", = ["macros", "rt-multi-thread"] }
With browser automation (pulls in chromiumoxide):
[]
= { = "sdk/rust", = ["connect"] }
Usage
use ;
async
Driving the browser (feature connect)
use CreateSessionOptions;
let session = client
.sessions
.create
.await?;
// Attaches to session.cdp_endpoint; returns the chromiumoxide Browser plus the
// spawned task pumping its event handler (every call resolves through it).
let connected = connect.await?;
let page = connected.browser.new_page.await.unwrap;
println!;
connected.disconnect.await; // drops the browser, awaits the handler
client.sessions.release.await?;
Profiles
use ;
let profile = client.profiles.create.await?;
// Attach it — the session's storage_state is fetched from its presigned URL.
let session = client
.sessions
.create
.await?;
match &session.storage_state
// …drive the browser, then persist what it accumulated.
client.profiles.save.await?;
for p in client.profiles.list.await?
storage_state is three-state, mirroring the TS storageState?: StorageState | null:
None = no profile attached, Some(None) = profile attached but empty,
Some(Some(_)) = the profile's state.
Managed proxy
use ;
country; // proxy: "gb"
smart; // gateway sweeps on block
off; // explicitly disabled
from;
Managed proxy requires stealth(true). client.proxy().countries() lists the
supported egress countries and reports whether the gateway has proxy credentials
configured at all — this endpoint has no TypeScript equivalent.
Replays
client.sessions.release.await?; // replays land ~1–3s after release
let replay = client.sessions.replay_url.await?;
println!;
let bytes = client.sessions.download_replay.await?; // NDJSON, gzip by default
Errors
Every fallible method returns Result<T, SolariError>:
| variant | when |
|---|---|
Api |
non-2xx from the API — carries status + parsed code |
Transport |
network failure, or retries exhausted |
Protocol |
a 2xx body that was malformed or missing required fields |
Config |
invalid client options (empty api key, bad base URL) |
Connect |
CDP attach failed (feature connect) |
err.status() and err.code() work on any error. code() yields the typed
SolariErrorCode — FeatureRequiresPlan, ConcurrencyLimitExceeded,
PlanLimitExceeded, BrowserUnhealthy — with unknown wire codes preserved as
Other(String) rather than dropped.
use SolariErrorCode;
match err.code
Retries
Defaults: max_attempts = 2, backoff_ms = 500 (a fixed delay, not
exponential), timeout_ms = 90_000. Only 502 / 503 / 504 and transport
errors are retried — every other status, including 4xx and 500, returns on the
first attempt. Tune per-client:
use ;
let client = new?;
Debug for Client and ClientOptions is hand-written to redact the API key —
neither will print your credentials.
Testing
Fully offline — the API is mocked with an in-process TCP HTTP server; no live gateway is needed.
cargo build
cargo test
The connect feature is off by default, so the core build never pulls in
chromiumoxide. To verify it too: cargo build --features connect.