Aliases: human-readable names mapped to fingerprints
Server: - POST /v1/alias/register — claim an alias (one per fingerprint) - GET /v1/alias/resolve/:name — alias → fingerprint - GET /v1/alias/whois/:fingerprint — fingerprint → alias (reverse) - GET /v1/alias/list — list all aliases - Bidirectional mapping in sled (a:name→fp, fp:fp→name) - One alias per person, re-registering replaces old alias Web client: - /alias <name> — register your alias - /aliases — list all registered aliases - /info — now shows alias alongside fingerprint - Peer input accepts @alias (resolved before sending) - Received messages show @alias instead of fingerprint - DM: paste @alias or fingerprint in peer input CLI TUI: - /alias <name> — register alias - /aliases — list all aliases - /peer @alias — resolves alias to fingerprint - Alias resolution displayed in system messages Addressing model: - @manwe (local) → server resolves → fingerprint - @manwe.b1.example.com (federated) → DNS resolve (Phase 3) - Raw fingerprint → always works, no resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -54,7 +54,7 @@ impl App {
|
||||
} else {
|
||||
messages.lock().unwrap().push(ChatLine {
|
||||
sender: "system".into(),
|
||||
text: "No peer set. Use /peer <fingerprint> to start chatting".into(),
|
||||
text: "No peer set. Use /peer <fp>, /peer @alias, or /g <group>".into(),
|
||||
is_system: true,
|
||||
is_self: false,
|
||||
});
|
||||
@@ -62,7 +62,7 @@ impl App {
|
||||
|
||||
messages.lock().unwrap().push(ChatLine {
|
||||
sender: "system".into(),
|
||||
text: "Commands: /peer <fp>, /info, /quit".into(),
|
||||
text: "Commands: /alias <name>, /peer <fp|@alias>, /g <group>, /info, /quit".into(),
|
||||
is_system: true,
|
||||
is_self: false,
|
||||
});
|
||||
@@ -181,8 +181,25 @@ impl App {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if text.starts_with("/alias ") {
|
||||
let name = text[7..].trim();
|
||||
self.register_alias(name, client).await;
|
||||
return;
|
||||
}
|
||||
if text == "/aliases" {
|
||||
self.list_aliases(client).await;
|
||||
return;
|
||||
}
|
||||
if text.starts_with("/peer ") {
|
||||
let fp = text[6..].trim().to_string();
|
||||
let raw = text[6..].trim().to_string();
|
||||
let fp = if raw.starts_with('@') {
|
||||
match self.resolve_alias(&raw[1..], client).await {
|
||||
Some(resolved) => resolved,
|
||||
None => return,
|
||||
}
|
||||
} else {
|
||||
raw
|
||||
};
|
||||
self.add_message(ChatLine {
|
||||
sender: "system".into(),
|
||||
text: format!("Peer set to {}", fp),
|
||||
@@ -544,6 +561,70 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn register_alias(&self, name: &str, client: &ServerClient) {
|
||||
let url = format!("{}/v1/alias/register", client.base_url);
|
||||
match client.client.post(&url)
|
||||
.json(&serde_json::json!({"alias": name, "fingerprint": normfp(&self.our_fp)}))
|
||||
.send().await
|
||||
{
|
||||
Ok(resp) => {
|
||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(err) = data.get("error") {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", err), is_system: true, is_self: false });
|
||||
} else {
|
||||
let alias = data.get("alias").and_then(|v| v.as_str()).unwrap_or(name);
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Alias @{} registered", alias), is_system: true, is_self: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", e), is_system: true, is_self: false }),
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_alias(&self, name: &str, client: &ServerClient) -> Option<String> {
|
||||
let url = format!("{}/v1/alias/resolve/{}", client.base_url, name);
|
||||
match client.client.get(&url).send().await {
|
||||
Ok(resp) => {
|
||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(fp) = data.get("fingerprint").and_then(|v| v.as_str()) {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("@{} → {}", name, fp), is_system: true, is_self: false });
|
||||
return Some(fp.to_string());
|
||||
}
|
||||
if let Some(err) = data.get("error") {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Unknown alias @{}: {}", name, err), is_system: true, is_self: false });
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", e), is_system: true, is_self: false });
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_aliases(&self, client: &ServerClient) {
|
||||
let url = format!("{}/v1/alias/list", client.base_url);
|
||||
match client.client.get(&url).send().await {
|
||||
Ok(resp) => {
|
||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(aliases) = data.get("aliases").and_then(|v| v.as_array()) {
|
||||
if aliases.is_empty() {
|
||||
self.add_message(ChatLine { sender: "system".into(), text: "No aliases".into(), is_system: true, is_self: false });
|
||||
} else {
|
||||
for a in aliases {
|
||||
let name = a.get("alias").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
let fp = a.get("fingerprint").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
self.add_message(ChatLine { sender: "system".into(), text: format!(" @{} → {}...", name, &fp[..fp.len().min(16)]), is_system: true, is_self: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", e), is_system: true, is_self: false }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normfp(fp: &str) -> String {
|
||||
|
||||
Reference in New Issue
Block a user