Skip to content

[WIP] VideoCallClient platform abstraction#644

Open
darioalessandro wants to merge 15 commits intomainfrom
cursor/client-platform-abstraction-da41
Open

[WIP] VideoCallClient platform abstraction#644
darioalessandro wants to merge 15 commits intomainfrom
cursor/client-platform-abstraction-da41

Conversation

@darioalessandro
Copy link
Copy Markdown
Member

_This PR introduces cross-platform support for videocall-client, allowing it to be used by both WASM (browser) and native (desktop/server) applications.

It refactors bot and videocall-cli to depend on the new NativeVideoCallClient API, eliminating significant code duplication for WebTransport, heartbeat logic, and VP9 encoding. This consolidates core communication and encoding logic into videocall-client and videocall-codecs, making videocall-client a unified library for all client types and improving overall maintainability.

Type of Change

  • New feature
  • Enhancement

Testing

  • Tests written or updated

Open in Cursor Open in Web

cursoragent and others added 6 commits February 19, 2026 16:59
Phase 0: Platform abstraction primitives
- Add platform/mod.rs with cfg-based dispatch (wasm vs native)
- Add platform/web.rs: now_ms(), IntervalHandle, spawn() using browser APIs
- Add platform/native.rs: now_ms(), IntervalHandle, spawn() using tokio
- Unit tests for native platform primitives (3 tests)

Phase 1: Make videocall-transport cross-platform
- Add feature flags (wasm/native) to videocall-transport
- Add native_webtransport.rs using web-transport-quinn
- Gate existing WASM modules behind 'wasm' feature

Phase 3 skeleton: NativeVideoCallClient
- Add native_client.rs with connection, heartbeat, packet I/O
- Gate WASM-only modules (encode, decode, media_devices, etc.) behind cfg
- Make crypto tests run on both platforms with cfg_attr
- Make constants module cross-platform (VP9 on native, browser-detect on WASM)

Cargo.toml changes:
- videocall-client: optional deps for wasm/native, feature flags
- videocall-transport: optional deps for wasm/native, feature flags
- Pin dependency versions for Rust 1.82 compatibility

Both targets compile:
- cargo check --features native ✓
- cargo check --features wasm --target wasm32-unknown-unknown ✓
- All 8 native tests pass ✓

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
- bot now depends on videocall-client with 'native' feature
- Deleted bot/src/webtransport_client.rs (replaced by NativeVideoCallClient)
- Updated video_producer.rs to send via client.send_packet() instead of mpsc
- Updated audio_producer.rs to send via client.send_packet() instead of mpsc
- Updated main.rs to use NativeVideoCallClient for connection lifecycle
- Heartbeat is now handled automatically by videocall-client
- Connection packet is now handled automatically by videocall-client
- Removed direct web-transport-quinn dependency from bot

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
…ient

- videocall-cli now depends on videocall-client with 'native' feature
- Rewrote consumers/webtransport.rs to use NativeVideoCallClient
  (was custom WebTransport client with manual heartbeat/connection logic)
- Removed direct web-transport-quinn dependency from videocall-cli
- Heartbeat and connection packets now handled by videocall-client
- CameraSynk trait still used for send_packet abstraction

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
videocall-client tests:
- platform_tests.rs: IntervalHandle, now_ms(), spawn(), ConnectionError (7 tests)
- crypto_tests.rs: AES/RSA roundtrip, cross-key failure, E2EE simulation (10 tests)
- native_client_tests.rs: construction, state, callbacks, error cases (12 tests)
- packet_tests.rs: heartbeat, video, audio, RTT, all packet types (7 tests)
- Unit tests in platform/native.rs (3 tests)
- Unit tests in crypto/ (4 tests cross-platform)
- Doc test (1)

videocall-transport tests:
- native_transport_tests.rs: invalid URL, unreachable server (3 tests)
- Doc test (1)

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
The videocall-client crate now requires either 'wasm' or 'native' feature
to be enabled. Updated yew-ui to use features = ["wasm"].

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
…cates)

Three nearly identical VP9 encoder implementations existed in:
- bot/src/video_encoder.rs
- videocall-cli/src/video_encoder.rs
- videocall-codecs/src/encoder.rs

Consolidated into videocall-codecs::encoder with the full VideoEncoderBuilder
API (cpu_used, quantizer control, update_bitrate_kbps, builder pattern).

Changes:
- Enhanced videocall-codecs::encoder with VideoEncoderBuilder from bot/CLI
- Bot: deleted video_encoder.rs, uses videocall-codecs::encoder
- CLI: deleted video_encoder.rs, re-exports from videocall-codecs::encoder
- Removed direct env-libvpx-sys dependency from bot (comes via videocall-codecs)

Net deletion: ~200 lines of duplicated code

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Feb 19, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Shows how yew-ui, bot, videocall-cli, and future apps all relate to
videocall-client via the wasm/native feature flags. Includes:

