v0.0.24: ETH display in TUI header/messages, web peer resolve, click-focus
TUI: - Header shows peer ETH address (resolved on /peer set) - Own messages show ETH format - Resolve display shows full formatted fingerprint (xxxx:xxxx:...) - peer_eth field stored on App for header display Web: - Pasting 0x address in peer input box now resolves via /v1/resolve/ - Send path resolves 0x/@ before encrypting - Click messages area → focuses text input - Own messages show ETH format Version: 0.0.23 → 0.0.24, SW cache wz-v4 → wz-v5 Build script: --local, --local-ship, --local-clean commands Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
warzone/Cargo.lock
generated
10
warzone/Cargo.lock
generated
@@ -2956,7 +2956,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-client"
|
name = "warzone-client"
|
||||||
version = "0.0.22"
|
version = "0.0.24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2989,7 +2989,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-mule"
|
name = "warzone-mule"
|
||||||
version = "0.0.22"
|
version = "0.0.24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2998,7 +2998,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.22"
|
version = "0.0.24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -3023,7 +3023,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-server"
|
name = "warzone-server"
|
||||||
version = "0.0.22"
|
version = "0.0.24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -3053,7 +3053,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-wasm"
|
name = "warzone-wasm"
|
||||||
version = "0.0.22"
|
version = "0.0.24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.23"
|
version = "0.0.24"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -371,6 +371,8 @@ impl App {
|
|||||||
if text.starts_with("/peer ") || text.starts_with("/p ") {
|
if text.starts_with("/peer ") || text.starts_with("/p ") {
|
||||||
let text = if text.starts_with("/p ") { format!("/peer {}", &text[3..]) } else { text.clone() };
|
let text = if text.starts_with("/p ") { format!("/peer {}", &text[3..]) } else { text.clone() };
|
||||||
let raw = text[6..].trim().to_string();
|
let raw = text[6..].trim().to_string();
|
||||||
|
let is_eth_input = raw.starts_with("0x") || raw.starts_with("0X");
|
||||||
|
let eth_input = if is_eth_input { Some(raw.clone()) } else { None };
|
||||||
let fp = if raw.starts_with('@') {
|
let fp = if raw.starts_with('@') {
|
||||||
match self.resolve_alias(&raw[1..], client).await {
|
match self.resolve_alias(&raw[1..], client).await {
|
||||||
Some(resolved) => resolved,
|
Some(resolved) => resolved,
|
||||||
@@ -389,9 +391,22 @@ impl App {
|
|||||||
self.add_message(ChatLine { sender: "system".into(), text: "Cannot set yourself as peer".into(), is_system: true, is_self: false, message_id: None, timestamp: Local::now() });
|
self.add_message(ChatLine { sender: "system".into(), text: "Cannot set yourself as peer".into(), is_system: true, is_self: false, message_id: None, timestamp: Local::now() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Resolve peer ETH for display
|
||||||
|
if is_eth_input {
|
||||||
|
self.peer_eth = eth_input;
|
||||||
|
} else {
|
||||||
|
// Try to look up ETH for this fingerprint
|
||||||
|
let resolve_url = format!("{}/v1/resolve/{}", client.base_url, normfp(&fp));
|
||||||
|
if let Ok(resp) = client.client.get(&resolve_url).send().await {
|
||||||
|
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||||
|
self.peer_eth = data.get("eth_address").and_then(|v| v.as_str()).map(String::from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let display = self.peer_eth.as_deref().unwrap_or(&fp);
|
||||||
self.add_message(ChatLine {
|
self.add_message(ChatLine {
|
||||||
sender: "system".into(),
|
sender: "system".into(),
|
||||||
text: format!("Peer set to {}", fp),
|
text: format!("Peer set to {}", display),
|
||||||
is_system: true,
|
is_system: true,
|
||||||
is_self: false,
|
is_self: false,
|
||||||
message_id: None, timestamp: Local::now(),
|
message_id: None, timestamp: Local::now(),
|
||||||
@@ -938,7 +953,11 @@ impl App {
|
|||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||||
if let Some(fp) = data.get("fingerprint").and_then(|v| v.as_str()) {
|
if let Some(fp) = data.get("fingerprint").and_then(|v| v.as_str()) {
|
||||||
self.add_message(ChatLine { sender: "system".into(), text: format!("{} → {}", addr, &fp[..fp.len().min(16)]), is_system: true, is_self: false, message_id: None, timestamp: Local::now() });
|
// Format fingerprint with colons: xxxx:xxxx:xxxx:...
|
||||||
|
let formatted: String = fp.chars().enumerate()
|
||||||
|
.flat_map(|(i, c)| if i > 0 && i % 4 == 0 { vec![':', c] } else { vec![c] })
|
||||||
|
.collect();
|
||||||
|
self.add_message(ChatLine { sender: "system".into(), text: format!("{} → {}", addr, formatted), is_system: true, is_self: false, message_id: None, timestamp: Local::now() });
|
||||||
return Some(fp.to_string());
|
return Some(fp.to_string());
|
||||||
}
|
}
|
||||||
if let Some(err) = data.get("error") {
|
if let Some(err) = data.get("error") {
|
||||||
|
|||||||
@@ -48,10 +48,12 @@ impl App {
|
|||||||
.split(frame.area());
|
.split(frame.area());
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
let peer_str = self
|
let peer_str = match (&self.peer_eth, &self.peer_fp) {
|
||||||
.peer_fp
|
(Some(eth), _) => format!("{}...", ð[..eth.len().min(12)]),
|
||||||
.as_deref()
|
(None, Some(fp)) => fp.clone(),
|
||||||
.unwrap_or("no peer");
|
(None, None) => "no peer".to_string(),
|
||||||
|
};
|
||||||
|
let peer_str = peer_str.as_str();
|
||||||
let is_connected = self.connected.load(Ordering::Relaxed);
|
let is_connected = self.connected.load(Ordering::Relaxed);
|
||||||
let (conn_indicator, conn_color) = if is_connected {
|
let (conn_indicator, conn_color) = if is_connected {
|
||||||
(" \u{25CF}", Color::Green) // ●
|
(" \u{25CF}", Color::Green) // ●
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ pub struct App {
|
|||||||
pub pending_files: Arc<Mutex<HashMap<String, PendingFileTransfer>>>,
|
pub pending_files: Arc<Mutex<HashMap<String, PendingFileTransfer>>>,
|
||||||
/// Our ETH address (derived from seed).
|
/// Our ETH address (derived from seed).
|
||||||
pub our_eth: String,
|
pub our_eth: String,
|
||||||
|
/// Current peer's ETH address (resolved on /peer set).
|
||||||
|
pub peer_eth: Option<String>,
|
||||||
/// Scroll offset from bottom (0 = pinned to newest).
|
/// Scroll offset from bottom (0 = pinned to newest).
|
||||||
pub scroll_offset: usize,
|
pub scroll_offset: usize,
|
||||||
/// Whether the WebSocket connection is active.
|
/// Whether the WebSocket connection is active.
|
||||||
@@ -123,6 +125,7 @@ impl App {
|
|||||||
receipts: Arc::new(Mutex::new(HashMap::new())),
|
receipts: Arc::new(Mutex::new(HashMap::new())),
|
||||||
pending_files: Arc::new(Mutex::new(HashMap::new())),
|
pending_files: Arc::new(Mutex::new(HashMap::new())),
|
||||||
our_eth,
|
our_eth,
|
||||||
|
peer_eth: None,
|
||||||
scroll_offset: 0,
|
scroll_offset: 0,
|
||||||
connected: Arc::new(AtomicBool::new(false)),
|
connected: Arc::new(AtomicBool::new(false)),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.23"
|
version = "0.0.24"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"
|
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async fn pwa_manifest() -> impl IntoResponse {
|
|||||||
|
|
||||||
async fn service_worker() -> impl IntoResponse {
|
async fn service_worker() -> impl IntoResponse {
|
||||||
([(header::CONTENT_TYPE, "application/javascript")], r##"
|
([(header::CONTENT_TYPE, "application/javascript")], r##"
|
||||||
const CACHE = 'wz-v4';
|
const CACHE = 'wz-v5';
|
||||||
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
|
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
|
||||||
|
|
||||||
self.addEventListener('install', e => {
|
self.addEventListener('install', e => {
|
||||||
@@ -242,7 +242,7 @@ let pollTimer = null;
|
|||||||
let ws = null; // WebSocket connection
|
let ws = null; // WebSocket connection
|
||||||
let wasmReady = false;
|
let wasmReady = false;
|
||||||
|
|
||||||
const VERSION = '0.0.23';
|
const VERSION = '0.0.24';
|
||||||
let DEBUG = true; // toggle with /debug command
|
let DEBUG = true; // toggle with /debug command
|
||||||
|
|
||||||
// ── Receipt tracking ──
|
// ── Receipt tracking ──
|
||||||
@@ -1193,13 +1193,12 @@ async function doSend() {
|
|||||||
let peer = $peerInput.value.trim();
|
let peer = $peerInput.value.trim();
|
||||||
if (!peer || peer.startsWith('#')) { addSys('Set a peer fingerprint/@alias or use /g <group>'); return; }
|
if (!peer || peer.startsWith('#')) { addSys('Set a peer fingerprint/@alias or use /g <group>'); return; }
|
||||||
|
|
||||||
if (peer.startsWith('@')) {
|
if (peer.startsWith('@') || peer.startsWith('0x') || peer.startsWith('0X')) {
|
||||||
const aliasName = peer.slice(1);
|
const endpoint = peer.startsWith('@') ? '/v1/alias/resolve/' + peer.slice(1) : '/v1/resolve/' + peer;
|
||||||
const resp = await fetch(SERVER + '/v1/alias/resolve/' + aliasName);
|
const resp = await fetch(SERVER + endpoint);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.error) { addSys('Unknown alias @' + aliasName); return; }
|
if (data.error) { addSys('Cannot resolve ' + peer + ': ' + data.error); return; }
|
||||||
peer = data.fingerprint;
|
peer = data.fingerprint;
|
||||||
addSys('Resolved @' + aliasName + ' → ' + peer.slice(0,16) + '...');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem('wz-peer', $peerInput.value.trim());
|
localStorage.setItem('wz-peer', $peerInput.value.trim());
|
||||||
|
|||||||
Reference in New Issue
Block a user