Struct dittolive_ditto::presence::Presence
source · pub struct Presence { /* private fields */ }
Expand description
The entrypoint to the Presence API, obtained with ditto.presence()
.
Implementations§
source§impl Presence
impl Presence
sourcepub fn graph(&self) -> PresenceGraph
pub fn graph(&self) -> PresenceGraph
Return an immediate view of peer connections as a PresenceGraph
.
§Example
Let’s take a look at how to request a PresenceGraph
and how to read through it:
use dittolive_ditto::prelude::*;
// Get an immediate `PresenceGraph` showing current connections
let graph = ditto.presence().graph();
let my_key = &graph.local_peer.peer_key_string;
let my_connections = &graph.local_peer.connections;
// Let's find all peers that are directly connected to me, the local peer
let direct_peers = my_connections
.iter()
.map(|connection| {
// Choose the peer in this connection that is not me
if connection.peer_key_string1 == *my_key {
&connection.peer_key_string2
} else {
&connection.peer_key_string1
}
})
.collect::<Vec<_>>();
println!("My direct peers are {direct_peers:?}");
// Let's look up some details about _all_ the remote peers we can see
let remote_peers_summary = graph
.remote_peers
.iter()
.map(|peer| {
// Peer Key => (Name, OS, Connection Count)
(
&peer.peer_key_string,
(&peer.device_name, &peer.os, peer.connections.len()),
)
})
.collect::<std::collections::BTreeMap<_, _>>();
println!("A summary of my remote peers looks like this: {remote_peers_summary:#?}");
Please note that obtaining a PresenceGraph
via .graph()
only shows a
snapshot of what devices were present when the call was made. In order to watch
changes to device presence, we’ll need to use .observe(...)
to register a
callback and receive updates.
sourcepub fn observe(
self: &Arc<Self>,
callback: impl Fn(&PresenceGraph) + Send + Sync + 'static,
) -> PresenceObserver
pub fn observe( self: &Arc<Self>, callback: impl Fn(&PresenceGraph) + Send + Sync + 'static, ) -> PresenceObserver
Receive PresenceGraph
updates when peer connections change in the Ditto mesh.
The returned PresenceObserver
must be kept in scope to continue reciving updates.
§Example
Let’s look at how we can use .observe()
to register a callback and
receive updates whenever the presence of our Ditto mesh changes. We could say the
presence has changed if any peers have added or removed connections in the mesh.
use dittolive_ditto::prelude::*;
let mut maybe_prev_graph = Arc::new(Mutex::new(None));
let _presence_observer = ditto.presence().observe(move |graph| {
let mut maybe_prev_graph = maybe_prev_graph.lock().unwrap();
let Some(prev_graph) = &*maybe_prev_graph else {
// First presence update! Print what remote peers we see by their keys
let remote_peers = graph
.remote_peers
.iter()
.map(|peer| &peer.peer_key_string)
.collect::<Vec<_>>();
println!("Received first presence update! Remote peers: {remote_peers:?}");
*maybe_prev_graph = Some(graph.clone());
return;
};
// Subsequent presence updates can compare the new graph against the old
let prev_remote_peers = prev_graph
.remote_peers
.iter()
.map(|peer| &peer.peer_key_string)
.collect::<std::collections::HashSet<_>>();
let latest_remote_peers = graph
.remote_peers
.iter()
.map(|peer| &peer.peer_key_string)
.collect::<std::collections::HashSet<_>>();
// Detect if new peers have joined the mesh
let new_peers = latest_remote_peers
.difference(&prev_remote_peers)
.collect::<Vec<_>>();
if !new_peers.is_empty() {
println!("New peers joined the mesh! {new_peers:?}");
}
// Detect if any peers left the mesh
let lost_peers = prev_remote_peers
.difference(&latest_remote_peers)
.collect::<Vec<_>>();
if !lost_peers.is_empty() {
println!("Peers have left the mesh! {lost_peers:?}");
}
*maybe_prev_graph = Some(graph.clone());
});
If instead you just need to check once to learn about which peers are connected
right now, try using the .graph()
method instead.
source§impl Presence
impl Presence
sourcepub fn set_peer_metadata(
&self,
peer_metadata: &impl Serialize,
) -> Result<(), DittoError>
pub fn set_peer_metadata( &self, peer_metadata: &impl Serialize, ) -> Result<(), DittoError>
Set a dictionary of arbitrary data about this device to be shared with peers in the mesh.
This data is gossiped in the presence collection across the mesh. This can be useful to include extra metadata like app versions,capabilities, etc., to help peers decide who to interact with.
This peer information is persisted in the SDK, and thus needn’t be set at every start of Ditto.
§Security (and caveats)
This peer info will be signed by your peer key to prevent forgery of this info by other peers.
However, for compatibility, there is no attestation of the lack of peer info – that is, participants in the mesh could maliciously remove peer info. If this is a concern for your application, a workaround for this is to have your application require that peers have a signed peer info dictionary present.
Similarly, as there is no monotonic version counter or timestamp/expiration of the signed peer info, replay attacks (replacing the current info with previously, possibly outdated, signed info) are possible without counter-measures. If this is a concern for your application, you might consider including a counter or creation timestamp to prevent replays, depending on your use-case.
§Performance caveats
Because this information is included in the presence data that is gossiped among peers, the size of this peer info and the frequency it is updated can drastically affect performance if it is too large.
§Errors
Because of the performance implications, the serialized info dictionary is currently limited to 4KiB.
§Examples
use dittolive_ditto::prelude::*;
use serde_json::json;
ditto.presence().set_peer_metadata(&json!({
"app_version": "1.0.0",
}))?;
sourcepub fn set_peer_metadata_json_str(&self, json: &str) -> Result<(), DittoError>
pub fn set_peer_metadata_json_str(&self, json: &str) -> Result<(), DittoError>
Set arbitrary metadata formatted as JSON to be associated with the current peer.
The metadata must not exceed 4 KB in size. Expects JSON.
- See also:
Self::set_peer_metadata()
for details on usage of metadata.
sourcepub fn peer_metadata_json_str(&self) -> String
pub fn peer_metadata_json_str(&self) -> String
Metadata associated with the current peer as JSON-encoded data.
Other peers in the same mesh can access this user-provided dictionary of
metadata via the presence graph at Self::graph()
and when
evaluating connection requests using
Self::set_connection_request_handler()
. Use Self::set_peer_metadata()
or Self::set_peer_metadata_json_str()
to set this value.
sourcepub fn peer_metadata_serde<T: DeserializeOwned>(&self) -> Result<T>
pub fn peer_metadata_serde<T: DeserializeOwned>(&self) -> Result<T>
DeserializeOwned
convenience around Self::peer_metadata_json_str()
.
sourcepub fn peer_metadata(&self) -> Arc<JsonObject>
pub fn peer_metadata(&self) -> Arc<JsonObject>
Metadata associated with the current peer.
Other peers in the same mesh can access this user-provided dictionary of
metadata via the presence graph at Self::graph()
and when
evaluating connection requests using
Self::set_connection_request_handler()
. Use Self::set_peer_metadata()
or Self::set_peer_metadata_json_str()
to set this value.
This is a convenience property that wraps Self::peer_metadata_json_str()
.
sourcepub fn set_connection_request_handler<F: IntoOption<impl 'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorization>>(
&self,
handler_or_none: F,
)
pub fn set_connection_request_handler<F: IntoOption<impl 'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorization>>( &self, handler_or_none: F, )
Set this handler to control which peers in a Ditto mesh can connect to the current peer.
Each peer in a Ditto mesh will attempt to connect to other peers that it can reach. By default, the mesh will try and establish connections that optimize for the best overall connectivity between peers. However, you can set this handler to assert some control over which peers you connect to.
If set, this handler is called for every incoming connection request
from a remote peer and is passed the other peer’s peer_key
,
peer_metadata
, and identity_service_metadata
. The handler can then
accept or reject the request by returning an according
ConnectionRequestAuthorization
value. When the connection
request is rejected, the remote peer may retry the connection request
after a short delay.
Connection request handlers must reliably respond to requests within a short time: if a handler takes too long to return, the connection request will fall back to being denied. The response –currently— times out after 10 seconds, but this exact value may be subject to change in future releases.
- Note: the handler is called from a different thread (“background hook”).
- See also:
Self::peer_metadata()
§Example
/// Let's imagine the app we are maintaining has a bug in `1.2.3`:
const BUGGY_VERSION: &str = "1.2.3";
// We avoid problems in updated versions of our app with these ones by
// rejecting connections to them, like so:
ditto
.presence()
.set_connection_request_handler(|connection_request: ConnectionRequest| {
match connection_request
.peer_metadata()
.get("app_version")
.and_then(|it| it.as_str())
{
// Reject peers reporting a known buggy version or reporting no
// version at all.
Some(BUGGY_VERSION) | None => return ConnectionRequestAuthorization::Deny,
Some(_non_buggy_version) => { /* no reason to reject here */ }
}
// Potentially other checks/reasons to reject…
// Eventually:
ConnectionRequestAuthorization::Allow
});
// You can also unset the `connection_request_handler` by setting it to `None`.
// This uses the default handler, which accepts all requests.
ditto.presence().set_connection_request_handler(None);
sourcepub fn set_connection_request_handler_async<ConnectionRequestAuthorizationFut>(
&self,
async_callback: impl 'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorizationFut,
)where
ConnectionRequestAuthorizationFut: 'static + Send + Future<Output = ConnectionRequestAuthorization>,
pub fn set_connection_request_handler_async<ConnectionRequestAuthorizationFut>(
&self,
async_callback: impl 'static + Send + Sync + Fn(ConnectionRequest) -> ConnectionRequestAuthorizationFut,
)where
ConnectionRequestAuthorizationFut: 'static + Send + Future<Output = ConnectionRequestAuthorization>,
Convenience around Self::set_connection_request_handler()
that allows the callback to be
async
.
Not responding in time will lead to a handshake timeout, effectively rejecting the peer.