Struct dittolive_ditto::presence::Presence

source ·
pub struct Presence { /* private fields */ }
Expand description

The entrypoint to the Presence API, obtained with ditto.presence().

See the presence module for guide-level docs and examples

Implementations§

source§

impl Presence

source

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.

source

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

source

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",
}))?;
source

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.

source

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.

source

pub fn peer_metadata_serde<T: DeserializeOwned>(&self) -> Result<T>

source

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().

source

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);
source

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.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CompatExt for T

§

fn compat(self) -> Compat<T>

Applies the [Compat] adapter by value. Read more
§

fn compat_ref(&self) -> Compat<&T>

Applies the [Compat] adapter by shared reference. Read more
§

fn compat_mut(&mut self) -> Compat<&mut T>

Applies the [Compat] adapter by mutable reference. Read more
§

impl<T> FitForCBox for T

§

type CBoxWrapped = Box_<T>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> ManuallyDropMut for T

§

type Ret = ManuallyDrop<T>

§

fn manually_drop_mut<'__>(&'__ mut self) -> &'__ mut ManuallyDrop<T>

§

impl<T> To for T
where T: ?Sized,

§

fn to<T>(self) -> T
where Self: Into<T>,

Converts to T by calling Into<T>::into.
§

fn try_to<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Tries to convert to T by calling TryInto<T>::try_into.
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<F> ZeroSizedElseWrathOfTheGඞds for F