Skip to content

Enable set_ws_client and set_http_client injectable interfaces - #1290

Open
jhugman wants to merge 3 commits into
mainfrom
jhugman/livekit-net/pr-2
Open

Enable set_ws_client and set_http_client injectable interfaces#1290
jhugman wants to merge 3 commits into
mainfrom
jhugman/livekit-net/pr-2

Conversation

@jhugman

@jhugman jhugman commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

This PR enables the uniffi bindings for livekit-net.

This is not in use by any crates in the livekit-uniffi, so we can safely land it now. However, there should be follow ups to any SDK that uses livekit-uniffi.

Before you submit your PR

Make sure the following is true before submitting your PR:

  • I have read the contributing guidelines and validated that this PR will be accepted.
  • I have read and followed the principles regarding breaking changes, testing, and code quality.

PR description

Describe the changes in this PR. Explain what the PR is meant to solve and how to reproduce the issue in the first place.

Breaking changes

If this PR introduces breaking changes, list them here and document the rationale for introducing such a change.

MSRV

If the PR modifies the crate's MSRV (Minimum Supported Rust Version), document it here.

Testing

Ideally, unit test the code you add, but ensure you're not repeating existing test cases. Use as many already written scaffolding, utilities as possible; write your own, when needed. If external services, APIs, tokens are required (e.g., running an LK server instance), provide the necessary information. Make sure your tests perform useful, context-aware assertions and do not simply emulate "happy paths".

Async

We want the project to be runtime-agnostic, so please reuse what's already in livekit-runtime and feel free to add anything missing. It's ok to use Tokio directly, when writing unit tests, if necessary. When testing, do not use artificial delays for the state to "catch up"; instead, respect the event flow and subscribe properly using channels or other mechanisms.

@jhugman
jhugman requested a review from ladvoc as a code owner July 28, 2026 18:34
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Changeset ✓

This PR includes a changeset covering all affected packages:

Package Bump
livekit patch
livekit-api patch
livekit-ffi patch
livekit-net patch
livekit-uniffi patch

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@jhugman
jhugman force-pushed the jhugman/livekit-net/pr-2 branch from 22edc1c to 1546ceb Compare July 28, 2026 18:43
devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment thread livekit-net/src/lib.rs Outdated
Comment thread livekit-net/src/lib.rs
Comment on lines +63 to +95
/// Self-test: GET `url` via the registered HTTP client; returns the full response
/// (status + headers + body) so callers can assert the whole struct round-trips the FFI.
/// Errors if no client is registered or the transport fails.
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub async fn self_test_http_get(url: String) -> Result<HttpResponse, TransportError> {
let c =
http_client().ok_or_else(|| TransportError::Other("no http client registered".into()))?;
c.request(HttpMethod::Get, url, Vec::new(), None).await
}

/// Self-test: connect, send `payload`, receive one frame, close; return the echoed bytes.
/// Errors if no client is registered, the transport fails, or the peer closes first.
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub async fn self_test_ws_echo(url: String, payload: Vec<u8>) -> Result<Vec<u8>, TransportError> {
let c = ws_client().ok_or_else(|| TransportError::Other("no ws client registered".into()))?;
let conn = c.connect(url, Vec::new(), 5_000).await?.connection;
conn.send(payload).await?;
let got = conn.recv().await?.ok_or(TransportError::Closed)?;
conn.close().await;
Ok(got)
}

/// Test probe: whether a process-wide HTTP client is registered.
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub fn has_http_client() -> bool {
http_client().is_some()
}

