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}