- Full component diagram with videocall-client internals (WASM vs native)
- Shared libraries (videocall-types, transport, codecs, neteq, diagnostics)
- Server infrastructure (WebTransport, WebSocket, NATS)
- Table: which client uses which API and encoding strategy
- What videocall-client handles automatically (heartbeat, E2EE, etc.)
- Crate dependency graph

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
@darioalessandro darioalessandro changed the title Client platform abstraction VideoCallClient platform abstraction Feb 19, 2026
@darioalessandro
Copy link
Copy Markdown
Member Author

@testradav this PR will take a while but it adds true cross-platform to VideoCallClient

cursoragent and others added 2 commits February 19, 2026 17:34
New module: videocall-transport/src/native_websocket.rs

NativeWebSocketClient provides:
- connect(url) -> (client, inbound_rx) — async WebSocket connection
- send(data) — send binary frames
- close() — graceful close with close frame
- force_close() — abrupt disconnect
- is_connected() — connection status check
- Automatic inbound reader loop (binary frames dispatched via mpsc)
- Ping/pong handled automatically by tungstenite
- Text frames silently ignored (protocol uses binary protobuf)

Dependency: tokio-tungstenite 0.24 with native-tls

Tests: 4 new WebSocket tests + 1 ignored integration test
All 9 transport tests pass, both doc tests pass

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
…call-transport

Migrated both integration test files to use NativeWebSocketClient
from videocall-transport instead of directly depending on tokio-tungstenite:

- actix-api/tests/jwt_integration_tests.rs — JWT auth tests now use
  NativeWebSocketClient::try_connect() for HTTP status code extraction
  and NativeWebSocketClient::connect() for happy-path connections

- actix-api/src/actors/transports/ws_chat_session.rs — meeting lifecycle
  tests now use NativeWebSocketClient for connect/receive/disconnect

Enhanced NativeWebSocketClient:
- Added WebSocketConnectError enum that preserves HTTP status codes
  (needed for auth tests that check 401/403/410 responses)
- Added try_connect() method returning typed errors
- Implemented Debug for NativeWebSocketClient

Cargo.toml: replaced direct tokio-tungstenite dev-dep with
videocall-transport (features = ["native"]) dev-dep

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
@darioalessandro darioalessandro marked this pull request as ready for review February 19, 2026 18:01
darioalessandro and others added 6 commits February 19, 2026 13:07
Merge resolution:
- videocall-client version bumped to 4.0.3 (from main)
- videocall-transport version 0.1.1, videocall-codecs 0.1.13, neteq 0.8.3
- yew-ui: version 4.0.3 + features = ["wasm"]
- bot/CLI: updated version refs to match

Fix wasm-pack test --node:
- Made tokio dev-dependency target-specific (cfg(not(wasm32))) to prevent
  mio from being compiled for wasm32 target
- Pinned coreaudio-sys to 0.2.16 for Rust 1.82 compat

All passing:
- wasm-pack test --node: 20 WASM tests pass
- cargo test --features native: 45 native tests pass
- cargo clippy (native + wasm): zero warnings
- cargo fmt --check: clean

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
…hub.com/security-union/videocall-rs into cursor/client-platform-abstraction-da41

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
The CI runs 'wasm-pack test --node' without explicit features.
Previously, WASM deps (web-sys, gloo, videocall-transport, etc.) were
optional behind the 'wasm' feature flag, causing compilation failures
when the CI didn't pass --features wasm.

Fix: use [target.'cfg(target_arch = "wasm32")'.dependencies] to
automatically pull in WASM deps when building for wasm32, matching
the pre-refactor behaviour. The 'native' feature remains opt-in.

This means:
- wasm-pack test --node (no features) → works ✓
- cargo build --features native → works ✓
- yew-ui no longer needs features = ["wasm"] → auto-detected ✓

All CI test commands verified:
- wasm-pack test --node: 20 tests pass
- cargo test -p videocall-codecs: 17 tests pass
- cargo test -p neteq --no-default-features --features web: 5 tests pass
- cargo test --features native: 45 tests pass
- clippy + fmt: clean

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
Renamed workflow: 'WebAssembly Tests' → 'Client Tests (WASM & Native)'

New 'Native Tests' job:
- clippy on videocall-client --features native
- clippy on videocall-transport --features native
- cargo test videocall-client --features native (45 tests)
- cargo test videocall-transport --features native (10 tests)
- cargo check bot (verifies it compiles with videocall-client)
- cargo check videocall-cli (verifies it compiles with videocall-client)

Existing 'WASM Tests' job unchanged:
- wasm-pack test --node for videocall-client (20 tests)
- cargo test for videocall-codecs
- cargo test for neteq --features web

Also added videocall-transport, bot, videocall-cli to trigger paths.

Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
Co-authored-by: Dario A Lencina-Talarico <darioalessandro@users.noreply.github.com>
@darioalessandro darioalessandro changed the title VideoCallClient platform abstraction [WIP] VideoCallClient platform abstraction Feb 19, 2026
}