/// Test probe: whether a process-wide WebSocket client is registered.
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub fn has_ws_client() -> bool {
ws_client().is_some()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Test-only helper functions are added to the crate's permanent public API

Four self-test helpers are made permanently public (pub async fn self_test_http_get at livekit-net/src/lib.rs:67 and the neighbouring probes) even when the bindings feature is off, so the library's supported surface grows with functions that exist only to exercise tests.
Impact: Consumers see and can depend on test scaffolding as if it were product API, which then cannot be changed without a breaking release.

AGENTS.md rule on new public API surface

AGENTS.md states: "When introducing new API surface, always default to private or pub(crate) unless there is a specific reason to expose publicly" and "Introduce new public APIs sparingly". The only stated reason for these helpers is FFI self-testing, so they should at minimum be gated behind #[cfg(feature = "uniffi")] (with the test using that feature), rather than exported unconditionally at livekit-net/src/lib.rs:63-95.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

has_http_client/has_ws_client resolved through the native fallback, so on
any native build they were a constant true and could not tell a host
whether its set_*_client call took effect. Read the OnceLocks directly.

Also adds the transitive changeset bumps for livekit, livekit-api and
livekit-ffi.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment thread livekit-net/src/lib.rs
Comment on lines +66 to +83
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub async fn self_test_http_get(url: String) -> Result<HttpResponse, TransportError> {
let c =
http_client().ok_or_else(|| TransportError::Other("no http client registered".into()))?;
c.request(HttpMethod::Get, url, Vec::new(), None).await
}

/// Self-test: connect, send `payload`, receive one frame, close; return the echoed bytes.
/// Errors if no client is registered, the transport fails, or the peer closes first.
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub async fn self_test_ws_echo(url: String, payload: Vec<u8>) -> Result<Vec<u8>, TransportError> {
let c = ws_client().ok_or_else(|| TransportError::Other("no ws client registered".into()))?;
let conn = c.connect(url, Vec::new(), 5_000).await?.connection;
conn.send(payload).await?;
let got = conn.recv().await?.ok_or(TransportError::Closed)?;
conn.close().await;
Ok(got)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Built-in network self-test calls can crash when triggered from a foreign host

The new self-test calls are made available to foreign callers (uniffi::export at livekit-net/src/lib.rs:66) without declaring which background worker should run them, so on builds that use the built-in networking they can abort the process instead of returning an error.
Impact: A host app that calls the self-test probes on a build with the built-in transport, without first registering its own transport, can hit a hard crash rather than a clean failure.

Missing tokio async_runtime on exported async functions while the native fallback uses tokio-based reqwest/tokio-tungstenite

self_test_http_get (livekit-net/src/lib.rs:67-71) and self_test_ws_echo (livekit-net/src/lib.rs:76-83) resolve the client via http_client() / ws_client(), which fall back to native::NativeTransport when nothing was registered (livekit-net/src/lib.rs:105-139). On --features uniffi,native-tokio (a combination the PR explicitly adds to CI, .github/workflows/tests.yml:178-179), that transport drives reqwest / tokio-tungstenite futures which require an active tokio reactor. UniFFI polls exported async functions on its own foreign executor thread unless the export is annotated with #[uniffi::export(async_runtime = "tokio")], so there is no tokio context and the tokio I/O driver panics ("there is no reactor running"). The livekit-net/Cargo.toml:59-64 comment asserts "No runtime feature: the exported traits are with_foreign ... and the exported setters are sync", which is no longer true now that async free functions that can drive native Rust futures are exported.

Prompt for agents
The newly exported async functions self_test_http_get and self_test_ws_echo in livekit-net/src/lib.rs can end up driving the built-in native transport (reqwest / tokio-tungstenite) when no foreign client has been registered, because http_client()/ws_client() fall back to native::NativeTransport on __native builds. UniFFI polls exported async functions on its own executor with no tokio runtime context unless the export declares async_runtime = "tokio", which would panic for tokio-based I/O. Consider either restricting these probes to the registered-client case (return an error instead of using the native fallback when no client is registered, i.e. read WS/HTTP OnceLocks directly), or gating the exports so the tokio async_runtime attribute is used when a tokio native backend is compiled in. Also update the stale rationale comment in livekit-net/Cargo.toml that claims no runtime feature is needed because all exported functions are sync.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant