2026-07-09 15:12:58 +08:00
|
|
|
//! The opinionated Logos client.
|
|
|
|
|
//!
|
|
|
|
|
//! [`open`] commits to the Logos service stack so independently built clients
|
|
|
|
|
//! share the same production services instead of each re-deriving them: a
|
|
|
|
|
//! delegate identity, the HTTP keypackage + account registry, and encrypted
|
|
|
|
|
//! on-disk storage. The stack is generic over the transport — any
|
|
|
|
|
//! [`Transport`] can be injected via [`open_with_transport`] — and the
|
|
|
|
|
//! concrete [`LogosChatClient`] commits to the embedded logos-delivery node,
|
|
|
|
|
//! whose native `liblogosdelivery` link keeps this crate outside the
|
|
|
|
|
//! workspace's default members.
|
|
|
|
|
//!
|
|
|
|
|
//! [`LogosChatClient`] points at `ChatClient`, which lives in
|
|
|
|
|
//! `logos-generic-chat`, so Rust's inherent-impl rule keeps the open
|
|
|
|
|
//! constructors off the alias; they are crate-level functions ([`open`],
|
|
|
|
|
//! [`open_with_transport`]) taking the all-inclusive [`LogosConfig`] instead.
|
|
|
|
|
|
|
|
|
|
use components::HttpRegistry;
|
|
|
|
|
use crossbeam_channel::Receiver;
|
|
|
|
|
use embedded_logos_delivery::{EmbeddedLogosDelivery, P2pConfig};
|
|
|
|
|
use libchat::{ChatStorage, StorageConfig};
|
|
|
|
|
use logos_account::TestLogosAccount;
|
|
|
|
|
|
|
|
|
|
use logos_generic_chat::{
|
2026-07-20 09:33:29 +08:00
|
|
|
ChatClient, ChatClientBuilder, ClientError, DelegateSigner, Event, GroupV2Config, Transport,
|
2026-07-09 15:12:58 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// The endpoint for the account and keypackage registration service.
|
|
|
|
|
pub const REGISTRY_ENDPOINT: &str = "https://devnet.chat-kc.logos.co";
|
|
|
|
|
|
|
|
|
|
/// Configuration for opening a Logos client.
|
|
|
|
|
///
|
|
|
|
|
/// `db_path` (a per-client location) and `db_key` (a secret) are required and
|
|
|
|
|
/// never baked into the library. Everything else defaults: the registry
|
2026-07-20 09:33:29 +08:00
|
|
|
/// endpoint to the baked-in Logos value, the embedded node's p2p settings to
|
|
|
|
|
/// [`P2pConfig::default`], and the GroupV2 timing to the de-mls library
|
|
|
|
|
/// defaults; override them with [`set_registry_url`](Self::set_registry_url),
|
|
|
|
|
/// [`set_p2p_config`](Self::set_p2p_config), and
|
|
|
|
|
/// [`set_group_v2_config`](Self::set_group_v2_config).
|
2026-07-09 15:12:58 +08:00
|
|
|
pub struct LogosConfig {
|
|
|
|
|
db_path: String,
|
|
|
|
|
db_key: String,
|
|
|
|
|
registry_url: String,
|
|
|
|
|
p2p_config: P2pConfig,
|
2026-07-20 09:33:29 +08:00
|
|
|
group_v2_config: Option<GroupV2Config>,
|
2026-07-09 15:12:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LogosConfig {
|
|
|
|
|
/// Config for the required per-client `db_path` and `db_key`. The registry
|
|
|
|
|
/// endpoint defaults to the baked-in Logos value; override it with
|
|
|
|
|
/// [`set_registry_url`](Self::set_registry_url).
|
|
|
|
|
pub fn new(db_path: impl Into<String>, db_key: impl Into<String>) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
db_path: db_path.into(),
|
|
|
|
|
db_key: db_key.into(),
|
|
|
|
|
registry_url: REGISTRY_ENDPOINT.to_string(),
|
|
|
|
|
p2p_config: P2pConfig::default(),
|
2026-07-20 09:33:29 +08:00
|
|
|
group_v2_config: None,
|
2026-07-09 15:12:58 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Override the registry endpoint (account + keypackage store; defaults to
|
|
|
|
|
/// the baked-in [`REGISTRY_ENDPOINT`]).
|
|
|
|
|
pub fn set_registry_url(&mut self, registry_url: impl Into<String>) {
|
|
|
|
|
self.registry_url = registry_url.into();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Override the embedded node's p2p settings (defaults to
|
|
|
|
|
/// [`P2pConfig::default`]). Only [`open`] starts an embedded node, so
|
|
|
|
|
/// [`open_with_transport`] ignores this.
|
|
|
|
|
pub fn set_p2p_config(&mut self, p2p_config: P2pConfig) {
|
|
|
|
|
self.p2p_config = p2p_config;
|
|
|
|
|
}
|
2026-07-20 09:33:29 +08:00
|
|
|
|
|
|
|
|
/// Override the GroupV2 timing/policy this client creates or joins groups
|
|
|
|
|
/// with (defaults to the de-mls library defaults).
|
|
|
|
|
///
|
|
|
|
|
/// # Deprecated
|
|
|
|
|
///
|
|
|
|
|
/// This is not a supported pathway for future use. Exposing the raw GroupV2
|
|
|
|
|
/// timing parameters to applications is a temporary workaround for slow
|
|
|
|
|
/// group startup: the values are interdependent (wrong combinations can
|
|
|
|
|
/// deadlock) and are not something an application can reasonably choose in a
|
|
|
|
|
/// way that stays interoperable across applications and future group
|
|
|
|
|
/// versions. The intended replacement is a wallclock/timer abstraction that
|
|
|
|
|
/// controls DeMLS wait timers without leaking these parameters, so do not
|
|
|
|
|
/// build on this method — it will be removed once that lands.
|
|
|
|
|
#[deprecated(
|
|
|
|
|
note = "unsupported pathway; exposing raw GroupV2 timing parameters is a \
|
|
|
|
|
temporary workaround and will be removed once a wallclock/timer \
|
|
|
|
|
abstraction replaces it"
|
|
|
|
|
)]
|
|
|
|
|
pub fn set_group_v2_config(&mut self, group_v2_config: GroupV2Config) {
|
|
|
|
|
self.group_v2_config = Some(group_v2_config);
|
|
|
|
|
}
|
2026-07-09 15:12:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Open a client on the Logos stack per `config`, starting an embedded
|
|
|
|
|
/// logos-delivery node per its p2p settings as the transport. A convenience
|
|
|
|
|
/// over [`open_with_transport`] that commits to the [`LogosChatClient`]
|
|
|
|
|
/// transport.
|
|
|
|
|
pub fn open(config: LogosConfig) -> Result<(LogosChatClient, Receiver<Event>), ClientError> {
|
|
|
|
|
let transport = EmbeddedLogosDelivery::start(config.p2p_config.clone())
|
|
|
|
|
.map_err(|e| ClientError::Transport(e.to_string()))?;
|
|
|
|
|
open_with_transport(config, transport)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Open a client on the Logos stack per `config` with the injected transport,
|
|
|
|
|
/// persisting to the encrypted database.
|
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
|
pub fn open_with_transport<T: Transport>(
|
|
|
|
|
config: LogosConfig,
|
|
|
|
|
transport: T,
|
|
|
|
|
) -> Result<(ChatClient<T, HttpRegistry, ChatStorage>, Receiver<Event>), ClientError> {
|
|
|
|
|
// A fresh account endorsing a fresh delegate each open: the account
|
|
|
|
|
// key is dropped after publishing the bundle, so devices cannot be
|
|
|
|
|
// added later. A caller-supplied, custody-holding account replaces
|
|
|
|
|
// this once the platform provides one.
|
|
|
|
|
let account = TestLogosAccount::new();
|
|
|
|
|
let delegate = DelegateSigner::random();
|
|
|
|
|
let mut registry = HttpRegistry::new(config.registry_url);
|
|
|
|
|
account
|
|
|
|
|
.add_delegate_signer(&mut registry, delegate.public_key())
|
|
|
|
|
.map_err(|e| ClientError::BundlePublish(e.to_string()))?;
|
2026-07-20 09:33:29 +08:00
|
|
|
let mut builder = ChatClientBuilder::new(account.address())
|
2026-07-09 15:12:58 +08:00
|
|
|
.ident(delegate)
|
|
|
|
|
.transport(transport)
|
|
|
|
|
.registration(registry)
|
|
|
|
|
.storage_config(StorageConfig::Encrypted {
|
|
|
|
|
path: config.db_path,
|
|
|
|
|
key: config.db_key,
|
2026-07-20 09:33:29 +08:00
|
|
|
});
|
|
|
|
|
if let Some(group_v2) = config.group_v2_config {
|
|
|
|
|
builder = builder.group_v2_config(group_v2);
|
|
|
|
|
}
|
|
|
|
|
builder.build()
|
2026-07-09 15:12:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The Logos client: a [`ChatClient`] wired to the Logos service stack — a
|
|
|
|
|
/// [`DelegateSigner`] identity acting for a fresh dev account, the HTTP
|
|
|
|
|
/// keypackage + account registry ([`HttpRegistry`], which is both the
|
|
|
|
|
/// keypackage store and the account → device directory), and encrypted
|
|
|
|
|
/// [`ChatStorage`] — running an embedded logos-delivery node as its
|
|
|
|
|
/// transport. Open one with [`open`], or swap the transport via
|
|
|
|
|
/// [`open_with_transport`].
|
|
|
|
|
pub type LogosChatClient = ChatClient<EmbeddedLogosDelivery, HttpRegistry, ChatStorage>;
|