mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-08-25 15:11:15 +00:00
fix: signer-scoped DirectV1 routing (#162)
Core is no longer account-aware: the client resolves an account address to signer ids via the account directory, and the signer's verifying-key hex serves as registry key, inbox subscription, and Welcome routing target end to end. The MLS credential stays the full id(). - GroupV2 reads the de-mls member id from the fetched key package and maps it to the signer id the welcome is delivered to. - All account machinery (directory trait, bundle codec, resolution) moves out of core into logos-account; the RegistrationService supertrait and Core::account_directory() are gone, and the client holds its own directory handle. - The account exposes functionality, never a signer: add_delegate_signer does the lamport upsert and signs internally. - Every client acts for an account (ChatClientBuilder::new(account)). DelegateSigner is a pure keypair; the client composes the wire credential from the signer and the account, so the association is client state. addr() is the account address. - resolve_device_ids fails fast (NotAnAccountKey / NoDeviceBundle / Directory) instead of falling back to treating an unresolved address as a signer id. LogosChatClient::open and chat-cli mint and publish a dev account each launch. - EphemeralRegistry keys key packages by hex pubkey like HttpRegistry. Supersedes #155 (routing_id).
This commit is contained in:
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::Result;
|
||||
use arboard::Clipboard;
|
||||
use crossbeam_channel::Receiver;
|
||||
use logos_chat::{ChatClient, ChatStore, Event, IdentityProvider, RegistrationService, Transport};
|
||||
use logos_chat::{AccountDirectory, ChatClient, ChatStore, Event, RegistrationService, Transport};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::now;
|
||||
@@ -41,14 +41,13 @@ pub struct AppState {
|
||||
pub active_chat: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ChatApp<I, T, R, S>
|
||||
pub struct ChatApp<T, R, S>
|
||||
where
|
||||
I: IdentityProvider + Send + 'static,
|
||||
T: Transport,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send + 'static,
|
||||
{
|
||||
pub client: ChatClient<I, T, R, S>,
|
||||
pub client: ChatClient<T, R, S>,
|
||||
events: Receiver<Event>,
|
||||
pub state: AppState,
|
||||
/// Ephemeral command output — not persisted, cleared on chat switch.
|
||||
@@ -59,15 +58,14 @@ where
|
||||
state_path: PathBuf,
|
||||
}
|
||||
|
||||
impl<I, T, R, S> ChatApp<I, T, R, S>
|
||||
impl<T, R, S> ChatApp<T, R, S>
|
||||
where
|
||||
I: IdentityProvider + Send,
|
||||
T: Transport,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send,
|
||||
{
|
||||
pub fn new(
|
||||
client: ChatClient<I, T, R, S>,
|
||||
client: ChatClient<T, R, S>,
|
||||
events: Receiver<Event>,
|
||||
user_name: &str,
|
||||
data_dir: &Path,
|
||||
|
||||
+20
-12
@@ -8,9 +8,10 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use crossbeam_channel::Receiver;
|
||||
use logos_account::TestLogosAccount;
|
||||
use logos_chat::{
|
||||
ChatClient, ChatClientBuilder, ChatStore, DelegateSigner, Event, HttpRegistry,
|
||||
IdentityProvider, LogosChatClient, NETWORK_PRESET, REGISTRY_ENDPOINT, RegistrationService,
|
||||
AccountDirectory, ChatClient, ChatClientBuilder, ChatStore, DelegateSigner, Event,
|
||||
HttpRegistry, LogosChatClient, NETWORK_PRESET, REGISTRY_ENDPOINT, RegistrationService,
|
||||
StorageConfig, Transport,
|
||||
};
|
||||
|
||||
@@ -106,10 +107,19 @@ fn main() -> Result<()> {
|
||||
.context("failed to create file transport")?;
|
||||
|
||||
let endpoint = cli.registry_url.as_deref().unwrap_or(REGISTRY_ENDPOINT);
|
||||
let (client, events) = ChatClientBuilder::new()
|
||||
.ident(DelegateSigner::random())
|
||||
// A fresh dev account endorsing a fresh delegate each launch,
|
||||
// mirroring `LogosChatClient::open`.
|
||||
let account = TestLogosAccount::new();
|
||||
let delegate = DelegateSigner::random();
|
||||
let mut registry = HttpRegistry::new(endpoint);
|
||||
account
|
||||
.add_delegate_signer(&mut registry, delegate.public_key())
|
||||
.map_err(|e| anyhow::anyhow!("{e:?}"))
|
||||
.context("failed to publish the device bundle")?;
|
||||
let (client, events) = ChatClientBuilder::new(account.address())
|
||||
.ident(delegate)
|
||||
.transport(transport)
|
||||
.registration(HttpRegistry::new(endpoint))
|
||||
.registration(registry)
|
||||
.storage_config(StorageConfig::Encrypted {
|
||||
path: db_str,
|
||||
key: "chat-cli".to_string(),
|
||||
@@ -135,15 +145,14 @@ fn db_path(cli: &Cli) -> Result<String> {
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn launch_tui<I, T, R, S>(
|
||||
client: ChatClient<I, T, R, S>,
|
||||
fn launch_tui<T, R, S>(
|
||||
client: ChatClient<T, R, S>,
|
||||
events: Receiver<Event>,
|
||||
cli: &Cli,
|
||||
) -> Result<()>
|
||||
where
|
||||
I: IdentityProvider + Send,
|
||||
T: Transport,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send,
|
||||
{
|
||||
let mut app = ChatApp::new(client, events, &cli.name, &cli.data)?;
|
||||
@@ -158,11 +167,10 @@ where
|
||||
result
|
||||
}
|
||||
|
||||
fn run_app<I, T, R, S>(terminal: &mut ui::Tui, app: &mut ChatApp<I, T, R, S>) -> Result<()>
|
||||
fn run_app<T, R, S>(terminal: &mut ui::Tui, app: &mut ChatApp<T, R, S>) -> Result<()>
|
||||
where
|
||||
I: IdentityProvider + Send,
|
||||
T: Transport,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send,
|
||||
{
|
||||
loop {
|
||||
|
||||
+13
-19
@@ -16,7 +16,7 @@ use ratatui::{
|
||||
widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
use logos_chat::{ChatStore, IdentityProvider, RegistrationService, Transport};
|
||||
use logos_chat::{AccountDirectory, ChatStore, RegistrationService, Transport};
|
||||
|
||||
use crate::app::ChatApp;
|
||||
|
||||
@@ -38,11 +38,10 @@ pub fn restore() -> io::Result<()> {
|
||||
}
|
||||
|
||||
/// Draw the UI.
|
||||
pub fn draw<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>)
|
||||
pub fn draw<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>)
|
||||
where
|
||||
I: IdentityProvider + Send + 'static,
|
||||
D: Transport + Send + 'static,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send + 'static,
|
||||
{
|
||||
let chunks = Layout::default()
|
||||
@@ -61,11 +60,10 @@ where
|
||||
draw_status(frame, app, chunks[3]);
|
||||
}
|
||||
|
||||
fn draw_header<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
|
||||
fn draw_header<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
|
||||
where
|
||||
I: IdentityProvider + Send + 'static,
|
||||
D: Transport + Send + 'static,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send + 'static,
|
||||
{
|
||||
let title = match app.current_session() {
|
||||
@@ -90,11 +88,10 @@ where
|
||||
frame.render_widget(header, area);
|
||||
}
|
||||
|
||||
fn draw_messages<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
|
||||
fn draw_messages<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
|
||||
where
|
||||
I: IdentityProvider + Send + 'static,
|
||||
D: Transport + Send + 'static,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send + 'static,
|
||||
{
|
||||
let remote_name = app
|
||||
@@ -182,11 +179,10 @@ where
|
||||
frame.render_stateful_widget(messages_widget, area, &mut list_state);
|
||||
}
|
||||
|
||||
fn draw_input<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
|
||||
fn draw_input<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
|
||||
where
|
||||
I: IdentityProvider + Send + 'static,
|
||||
D: Transport + Send + 'static,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send + 'static,
|
||||
{
|
||||
// Inner width: area minus borders (2).
|
||||
@@ -215,11 +211,10 @@ where
|
||||
frame.set_cursor_position((cursor_x, area.y + 1));
|
||||
}
|
||||
|
||||
fn draw_status<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
|
||||
fn draw_status<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
|
||||
where
|
||||
I: IdentityProvider + Send + 'static,
|
||||
D: Transport + Send + 'static,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send + 'static,
|
||||
{
|
||||
let status = Paragraph::new(app.status.as_str())
|
||||
@@ -231,11 +226,10 @@ where
|
||||
}
|
||||
|
||||
/// Handle keyboard events.
|
||||
pub fn handle_events<I, D, R, S>(app: &mut ChatApp<I, D, R, S>) -> io::Result<bool>
|
||||
pub fn handle_events<D, R, S>(app: &mut ChatApp<D, R, S>) -> io::Result<bool>
|
||||
where
|
||||
I: IdentityProvider + Send + 'static,
|
||||
D: Transport + Send + 'static,
|
||||
R: RegistrationService + Send + 'static,
|
||||
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
|
||||
S: ChatStore + Send + 'static,
|
||||
{
|
||||
// Poll for events with a short timeout to allow checking incoming messages
|
||||
|
||||
Reference in New Issue
Block a user