Skip to main content

o_sfu_protocol/
lib.rs

1//! browser and native signaling protocol for `o-sfu`
2//!
3//! `o-sfu-protocol` keeps client-side protocol state pure
4//! the core state machine accepts host events and returns ordered command
5//! vectors that the embedding host executes through its own WebSocket,
6//! `RTCPeerConnection` and timer APIs
7//!
8//! the crate has 3 public facades:
9//!
10//! - `host` is the state-machine surface for browser, native and test hosts
11//! - `wire` contains JSON envelope and signaling payload types
12//! - `bundle` preserves the browser bundle compatibility API used by Odoo
13//!
14//! Hosts execute every command in each returned vector before reporting the
15//! resulting socket and peer-connection events to the same
16//! [`host::ProtocolCore`]. The host creates the peer connection when applying
17//! an initial offer. While that negotiation is pending, the host
18//! submits its correlated answer then reports readiness after the peer
19//! connection becomes usable.
20//!
21//! ```
22//! use o_sfu_protocol::host::{Command, ConnectionState, NegotiationKind, ProtocolCore};
23//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
24//! let mut core = ProtocolCore::new();
25//! let connect = core.connect("wss://sfu.test/ws", "signed-token", None);
26//! assert!(matches!(
27//!     connect.as_slice(),
28//!     [
29//!         Command::SetAvailableFeatures { .. },
30//!         Command::SetRecordingState { .. },
31//!         Command::EmitStateChange { state: ConnectionState::Connecting, .. },
32//!         Command::Connect { .. },
33//!     ]
34//! ));
35//!
36//! let auth = core.on_ws_open();
37//! assert!(matches!(auth.as_slice(), [Command::SendWebSocket { .. }]));
38//! # let welcome = concat!(
39//! #     r#"[{"t":"welcome","p":{"features":{"rtc":true,"transcription":false,"#,
40//! #     r#""audioRecording":false,"videoRecording":false},"recording":{},"peers":[]}}]"#,
41//! # );
42//! let welcome = core.on_ws_message(welcome);
43//! assert!(matches!(welcome.get(2),
44//!     Some(Command::EmitStateChange { state: ConnectionState::Authenticated, .. })
45//! ));
46//!
47//! let offer = r#"[{"t":"offer","q":"offer-1","p":{"sdp":"v=0\r\n","uploadSlots":[]}}]"#;
48//! let negotiation = core.on_ws_message(offer);
49//! let [Command::ApplyNegotiation { request_id, kind, .. }] = negotiation.as_slice()
50//! else {
51//!     return Err("unexpected negotiation command order".into());
52//! };
53//!
54//! assert!(core.on_transport_ready().is_empty());
55//!
56//! let answer = core.submit_negotiation_answer(request_id, *kind, "v=0\r\ns=answer\r\n");
57//! assert_eq!(*kind, NegotiationKind::Offer);
58//! assert!(matches!(answer.as_slice(), [Command::SendWebSocket { .. }]));
59//!
60//! // The host sends `answer` then waits until the peer connection is usable.
61//! let ready = core.on_transport_ready();
62//! assert_eq!(
63//!     ready.as_slice(),
64//!     &[Command::EmitStateChange { state: ConnectionState::Connected, cause: None }]
65//! );
66//! # Ok(())
67//! # }
68//! ```
69
70mod bundle_api;
71mod core;
72mod host_bridge;
73mod shared;
74mod signaling;
75#[cfg(target_arch = "wasm32")]
76pub mod wasm;
77
78/// host-owned protocol state machine and side-effect commands
79///
80/// hosts drive `ProtocolCore` by reporting WebSocket, timer and peer-connection
81/// events
82/// every transition returns ordered commands, so side effects stay explicit at
83/// the host boundary
84pub mod host {
85    pub use crate::{core::*, host_bridge::*};
86}
87
88/// JSON wire envelopes and signaling payloads
89///
90/// this facade contains the serialized protocol contract exchanged over the
91/// WebSocket
92/// browser and native hosts should share these types instead of duplicating
93/// envelope names or request payload shapes
94pub mod wire {
95    pub use crate::{shared::*, signaling::*};
96}
97
98/// browser bundle compatibility API
99pub mod bundle {
100    pub use crate::{bundle_api::*, shared::*};
101}