dittolive_ditto/presence/
mod.rs

1//! Use [`ditto.presence()`] to gain insight to connected Peers in the Ditto **mesh** via the
2//! [`Presence`] API.
3//!
4//! The [`Presence`] API can be used to inspect the ways in which peers are connected to one
5//! another in the Ditto mesh. Using [`.graph()`] or [`.observe()`], you can see an immediate
6//! view of peer connections or receive callbacks with peer connection updates, respectively.
7//!
8//! The key piece of data in the presence API is the [`PresenceGraph`], which contains a
9//! description of the "local peer" as well as a list of "remote peers". Remote peers are any
10//! peers which can be reached by this local peer in the Ditto mesh, either directly via a
11//! transport on this device, or indirectly via multiple hops through other peers.
12//!
13//! # Example
14//!
15//! Let's take a look at how to request a [`PresenceGraph`] and how to read through it:
16//!
17//! ```
18//! use dittolive_ditto::prelude::*;
19//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
20//!
21//! // Get an immediate `PresenceGraph` showing current connections
22//! let graph = ditto.presence().graph();
23//! let my_key = &graph.local_peer.peer_key_string;
24//! let my_connections = &graph.local_peer.connections;
25//!
26//! // Let's find all peers that are directly connected to me, the local peer
27//! let direct_peers = my_connections
28//!     .iter()
29//!     .map(|connection| {
30//!         // Choose the peer in this connection that is not me
31//!         if connection.peer_key_string1 == *my_key {
32//!             &connection.peer_key_string2
33//!         } else {
34//!             &connection.peer_key_string1
35//!         }
36//!     })
37//!     .collect::<Vec<_>>();
38//! println!("My direct peers are {direct_peers:?}");
39//!
40//! // Let's look up some details about _all_ the remote peers we can see
41//! let remote_peers_summary = graph
42//!     .remote_peers
43//!     .iter()
44//!     .map(|peer| {
45//!         // Peer Key => (Name, OS, Connection Count)
46//!         (
47//!             &peer.peer_key_string,
48//!             (&peer.device_name, &peer.os, peer.connections.len()),
49//!         )
50//!     })
51//!     .collect::<std::collections::BTreeMap<_, _>>();
52//! println!("A summary of my remote peers looks like this: {remote_peers_summary:#?}");
53//! # Ok(())
54//! # }
55//! ```
56//!
57//! Please note that obtaining a [`PresenceGraph`] via [`.graph()`] only shows a
58//! _snapshot_ of what devices were present when the call was made. In order to watch
59//! changes to device presence, we'll need to use [`.observe()`] to register a
60//! callback and receive updates.
61//!
62//! # Example
63//!
64//! Let's look at how we can use the [`.observe()`] API to register a callback and
65//! receive updates whenever the presence of our Ditto mesh changes. We could say the
66//! presence has changed if any peers have added or removed connections in the mesh.
67//!
68//! ```
69//! # use std::sync::{Arc, Mutex};
70//! use dittolive_ditto::prelude::*;
71//! # fn example(ditto: &Ditto) -> anyhow::Result<()> {
72//!
73//! let mut maybe_prev_graph = Arc::new(Mutex::new(None));
74//! let _presence_observer = ditto.presence().observe(move |graph| {
75//!     let mut maybe_prev_graph = maybe_prev_graph.lock().unwrap();
76//!
77//!     let Some(prev_graph) = &*maybe_prev_graph else {
78//!         // First presence update! Print what remote peers we see by their keys
79//!         let remote_peers = graph
80//!             .remote_peers
81//!             .iter()
82//!             .map(|peer| &peer.peer_key_string)
83//!             .collect::<Vec<_>>();
84//!         println!("Received first presence update! Remote peers: {remote_peers:?}");
85//!         *maybe_prev_graph = Some(graph.clone());
86//!         return;
87//!     };
88//!
89//!     // Subsequent presence updates can compare the new graph against the old
90//!     let prev_remote_peers = prev_graph
91//!         .remote_peers
92//!         .iter()
93//!         .map(|peer| &peer.peer_key_string)
94//!         .collect::<std::collections::HashSet<_>>();
95//!     let latest_remote_peers = graph
96//!         .remote_peers
97//!         .iter()
98//!         .map(|peer| &peer.peer_key_string)
99//!         .collect::<std::collections::HashSet<_>>();
100//!
101//!     // Detect if new peers have joined the mesh
102//!     let new_peers = latest_remote_peers
103//!         .difference(&prev_remote_peers)
104//!         .collect::<Vec<_>>();
105//!     if !new_peers.is_empty() {
106//!         println!("New peers joined the mesh! {new_peers:?}");
107//!     }
108//!
109//!     // Detect if any peers left the mesh
110//!     let lost_peers = prev_remote_peers
111//!         .difference(&latest_remote_peers)
112//!         .collect::<Vec<_>>();
113//!     if !lost_peers.is_empty() {
114//!         println!("Peers have left the mesh! {lost_peers:?}");
115//!     }
116//!
117//!     *maybe_prev_graph = Some(graph.clone());
118//! });
119//! # Ok(())
120//! # }
121//! ```
122//!
123//! [`ditto.presence()`]: crate::ditto::Ditto::presence
124//! [`.graph()`]: Presence::graph
125//! [`.observe()`]: Presence::observe
126
127#![doc(alias = "mesh")]
128#![warn(missing_docs)]
129
130use_prelude!();
131
132use std::{
133    future::Future,
134    sync::{Mutex, OnceLock, Weak},
135};
136
137pub use observer::PresenceObserver;
138
139use crate::{ditto::DittoFields, transport::v2::V2Presence};
140
141mod connection_request_handler;
142pub(crate) mod observer;
143pub(crate) mod observer_legacy;
144
145// These items all "publicly" exist here in `presence`, even if defined elsewhere
146pub use self::connection_request_handler::{ConnectionRequest, ConnectionRequestAuthorization};
147pub use crate::transport::v3::{Connection, Peer, PresenceGraph, PresenceOs};
148
149/// Convenience type for JSON objects seen in [`peer_metadata`][0].
150///
151/// [0]: Presence::peer_metadata
152pub type JsonObject = ::serde_json::Map<String, ::serde_json::Value>;
153
154#[derive(Debug, Default)]
155pub(crate) struct PeerMetadataCache {
156    json_str: String,
157    value: Arc<JsonObject>,
158}
159
160/// The entrypoint to the Presence API, obtained with [`ditto.presence()`].
161///
162/// [See the `presence` module for guide-level docs and examples][0]
163///
164/// [`ditto.presence()`]: crate::prelude::Ditto::presence
165/// [0]: crate::presence
166pub struct Presence {
167    ditto: Weak<DittoFields>,
168    peer_metadata_cache: Mutex<PeerMetadataCache>,
169
170    // TODO(v5): remove
171    observers_v2: Mutex<Vec<Weak<observer_legacy::PeersObserverCtx>>>,
172    // TODO(v5): remove
173    observers_v3: Mutex<Vec<observer_legacy::WeakPresenceObserver>>,
174    // TODO(v5): remove
175    registered: Mutex<bool>,
176}
177
178// Primary public API
179impl Presence {
180    pub(crate) fn new(ditto: Weak<DittoFields>) -> Self {
181        Self {
182            ditto,
183            peer_metadata_cache: <_>::default(),
184
185            // TODO(v5): remove
186            observers_v2: <_>::default(),
187            // TODO(v5): remove
188            observers_v3: <_>::default(),
189            // TODO(v5): remove
190            registered: false.into(),
191        }
192    }
193
194    /// Return an immediate view of peer connections as a [`PresenceGraph`].
195    ///
196    /// # Example
197    ///
198    /// Let's take a look at how to request a [`PresenceGraph`] and how to read through it:
199    ///
200    /// ```
201    /// use dittolive_ditto::prelude::*;
202    /// # fn example(ditto: &Ditto) -> anyhow::Result<()> {
203    ///
204    /// // Get an immediate `PresenceGraph` showing current connections
205    /// let graph = ditto.presence().graph();
206    /// let my_key = &graph.local_peer.peer_key_string;
207    /// let my_connections = &graph.local_peer.connections;
208    ///
209    /// // Let's find all peers that are directly connected to me, the local peer
210    /// let direct_peers = my_connections
211    ///     .iter()
212    ///     .map(|connection| {
213    ///         // Choose the peer in this connection that is not me
214    ///         if connection.peer_key_string1 == *my_key {
215    ///             &connection.peer_key_string2
216    ///         } else {
217    ///             &connection.peer_key_string1
218    ///         }
219    ///     })
220    ///     .collect::<Vec<_>>();
221    /// println!("My direct peers are {direct_peers:?}");
222    ///
223    /// // Let's look up some details about _all_ the remote peers we can see
224    /// let remote_peers_summary = graph
225    ///     .remote_peers
226    ///     .iter()
227    ///     .map(|peer| {
228    ///         // Peer Key => (Name, OS, Connection Count)
229    ///         (
230    ///             &peer.peer_key_string,
231    ///             (&peer.device_name, &peer.os, peer.connections.len()),
232    ///         )
233    ///     })
234    ///     .collect::<std::collections::BTreeMap<_, _>>();
235    /// println!("A summary of my remote peers looks like this: {remote_peers_summary:#?}");
236    /// # Ok(())
237    /// # }
238    /// ```
239    ///
240    /// Please note that obtaining a [`PresenceGraph`] via `.graph()` only shows a
241    /// _snapshot_ of what devices were present when the call was made. In order to watch
242    /// changes to device presence, we'll need to use [`.observe(...)`] to register a
243    /// callback and receive updates.
244    ///
245    /// [`.observe(...)`]: Self::observe
246    pub fn graph(&self) -> PresenceGraph {
247        let ditto = self.ditto.upgrade().unwrap();
248        let buffer = ffi_sdk::dittoffi_presence_graph(&ditto.ditto);
249        serde_json::from_slice::<PresenceGraph>(&buffer)
250            .expect("should receive UTF-8 encoded JSON PresenceGraph")
251    }
252
253    /// Receive [`PresenceGraph`] updates when peer connections change in the Ditto mesh.
254    ///
255    /// Use [`observer.cancel()`] to stop receiving updates.
256    ///
257    /// # Example
258    ///
259    /// Let's look at how we can use `.observe()` to register a callback and
260    /// receive updates whenever the presence of our Ditto mesh changes. We could say the
261    /// presence has changed if any peers have added or removed connections in the mesh.
262    ///
263    /// ```
264    /// # use std::sync::{Arc, Mutex};
265    /// use dittolive_ditto::prelude::*;
266    /// # fn example(ditto: &Ditto) -> anyhow::Result<()> {
267    ///
268    /// let mut maybe_prev_graph = Arc::new(Mutex::new(None));
269    /// let _presence_observer = ditto.presence().register_observer(move |graph| {
270    ///     let mut maybe_prev_graph = maybe_prev_graph.lock().unwrap();
271    ///
272    ///     let Some(prev_graph) = &*maybe_prev_graph else {
273    ///         // First presence update! Print what remote peers we see by their keys
274    ///         let remote_peers = graph
275    ///             .remote_peers
276    ///             .iter()
277    ///             .map(|peer| &peer.peer_key_string)
278    ///             .collect::<Vec<_>>();
279    ///         println!("Received first presence update! Remote peers: {remote_peers:?}");
280    ///         *maybe_prev_graph = Some(graph.clone());
281    ///         return;
282    ///     };
283    ///
284    ///     // Subsequent presence updates can compare the new graph against the old
285    ///     let prev_remote_peers = prev_graph
286    ///         .remote_peers
287    ///         .iter()
288    ///         .map(|peer| &peer.peer_key_string)
289    ///         .collect::<std::collections::HashSet<_>>();
290    ///     let latest_remote_peers = graph
291    ///         .remote_peers
292    ///         .iter()
293    ///         .map(|peer| &peer.peer_key_string)
294    ///         .collect::<std::collections::HashSet<_>>();
295    ///
296    ///     // Detect if new peers have joined the mesh
297    ///     let new_peers = latest_remote_peers
298    ///         .difference(&prev_remote_peers)
299    ///         .collect::<Vec<_>>();
300    ///     if !new_peers.is_empty() {
301    ///         println!("New peers joined the mesh! {new_peers:?}");
302    ///     }
303    ///
304    ///     // Detect if any peers left the mesh
305    ///     let lost_peers = prev_remote_peers
306    ///         .difference(&latest_remote_peers)
307    ///         .collect::<Vec<_>>();
308    ///     if !lost_peers.is_empty() {
309    ///         println!("Peers have left the mesh! {lost_peers:?}");
310    ///     }
311    ///
312    ///     *maybe_prev_graph = Some(graph.clone());
313    /// });
314    /// # Ok(())
315    /// # }
316    /// ```
317    ///
318    /// If instead you just need to check once to learn about which peers are connected
319    /// _right now_, try using the [`.graph()`] method instead.
320    ///
321    /// [`observer.cancel()`]: crate::presence::PresenceObserver::cancel
322    /// [`.graph()`]: Self::graph
323    pub fn register_observer(
324        self: &Arc<Self>,
325        callback: impl Fn(&PresenceGraph) + Send + Sync + 'static,
326    ) -> Result<PresenceObserver> {
327        let ditto = self
328            .ditto
329            .upgrade()
330            .ok_or(ErrorKind::ReleasedDittoInstance)?;
331
332        let callback = Arc::new(Mutex::new(callback));
333        let callback = move |graph: &PresenceGraph| {
334            let callback = callback.lock().unwrap();
335            callback(graph);
336        };
337
338        PresenceObserver::new(&*ditto.ditto, callback)
339    }
340}
341
342// Peer Metadata & on-connecting API.
343impl Presence {
344    /// Set a dictionary of arbitrary data about this device to be shared with peers in the mesh.
345    ///
346    /// This data is gossiped in the presence collection across the mesh. This can be useful to
347    /// include extra metadata like app versions,capabilities, etc., to help peers decide who to
348    /// interact with.
349    ///
350    /// This peer information is persisted in the SDK, and thus needn't be set at every start
351    /// of Ditto.
352    ///
353    /// # Security (and caveats)
354    /// This peer info will be signed by your peer key to prevent forgery of this info by other
355    /// peers.
356    ///
357    /// However, for compatibility, there is no attestation of the *lack* of peer info -- that
358    /// is, participants in the mesh could maliciously remove peer info. If this is a concern
359    /// for your application, a workaround for this is to have your application require that
360    /// peers have a signed peer info dictionary present.
361    ///
362    /// Similarly, as there is no monotonic version counter or timestamp/expiration of the
363    /// signed peer info, replay attacks (replacing the current info with previously, possibly
364    /// outdated, signed info) are possible without counter-measures. If this is a concern
365    /// for your application, you might consider including a counter or creation timestamp
366    /// to prevent replays, depending on your use-case.
367    ///
368    /// # Performance caveats
369    /// Because this information is included in the presence data that is gossiped among peers,
370    /// the size of this peer info and the frequency it is updated can *drastically* affect
371    /// performance if it is too large.
372    ///
373    /// # Errors
374    /// Because of the performance implications, the serialized info dictionary is currently
375    /// limited to 4KiB.
376    ///
377    /// # Examples
378    /// ```
379    /// # use std::collections::HashMap;
380    /// use dittolive_ditto::prelude::*;
381    /// use serde_json::json;
382    /// # |ditto: Ditto| -> Result<(), Box<dyn std::error::Error>> {
383    /// ditto.presence().set_peer_metadata(&json!({
384    ///     "app_version": "1.0.0",
385    /// }))?;
386    /// # Ok(())
387    /// # };
388    /// ```
389    pub fn set_peer_metadata(&self, peer_metadata: &impl Serialize) -> Result<(), DittoError> {
390        let payload = ::serde_json::to_string(peer_metadata)?;
391        self.set_peer_metadata_json_str(&payload)
392    }
393
394    /// Set arbitrary metadata formatted as JSON to be associated with the
395    /// current peer.
396    ///
397    /// The metadata must not exceed 4 KB in size. Expects JSON.
398    ///
399    /// - See also: [`Self::set_peer_metadata()`] for details on usage of metadata.
400    pub fn set_peer_metadata_json_str(&self, json: &str) -> Result<(), DittoError> {
401        let ditto = self
402            .ditto
403            .upgrade()
404            .ok_or(ErrorKind::ReleasedDittoInstance)?;
405        ffi_sdk::dittoffi_presence_set_peer_metadata_json_throws(
406            &ditto.ditto,
407            json.as_bytes().into(),
408        )
409        .into_rust_result()?;
410        Ok(())
411    }
412
413    /// Metadata associated with the current peer as JSON-encoded data.
414    ///
415    /// Other peers in the same mesh can access this user-provided dictionary of
416    /// metadata via the presence graph at [`Self::graph()`] and when
417    /// evaluating connection requests using
418    /// [`Self::set_connection_request_handler()`]. Use [`Self::set_peer_metadata()`]
419    /// or [`Self::set_peer_metadata_json_str()`] to set this value.
420    pub fn peer_metadata_json_str(&self) -> String {
421        let ditto = self
422            .ditto
423            .upgrade()
424            .ok_or(ErrorKind::ReleasedDittoInstance)
425            .unwrap();
426
427        String::from_utf8(From::<Box<[u8]>>::from(
428            ffi_sdk::dittoffi_presence_peer_metadata_json(&ditto.ditto).into(),
429        ))
430        .expect("UTF-8")
431    }
432
433    /// [`DeserializeOwned`] convenience around [`Self::peer_metadata_json_str()`].
434    pub fn peer_metadata_serde<T: DeserializeOwned>(&self) -> Result<T> {
435        let value = ::serde_json::from_str(&self.peer_metadata_json_str())?;
436        Ok(value)
437    }
438
439    /// Metadata associated with the current peer.
440    ///
441    /// Other peers in the same mesh can access this user-provided dictionary of
442    /// metadata via the presence graph at [`Self::graph()`] and when
443    /// evaluating connection requests using
444    /// [`Self::set_connection_request_handler()`]. Use [`Self::set_peer_metadata()`]
445    /// or [`Self::set_peer_metadata_json_str()`] to set this value.
446    ///
447    /// This is a convenience property that wraps [`Self::peer_metadata_json_str()`].
448    pub fn peer_metadata(&self) -> Arc<JsonObject> {
449        let json_str = self.peer_metadata_json_str();
450        let mut cache = self
451            .peer_metadata_cache
452            .lock()
453            .unwrap_or_else(|it| it.into_inner());
454        if json_str != cache.json_str {
455            *cache = PeerMetadataCache {
456                value: Arc::new(
457                    ::serde_json::from_str(&json_str).expect("incorrect json from `dittoffi`"),
458                ),
459                json_str,
460            };
461        }
462        cache.value.retain()
463    }
464
465    /// Set this handler to control which peers in a Ditto mesh can connect to
466    /// the current peer.
467    ///
468    /// Each peer in a Ditto mesh will attempt to connect to other peers that it
469    /// can reach. By default, the mesh will try and establish connections that
470    /// optimize for the best overall connectivity between peers. However, you
471    /// can set this handler to assert some control over which peers you connect
472    /// to.
473    ///
474    /// If set, this handler is called for every incoming connection request
475    /// from a remote peer and is passed the other peer's `peer_key`,
476    /// `peer_metadata`, and `identity_service_metadata`. The handler can then
477    /// accept or reject the request by returning an according
478    /// [`ConnectionRequestAuthorization`] value. When the connection
479    /// request is rejected, the remote peer may retry the connection request
480    /// after a short delay.
481    ///
482    /// Connection request handlers must reliably respond to requests within a
483    /// short time: **if a handler takes too long to return, the connection
484    /// request will fall back to being denied**. The response –currently— times
485    /// out after 10 seconds, but this exact value may be subject to change in
486    /// future releases.
487    ///
488    /// - Note: the handler is called from a different thread ("background hook").
489    /// - See also: [`Self::peer_metadata()`]
490    ///
491    /// ## Example
492    ///
493    /// ```
494    /// # use dittolive_ditto::prelude::*;
495    /// # |ditto: Ditto| -> Result<(), Box<dyn std::error::Error>> {
496    /// /// Let's imagine the app we are maintaining has a bug in `1.2.3`:
497    /// const BUGGY_VERSION: &str = "1.2.3";
498    ///
499    /// // We avoid problems in updated versions of our app with these ones by
500    /// // rejecting connections to them, like so:
501    /// ditto
502    ///     .presence()
503    ///     .set_connection_request_handler(|connection_request: ConnectionRequest| {
504    ///         match connection_request
505    ///             .peer_metadata()
506    ///             .get("app_version")
507    ///             .and_then(|it| it.as_str())
508    ///         {
509    ///             // Reject peers reporting a known buggy version or reporting no
510    ///             // version at all.
511    ///             Some(BUGGY_VERSION) | None => return ConnectionRequestAuthorization::Deny,
512    ///             Some(_non_buggy_version) => { /* no reason to reject here */ }
513    ///         }
514    ///         // Potentially other checks/reasons to reject…
515    ///
516    ///         // Eventually:
517    ///         ConnectionRequestAuthorization::Allow
518    ///     });
519    ///
520    /// // You can also unset the `connection_request_handler` by setting it to `None`.
521    /// // This uses the default handler, which accepts all requests.
522    /// ditto.presence().set_connection_request_handler(None);
523    /// # Ok(())
524    /// # };
525    /// ```
526    pub fn set_connection_request_handler<
527        F: sealed::IntoOption<
528            impl 'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorization,
529        >,
530    >(
531        &self,
532        handler_or_none: F,
533    ) {
534        let ditto = self
535            .ditto
536            .upgrade()
537            .ok_or(ErrorKind::ReleasedDittoInstance)
538            .unwrap();
539        let ffi_callback = F::into_option(handler_or_none).map(|callback| {
540            Arc::new(move |raw: repr_c::Box<ffi_sdk::FfiConnectionRequest>| {
541                let connection_request = ConnectionRequest::new(raw);
542                let raw = connection_request.raw();
543                callback(connection_request).into_ffi(&raw);
544            })
545            .into()
546        });
547        ffi_sdk::dittoffi_presence_set_connection_request_handler(&ditto.ditto, ffi_callback)
548    }
549
550    /// Convenience around [`Self::set_connection_request_handler()`] that allows the callback to be
551    /// `async`.
552    ///
553    /// Not responding in time will lead to a handshake timeout, effectively rejecting the peer.
554    pub fn set_connection_request_handler_async<ConnectionRequestAuthorizationFut>(
555        &self,
556        async_callback: impl 'static
557            + Send
558            + Sync
559            + Fn(ConnectionRequest) -> ConnectionRequestAuthorizationFut,
560    ) where
561        ConnectionRequestAuthorizationFut:
562            'static + Send + Future<Output = ConnectionRequestAuthorization>,
563    {
564        use tokio::{runtime, sync::mpsc};
565
566        let ditto = self
567            .ditto
568            .upgrade()
569            .ok_or(ErrorKind::ReleasedDittoInstance)
570            .unwrap();
571
572        let new_mini_runtime = || {
573            // Using an unbounded channel to implement `task::spawn()`, like tokio does.
574            let (tx, mut rx) = mpsc::unbounded_channel();
575            let runtime = runtime::Builder::new_current_thread()
576                .enable_all()
577                .build()
578                .expect("failed to build `async` runtime");
579            ::std::thread::spawn(move || {
580                runtime.block_on(async move {
581                    while let Some(task) = rx.recv().await {
582                        () = task.await;
583                    }
584                })
585            });
586            tx
587        };
588
589        ffi_sdk::dittoffi_presence_set_connection_request_handler(
590            &ditto.ditto,
591            Some(
592                Arc::new(move |raw| {
593                    static MINI_RUNTIME: OnceLock<
594                        mpsc::UnboundedSender<Pin<Box<dyn Send + Future<Output = ()>>>>,
595                    > = OnceLock::new();
596
597                    let connection_request = ConnectionRequest::new(raw);
598                    let raw = connection_request.raw();
599                    let task_to_spawn_detached = {
600                        let async_callback_connection_request = async_callback(connection_request);
601                        async move {
602                            async_callback_connection_request.await.into_ffi(&raw);
603                        }
604                    };
605                    MINI_RUNTIME
606                        .get_or_init(new_mini_runtime)
607                        .send(Box::pin(task_to_spawn_detached))
608                        .expect("dedicated async runtime to be alive");
609                })
610                .into(),
611            ),
612        )
613    }
614}
615
616// TODO(v5): remove
617#[allow(deprecated)]
618impl Presence {
619    // TODO(v5): remove
620    #[doc(hidden)]
621    #[deprecated(note = "Use `.graph()` instead")]
622    pub fn exec(&self) -> PresenceGraph {
623        self.graph()
624    }
625
626    /// Receive [`PresenceGraph`] updates when peer connections change in the Ditto mesh.
627    ///
628    /// The returned [`PresenceObserver`] must be kept in scope to continue reciving updates.
629    ///
630    /// # Example
631    ///
632    /// Let's look at how we can use `.observe()` to register a callback and
633    /// receive updates whenever the presence of our Ditto mesh changes. We could say the
634    /// presence has changed if any peers have added or removed connections in the mesh.
635    ///
636    /// ```
637    /// # use std::sync::{Arc, Mutex};
638    /// use dittolive_ditto::prelude::*;
639    /// # fn example(ditto: &Ditto) -> anyhow::Result<()> {
640    ///
641    /// let mut maybe_prev_graph = Arc::new(Mutex::new(None));
642    /// let _presence_observer = ditto.presence().observe(move |graph| {
643    ///     let mut maybe_prev_graph = maybe_prev_graph.lock().unwrap();
644    ///
645    ///     let Some(prev_graph) = &*maybe_prev_graph else {
646    ///         // First presence update! Print what remote peers we see by their keys
647    ///         let remote_peers = graph
648    ///             .remote_peers
649    ///             .iter()
650    ///             .map(|peer| &peer.peer_key_string)
651    ///             .collect::<Vec<_>>();
652    ///         println!("Received first presence update! Remote peers: {remote_peers:?}");
653    ///         *maybe_prev_graph = Some(graph.clone());
654    ///         return;
655    ///     };
656    ///
657    ///     // Subsequent presence updates can compare the new graph against the old
658    ///     let prev_remote_peers = prev_graph
659    ///         .remote_peers
660    ///         .iter()
661    ///         .map(|peer| &peer.peer_key_string)
662    ///         .collect::<std::collections::HashSet<_>>();
663    ///     let latest_remote_peers = graph
664    ///         .remote_peers
665    ///         .iter()
666    ///         .map(|peer| &peer.peer_key_string)
667    ///         .collect::<std::collections::HashSet<_>>();
668    ///
669    ///     // Detect if new peers have joined the mesh
670    ///     let new_peers = latest_remote_peers
671    ///         .difference(&prev_remote_peers)
672    ///         .collect::<Vec<_>>();
673    ///     if !new_peers.is_empty() {
674    ///         println!("New peers joined the mesh! {new_peers:?}");
675    ///     }
676    ///
677    ///     // Detect if any peers left the mesh
678    ///     let lost_peers = prev_remote_peers
679    ///         .difference(&latest_remote_peers)
680    ///         .collect::<Vec<_>>();
681    ///     if !lost_peers.is_empty() {
682    ///         println!("Peers have left the mesh! {lost_peers:?}");
683    ///     }
684    ///
685    ///     *maybe_prev_graph = Some(graph.clone());
686    /// });
687    /// # Ok(())
688    /// # }
689    /// ```
690    ///
691    /// If instead you just need to check once to learn about which peers are connected
692    /// _right now_, try using the [`.graph()`] method instead.
693    ///
694    /// [`.graph()`]: Self::graph
695    #[doc(hidden)]
696    #[allow(deprecated)]
697    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
698    pub fn observe(
699        self: &Arc<Self>,
700        callback: impl Fn(&PresenceGraph) + Send + Sync + 'static,
701    ) -> observer_legacy::PresenceObserver {
702        if !*self.registered.lock().unwrap() {
703            self.subscribe();
704        }
705
706        let context = observer_legacy::PresenceObserverCtx::new(Box::new(callback));
707        let arc_context = Arc::new(context);
708        let weak_context = Arc::downgrade(&arc_context);
709
710        {
711            // New scope to minimize the time we hold the lock.
712            let mut observers = self.observers_v3.lock().unwrap();
713            observers.push(weak_context);
714        }
715
716        ::std::thread::spawn({
717            let this = self.retain();
718            move || {
719                let Some(ditto) = this.ditto.upgrade() else {
720                    return;
721                };
722
723                // Initial event.
724                let str_box = ffi_sdk::ditto_presence_v3(&ditto.ditto);
725                this.on_presence_v3(str_box.to_str())
726            }
727        });
728
729        observer_legacy::PresenceObserver::new(arc_context)
730    }
731
732    /// Add a peer observer and return a PresenceObserver to be able to drop it when desired.
733    // TODO(v5): remove
734    #[deprecated(note = "Use `presence().observe()` instead")]
735    pub(crate) fn add_observer(
736        self: &Arc<Self>,
737        handler: impl Fn(V2Presence) + Send + Sync + 'static,
738    ) -> PeersObserver {
739        let context = observer_legacy::PeersObserverCtx::new(Box::new(handler));
740        let arc_context = Arc::new(context);
741        let weak_context = Arc::downgrade(&arc_context);
742
743        if !*self.registered.lock().unwrap() {
744            self.subscribe();
745        }
746
747        {
748            // New scope to minimize the time we hold the lock.
749            let mut observers = self.observers_v2.lock().unwrap();
750            observers.push(weak_context);
751        }
752
753        ::std::thread::spawn({
754            let this = self.retain();
755            move || {
756                let Some(ditto) = this.ditto.upgrade() else {
757                    return;
758                };
759
760                // Initial event.
761                let str_box = ffi_sdk::ditto_presence_v2(&ditto.ditto);
762                this.on_presence_v2(str_box.to_str())
763            }
764        });
765        PeersObserver::new(arc_context)
766    }
767
768    /// C wrapper for calling the real callback on Presence
769    // TODO(v5): remove
770    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
771    unsafe extern "C" fn on_event_v2(ctx: *mut c_void, json: char_p::Ref<'_>, _: Blocking) {
772        Self::borrowing_from_ctx(ctx, |weak_ctx| {
773            if let Some(strong_ctx) = weak_ctx.upgrade() {
774                let presence_json_str = json.to_str();
775                strong_ctx.on_presence_v2(presence_json_str);
776            }
777        })
778    }
779
780    /// C wrapper for calling the real callback on Presence
781    // TODO(v5): remove
782    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
783    unsafe extern "C" fn on_event_v3(ctx: *mut c_void, json: char_p::Ref<'_>, _: Blocking) {
784        Self::borrowing_from_ctx(ctx, |weak_ctx| {
785            if let Some(strong_ctx) = weak_ctx.upgrade() {
786                let presence_json_str = json.to_str();
787                strong_ctx.on_presence_v3(presence_json_str);
788            }
789        })
790    }
791
792    // TODO(v5): remove
793    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
794    fn on_presence_v2(&self, json_str: &str) {
795        // v2 presence
796        let mut observers_v2 = self.observers_v2.lock().unwrap();
797        if observers_v2.is_empty() {
798            return;
799        }
800        if let Ok(presence_v2) = serde_json::from_str::<V2Presence>(json_str) {
801            observers_v2.retain(|weak_observer| {
802                if let Some(observer) = weak_observer.upgrade() {
803                    (observer.on_presence)(presence_v2.clone());
804                    true
805                } else {
806                    false
807                }
808            })
809        }
810    }
811
812    // TODO(v5): remove
813    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
814    fn on_presence_v3(&self, json_str: &str) {
815        // v3 presence
816        let mut observers_v3 = self.observers_v3.lock().unwrap();
817        if observers_v3.is_empty() {
818            return;
819        }
820        if let Ok(presence_graph) = serde_json::from_str(json_str) {
821            observers_v3.retain(|weak_observer| {
822                if let Some(observer) = weak_observer.upgrade() {
823                    (observer.on_presence)(&presence_graph);
824                    true
825                } else {
826                    false
827                }
828            })
829        }
830    }
831
832    // TODO(v5): remove
833    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
834    fn subscribe(self: &Arc<Self>) {
835        let ditto = self
836            .ditto
837            .upgrade()
838            .ok_or(ErrorKind::ReleasedDittoInstance)
839            .unwrap();
840
841        unsafe {
842            let weak_self = Arc::downgrade(self);
843            ffi_sdk::ditto_register_presence_v2_callback(
844                &ditto.ditto,
845                weak_self.as_ptr() as *mut _,
846                Some(Presence::retain),
847                Some(Presence::release),
848                Some(<unsafe extern "C" fn(_, char_p::Ref<'_>, _)>::into(
849                    Presence::on_event_v2,
850                )),
851            );
852            ffi_sdk::ditto_register_presence_callback_v3(
853                &ditto.ditto,
854                weak_self.as_ptr() as *mut _,
855                Some(Presence::retain),
856                Some(Presence::release),
857                Some(<unsafe extern "C" fn(_, char_p::Ref<'_>, _)>::into(
858                    Presence::on_event_v3,
859                )),
860            );
861            // Guards against mistakes such as using `.into_raw()` rather than `.as_ptr()`.
862            drop(weak_self);
863        }
864
865        *self.registered.lock().unwrap() = true;
866    }
867
868    /// `ctx` is, conceptually-speaking a `&'short_lived Weak< DiskUsageObserverCtx<F> >`.
869    ///
870    /// This scoped/callback API embodies that.
871    // TODO(v5): remove
872    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
873    #[track_caller]
874    unsafe fn borrowing_from_ctx(ctx: *const c_void, yielding: impl FnOnce(&Weak<Presence>)) {
875        let weak_ctx = ::core::mem::ManuallyDrop::new(Weak::from_raw(ctx.cast::<Presence>()));
876        yielding(&weak_ctx)
877    }
878
879    // TODO(v5): remove
880    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
881    unsafe extern "C" fn retain(ctx: *mut c_void, _: Blocking) {
882        Self::borrowing_from_ctx(ctx, |weak_ctx| _ = Weak::into_raw(weak_ctx.clone()))
883    }
884
885    // TODO(v5): remove
886    #[deprecated(note = "Use `ditto.presence().register_observer(...)` instead")]
887    unsafe extern "C" fn release(ctx: *mut c_void, _: Blocking) {
888        drop(Weak::<Presence>::from_raw(ctx.cast()))
889    }
890}
891
892/// Defines a simplified connection type between peers for reporting presence
893/// info.
894///
895/// These connections indicate P2P connections _only_. A connection to the Big Peer
896/// is recorded by a simple boolean flag on the [`Peer`] type.
897#[non_exhaustive]
898#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
899pub enum ConnectionType {
900    /// Connected to the remote peer via Bluetooth Low-Energy (BLE).
901    Bluetooth,
902
903    /// Connected to the remote peer via LAN, e.g. home or office WiFi.
904    AccessPoint,
905
906    /// Direct WiFi between peers, using AWDL (Apple) or WiFi Aware (Android).
907    P2PWiFi,
908
909    /// Connected to the remote peer via WebSocket, a bidirectional stream over http(s).
910    WebSocket,
911
912    /// Unknown connection type, remote peer is likely a higher version.
913    #[doc(hidden)]
914    Unknown,
915}
916
917impl ConnectionType {
918    pub(crate) fn from_ffi(ffi: ::ffi_sdk::ConnectionType) -> Self {
919        match ffi {
920            ::ffi_sdk::ConnectionType::Bluetooth => Self::Bluetooth,
921            ::ffi_sdk::ConnectionType::AccessPoint => Self::AccessPoint,
922            ::ffi_sdk::ConnectionType::P2PWiFi => Self::P2PWiFi,
923            ::ffi_sdk::ConnectionType::WebSocket => Self::WebSocket,
924            #[allow(unreachable_patterns)]
925            _ => {
926                debug!(connection_type = ?ffi, "got unknown `ConnectionType`");
927                Self::Unknown
928            }
929        }
930    }
931}
932
933mod sealed {
934    use super::*;
935
936    /// A trait implemented for both `None` and instances of
937    /// `'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorization`.
938    ///
939    /// Used to keep perfect API parity with that of other SDKs.
940    pub trait IntoOption<
941        F: 'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorization,
942    >
943    {
944        fn into_option(_: Self) -> Option<F>;
945    }
946
947    impl IntoOption<fn(ConnectionRequest) -> ConnectionRequestAuthorization>
948        for Option<::never_say_never::Never>
949    {
950        fn into_option(_: Self) -> Option<fn(ConnectionRequest) -> ConnectionRequestAuthorization> {
951            None
952        }
953    }
954
955    impl<F> IntoOption<F> for F
956    where
957        F: 'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorization,
958    {
959        fn into_option(f: Self) -> Option<Self> {
960            Some(f)
961        }
962    }
963}