mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-06-30 21:20:09 +00:00
Merge 2ff607dfe96e35bc71d3fc830974b5f695e2b61a into ebae3317d6dba08af924b937abe2be9b62404abc
This commit is contained in:
commit
7ae2d55cbf
46
.github/scripts/mock-registry.py
vendored
Normal file
46
.github/scripts/mock-registry.py
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Mock keypackage/account registry for the chat-cli CI smoketest.
|
||||||
|
|
||||||
|
On startup the client registers its keypackage and account bundle. Publishing the
|
||||||
|
bundle first fetches any existing record, so the stub answers:
|
||||||
|
|
||||||
|
* POST /v0/keypackage, POST /v0/account -> 200 (accept the write)
|
||||||
|
* GET (any) -> 404 (nothing published yet)
|
||||||
|
|
||||||
|
A 404 is what a fresh account looks like, which the client reads as "no existing
|
||||||
|
record". This validates nothing — it only unblocks the smoketest; protocol-level
|
||||||
|
behavior is covered by the workspace tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
# Match the client's HTTP/1.1 requests so reqwest frames the response body.
|
||||||
|
protocol_version = "HTTP/1.1"
|
||||||
|
|
||||||
|
def _drain(self):
|
||||||
|
# Consume the request body so the client's request completes cleanly.
|
||||||
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
|
if length:
|
||||||
|
self.rfile.read(length)
|
||||||
|
|
||||||
|
def _reply(self, status):
|
||||||
|
self.send_response(status)
|
||||||
|
self.send_header("Content-Length", "0")
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
self._drain()
|
||||||
|
self._reply(200)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self._drain()
|
||||||
|
self._reply(404)
|
||||||
|
|
||||||
|
def log_message(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
HTTPServer(("127.0.0.1", 18080), Handler).serve_forever()
|
||||||
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@ -74,4 +74,15 @@ jobs:
|
|||||||
- name: Build chat-cli (logos-delivery)
|
- name: Build chat-cli (logos-delivery)
|
||||||
run: nix develop -c bash -c 'LOGOS_DELIVERY_LIB_DIR=./result/lib cargo build --release -p chat-cli'
|
run: nix develop -c bash -c 'LOGOS_DELIVERY_LIB_DIR=./result/lib cargo build --release -p chat-cli'
|
||||||
- name: Run chat-cli smoketest
|
- name: Run chat-cli smoketest
|
||||||
run: nix develop -c ./target/release/chat-cli --name ci-test --smoketest
|
# The client registers against the keypackage/account registry on
|
||||||
|
# startup, so stand up a mock that accepts those calls. It runs on the
|
||||||
|
# system Python (no Nix needed); only chat-cli needs the dev shell.
|
||||||
|
run: |
|
||||||
|
python3 .github/scripts/mock-registry.py &
|
||||||
|
mock_pid=$!
|
||||||
|
trap 'kill "$mock_pid" 2>/dev/null || true' EXIT
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
curl -s -o /dev/null http://127.0.0.1:18080/ && break
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
nix develop -c ./target/release/chat-cli --name ci-test --smoketest --registry-url http://127.0.0.1:18080
|
||||||
|
|||||||
@ -9,8 +9,7 @@ use anyhow::{Context, Result};
|
|||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use logos_chat::{
|
use logos_chat::{
|
||||||
ChatClient, ChatClientBuilder, ChatStore, Event, HttpRegistry, IdentityProvider,
|
ChatClient, ChatStore, Event, IdentityProvider, LogosChatClient, RegistrationService, Transport,
|
||||||
RegistrationService, StorageConfig, Transport,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use components::{EmbeddedP2pDeliveryService, P2pConfig};
|
use components::{EmbeddedP2pDeliveryService, P2pConfig};
|
||||||
@ -79,9 +78,9 @@ struct Cli {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
smoketest: bool,
|
smoketest: bool,
|
||||||
|
|
||||||
/// Optional KeyPackage registry base URL. When set, uses the HTTP-backed
|
/// Override the Logos registry endpoint (account + keypackage store). When
|
||||||
/// registry instead of the in-memory `EphemeralRegistry`.
|
/// omitted, the preconfigured endpoint is used.
|
||||||
/// Example: `--registry-url http://localhost:8080`.
|
/// Example: `--registry-url http://127.0.0.1:18080`.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
registry_url: Option<String>,
|
registry_url: Option<String>,
|
||||||
}
|
}
|
||||||
@ -127,33 +126,12 @@ fn run<T: Transport>(transport: T, cli: &Cli) -> Result<()> {
|
|||||||
.to_str()
|
.to_str()
|
||||||
.context("db path contains non-UTF-8 characters")?
|
.context("db path contains non-UTF-8 characters")?
|
||||||
.to_string();
|
.to_string();
|
||||||
let storage = StorageConfig::Encrypted {
|
|
||||||
path: db_str,
|
|
||||||
key: "chat-cli".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match cli.registry_url.as_deref() {
|
let (client, events) =
|
||||||
Some(url) => {
|
LogosChatClient::open(transport, db_str, "chat-cli", cli.registry_url.as_deref())
|
||||||
let registry = HttpRegistry::new(url);
|
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
||||||
let (client, events) = ChatClientBuilder::new()
|
.context("failed to open chat client")?;
|
||||||
.transport(transport)
|
launch_tui(client, events, cli)
|
||||||
.storage_config(storage)
|
|
||||||
.registration(registry)
|
|
||||||
.build()
|
|
||||||
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
|
||||||
.context("failed to open chat client with HTTP registry")?;
|
|
||||||
launch_tui(client, events, cli)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let (client, events) = ChatClientBuilder::new()
|
|
||||||
.transport(transport)
|
|
||||||
.storage_config(storage)
|
|
||||||
.build()
|
|
||||||
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
|
||||||
.context("failed to open chat client")?;
|
|
||||||
launch_tui(client, events, cli)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch_tui<I, T, R, S>(
|
fn launch_tui<I, T, R, S>(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ mod delegate;
|
|||||||
mod delivery_in_process;
|
mod delivery_in_process;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod event;
|
mod event;
|
||||||
|
mod logos;
|
||||||
|
|
||||||
pub use builder::{ChatClientBuilder, Unset};
|
pub use builder::{ChatClientBuilder, Unset};
|
||||||
pub use client::{ChatClient, Transport};
|
pub use client::{ChatClient, Transport};
|
||||||
@ -11,6 +12,7 @@ pub use delegate::DelegateSigner;
|
|||||||
pub use delivery_in_process::{InProcessDelivery, MessageBus};
|
pub use delivery_in_process::{InProcessDelivery, MessageBus};
|
||||||
pub use errors::ClientError;
|
pub use errors::ClientError;
|
||||||
pub use event::{Event, MessageSender};
|
pub use event::{Event, MessageSender};
|
||||||
|
pub use logos::LogosChatClient;
|
||||||
|
|
||||||
// Re-export types callers need to interact with ChatClient.
|
// Re-export types callers need to interact with ChatClient.
|
||||||
pub use libchat::{
|
pub use libchat::{
|
||||||
|
|||||||
61
crates/client/src/logos.rs
Normal file
61
crates/client/src/logos.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//! The opinionated Logos client.
|
||||||
|
//!
|
||||||
|
//! [`ChatClientBuilder`] is generic and can only default the zero-config
|
||||||
|
//! components (random identity, ephemeral registry, in-memory storage) — it has
|
||||||
|
//! no way to know a registry endpoint or a database path, so its defaults are
|
||||||
|
//! the test-grade ones. `LogosChatClient` is the layer that *does* commit to a
|
||||||
|
//! stack: a delegate identity, the HTTP keypackage + account registry, and
|
||||||
|
//! encrypted on-disk storage. It exists so independently built clients share the
|
||||||
|
//! same production services instead of each re-deriving them.
|
||||||
|
//!
|
||||||
|
//! Only the transport is left to the caller: it carries native dependencies and
|
||||||
|
//! environment-specific configuration that belong to the binary, not here.
|
||||||
|
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use libchat::{ChatStorage, StorageConfig};
|
||||||
|
|
||||||
|
use crate::ChatClientBuilder;
|
||||||
|
use crate::client::{ChatClient, Transport};
|
||||||
|
use crate::delegate::DelegateSigner;
|
||||||
|
use crate::errors::ClientError;
|
||||||
|
use crate::event::Event;
|
||||||
|
use components::HttpRegistry;
|
||||||
|
|
||||||
|
// The endpoint for account and keypackage registration service.
|
||||||
|
const REGISTRY_ENDPOINT: &str = "https://devnet.chat-kc.logos.co";
|
||||||
|
|
||||||
|
/// A [`ChatClient`] wired to the Logos service stack: a [`DelegateSigner`]
|
||||||
|
/// identity, the HTTP keypackage + account registry ([`HttpRegistry`], which is
|
||||||
|
/// both the keypackage store and the account → device directory), and encrypted
|
||||||
|
/// [`ChatStorage`]. Only the transport `T` is supplied by the caller.
|
||||||
|
pub type LogosChatClient<T> = ChatClient<DelegateSigner, T, HttpRegistry, ChatStorage>;
|
||||||
|
|
||||||
|
impl<T> LogosChatClient<T>
|
||||||
|
where
|
||||||
|
T: Transport + Send + 'static,
|
||||||
|
{
|
||||||
|
/// Open a client on the Logos stack over `transport`, persisting to the
|
||||||
|
/// encrypted database at `db_path` unlocked with `db_key`. When `registry_url`
|
||||||
|
/// is `Some`, it overrides the preconfigured registry endpoint (e.g. a local
|
||||||
|
/// deployment); otherwise the baked-in endpoint is used.
|
||||||
|
///
|
||||||
|
/// `db_path` is a per-client location and `db_key` is a secret, so both are
|
||||||
|
/// caller-supplied — never baked into the library.
|
||||||
|
pub fn open(
|
||||||
|
transport: T,
|
||||||
|
db_path: impl Into<String>,
|
||||||
|
db_key: impl Into<String>,
|
||||||
|
registry_url: Option<&str>,
|
||||||
|
) -> Result<(Self, Receiver<Event>), ClientError> {
|
||||||
|
let endpoint = registry_url.unwrap_or(REGISTRY_ENDPOINT);
|
||||||
|
ChatClientBuilder::new()
|
||||||
|
.ident(DelegateSigner::random())
|
||||||
|
.transport(transport)
|
||||||
|
.registration(HttpRegistry::new(endpoint))
|
||||||
|
.storage_config(StorageConfig::Encrypted {
|
||||||
|
path: db_path.into(),
|
||||||
|
key: db_key.into(),
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user