#[cfg(test)]
mod tests {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this looks like slop

@darioalessandro
Copy link
Copy Markdown
Member Author

Now I have the full picture. Let me map the layers.

Current Architecture: Two Completely Separate Stacks

WASM stack (5 layers, ~1200 lines):
ConnectionController → ConnectionManager → Connection → Task → WebMedia trait
timers multi-server RTT heartbeat enum WS|WT send_bytes()
gloo::Interval election, state AES encrypt fallback web_sys APIs
Rc Callback<>, Rc Rc

Native stack (1 file, 389 lines):
NativeVideoCallClient (monolithic)
tokio::spawn, mpsc, Arc
heartbeat, send loop, inbound consumer all inline

Why They Can't Simply Merge Today

The root blocker is the concurrency model mismatch:

┌───────────────┬────────────────────────────────────────────────┬───────────────────────────────┐
│ │ WASM │ Native │
├───────────────┼────────────────────────────────────────────────┼───────────────────────────────┤
│ Smart pointer │ Rc<Cell<>>, Rc<RefCell<>> │ Arc, Arc<Mutex<>> │
├───────────────┼────────────────────────────────────────────────┼───────────────────────────────┤
│ Callbacks │ videocall_types::Callback (Rc-based, not Send) │ Box<dyn Fn() + Send + Sync> │
├───────────────┼────────────────────────────────────────────────┼───────────────────────────────┤
│ Timers │ gloo::Interval │ tokio::time::interval │
├───────────────┼────────────────────────────────────────────────┼───────────────────────────────┤
│ Spawn │ wasm_bindgen_futures::spawn_local │ tokio::spawn │
├───────────────┼────────────────────────────────────────────────┼───────────────────────────────┤
│ Timestamps │ js_sys::Date::now() │ std::time::SystemTime │
└───────────────┴────────────────────────────────────────────────┴───────────────────────────────┘

The Callback type permeates the entire WASM connection stack — ConnectOptions, ConnectionManagerOptions, Connection,
all use it. It's !Send, so it can't be used in tokio tasks. This isn't a superficial difference; it's baked into every
struct.

What IS Duplicated (and can be shared today)

Despite the concurrency differences, there's real protocol logic duplicated:

  1. Heartbeat packet construction — connection.rs:117-141 and native_client.rs:298-319 build identical
    MediaPacket{HEARTBEAT} → PacketWrapper structs
  2. Connection packet construction — native_client.rs:257-277 (not done in connection.rs because Task does it, but same
    logic)
  3. RTT packet construction — connection_manager.rs:372-388
  4. State enum — ConnectionState could be shared
  5. Protocol constants — heartbeat interval (1s), election period, etc.

Proposed Plan: Incremental Unification

Phase 1: Shared protocol module (easy, do now)

Extract pure packet construction and state types into a cross-platform module:

// videocall-client/src/protocol.rs — always compiled, no platform deps

pub fn build_heartbeat_packet(
userid: &str, timestamp: f64,
video: bool, audio: bool, screen: bool,
aes: Option<&Aes128State>,
) -> PacketWrapper { ... }

pub fn build_connection_packet(userid: &str, meeting_id: &str) -> Result { ... }

pub fn build_rtt_packet(userid: &str, timestamp: f64, aes: &Aes128State) -> Result { ... }

pub enum ConnectionState { Testing {...}, Connected {...}, Reconnecting {...}, Failed {...} }

pub const HEARTBEAT_INTERVAL_MS: u32 = 1000;
pub const RTT_PROBE_INTERVAL_MS: u32 = 200;

Both WASM Connection and native NativeVideoCallClient call these instead of building packets inline. This eliminates
the most error-prone duplication (packet format divergence).

Phase 2: Transport trait (moderate, do next)

The WebMedia trait already exists but is WASM-only. Generalize it:

// videocall-transport/src/transport_trait.rs — cross-platform

pub trait Transport: Send {
fn send_bytes(&self, data: Vec) -> Result<()>;
fn is_connected(&self) -> bool;
}

// WASM: impl Transport for WebSocketTask, WebTransportTask
// Native: impl Transport for NativeWebTransportSession, NativeWebSocketClient

This lets the Connection struct become generic over transport.

Phase 3: Unified Connection (larger refactor)

Once transports have a shared trait, the Connection struct (heartbeat + state tracking + send) can become
cross-platform:

pub struct Connection<T: Transport> {
transport: T,
heartbeat_handle: platform::IntervalHandle, // already cross-platform!
state: Arc, // works on both
// ...
}

The platform module already provides cross-platform IntervalHandle, now_ms(), and spawn() — so timers/timestamps are
solved.

The big change: Replace Callback<> with mpsc channels for events. This is needed because:

  • Channels are Send (work in native tokio tasks)
  • They work fine in WASM too (tokio mpsc compiles to wasm32)
  • They're more composable (callers can select! over events)

This makes ConnectionManager (election, RTT, multi-server) reusable on native too.

What I'd recommend

Phase 1 is low-risk, high-value — do it in this PR. It prevents packet format divergence between the two stacks without
touching the concurrency model.

Phase 2+3 is the real unification but it means refactoring the WASM client to drop Callback<> in favor of channels,
which touches yew-ui integration. That's a separate PR.

Want me to start with Phase 1 — extracting the shared protocol module and wiring both stacks to use it?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants