Skip to main content

sim_lib_web_bridge/
device_peer.rs

1//! Device peers for synchronized surface hubs.
2
3use sim_kernel::Symbol;
4use sim_lib_view_device::{DeviceProfile, EdgeId};
5
6use crate::{SurfaceHub, SurfaceRole};
7
8/// Registers a device edge session as a peer surface on `hub`.
9///
10/// The device profile is converted to ordinary surface capabilities before
11/// registration, so committed edits use the same [`SurfaceHub`] broadcast path
12/// as phone, desktop, and terminal panes while projecting to the device's own
13/// profile.
14pub fn register_device_peer(
15    hub: &mut SurfaceHub,
16    session: &EdgeId,
17    profile: &DeviceProfile,
18) -> Symbol {
19    register_device_peer_with_role(hub, session, profile, SurfaceRole::Peer)
20}
21
22/// Registers a device edge session as a surface with an explicit hub role.
23pub fn register_device_peer_with_role(
24    hub: &mut SurfaceHub,
25    session: &EdgeId,
26    profile: &DeviceProfile,
27    role: SurfaceRole,
28) -> Symbol {
29    let surface = device_peer_surface(session);
30    hub.register_surface_with_role(
31        surface.clone(),
32        profile.to_surface_caps(session.as_symbol().as_qualified_str()),
33        role,
34    );
35    surface
36}
37
38/// Returns the hub surface id used for a device edge session.
39pub fn device_peer_surface(session: &EdgeId) -> Symbol {
40    Symbol::qualified("device/peer", session.as_symbol().as_qualified_str())
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    use sim_kernel::{Expr, NumberLiteral};
48    use sim_lib_intent::{Origin, intent};
49    use sim_lib_view::surface;
50    use sim_lib_view_device::{ConsentReceipt, DeviceProfile};
51    use sim_value::build;
52
53    fn number(value: &str) -> Expr {
54        Expr::Number(NumberLiteral {
55            domain: build::keyword("i64"),
56            canonical: value.to_owned(),
57        })
58    }
59
60    fn doc() -> Expr {
61        Expr::Map(vec![
62            (Expr::Symbol(build::keyword("a")), number("1")),
63            (Expr::Symbol(build::keyword("b")), number("2")),
64            (Expr::Symbol(build::keyword("c")), number("3")),
65        ])
66    }
67
68    fn edit(field: &str, value: Expr) -> Expr {
69        intent(
70            "edit-field",
71            Origin::human(1),
72            vec![
73                ("target", doc()),
74                (
75                    "path",
76                    Expr::List(vec![Expr::Vector(vec![
77                        Expr::Symbol(build::keyword("k")),
78                        Expr::Symbol(build::keyword(field)),
79                    ])]),
80                ),
81                ("value", value),
82            ],
83        )
84    }
85
86    #[test]
87    fn device_peer_receives_committed_edits_projected_by_profile() {
88        let session = EdgeId::named("watch-route");
89        let receipt = ConsentReceipt::new(Vec::new(), 60_000, Vec::new(), session.clone(), 3);
90        assert_eq!(receipt.session, session);
91
92        let watch_caps = surface::preset("watch").unwrap();
93        let profile = DeviceProfile::from_surface_caps(&watch_caps);
94        let mut hub = SurfaceHub::new();
95        let device_surface = register_device_peer(&mut hub, &session, &profile);
96        hub.register_surface(
97            build::keyword("desktop"),
98            surface::preset("desktop").unwrap(),
99        );
100        hub.seed(build::keyword("doc"), doc());
101
102        let device_scene = hub
103            .open(
104                &device_surface,
105                build::keyword("pane"),
106                build::keyword("doc"),
107            )
108            .unwrap();
109        let desktop_scene = hub
110            .open(
111                &build::keyword("desktop"),
112                build::keyword("pane"),
113                build::keyword("doc"),
114            )
115            .unwrap();
116        assert_ne!(
117            device_scene, desktop_scene,
118            "device profile projection should differ from a dense desktop peer"
119        );
120
121        let broadcasts = hub
122            .submit(
123                &build::keyword("desktop"),
124                &build::keyword("pane"),
125                &edit("a", number("9")),
126            )
127            .unwrap();
128
129        assert!(broadcasts.iter().any(|item| item.surface == device_surface));
130        assert!(
131            broadcasts
132                .iter()
133                .any(|item| item.surface == build::keyword("desktop"))
134        );
135
136        let broadcasts = hub
137            .submit(
138                &device_surface,
139                &build::keyword("pane"),
140                &edit("b", number("8")),
141            )
142            .unwrap();
143
144        assert!(broadcasts.iter().any(|item| item.surface == device_surface));
145        assert!(
146            broadcasts
147                .iter()
148                .any(|item| item.surface == build::keyword("desktop"))
149        );
150    }
151}