v0.0.17: fix /r reply in TUI, /p shortcut, /eth, /unalias
TUI fixes: - /r and /reply now work: tracks last_dm_peer from received messages - /r switches peer to last DM sender, then type normally - /p @alias works as shortcut for /peer @alias - /eth shows Ethereum address in TUI - /unalias removes your alias Web fixes: - /p @alias and /peer @alias resolve and set peer - /r and /reply work (switch to last DM sender) - /unalias removes alias - /admin-unalias <alias> <password> for admin removal - File download now shows as clickable link (not auto-download) Server: - POST /v1/alias/unregister — remove own alias - POST /v1/alias/admin-remove — admin removes any alias - WARZONE_ADMIN_PASSWORD env var (default: "admin") 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
@@ -2789,7 +2789,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-client"
|
name = "warzone-client"
|
||||||
version = "0.0.16"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2822,7 +2822,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-mule"
|
name = "warzone-mule"
|
||||||
version = "0.0.16"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2831,7 +2831,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.16"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -2856,7 +2856,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-server"
|
name = "warzone-server"
|
||||||
version = "0.0.16"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2883,7 +2883,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-wasm"
|
name = "warzone-wasm"
|
||||||
version = "0.0.16"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.16"
|
version = "0.0.17"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -125,6 +125,12 @@ pub fn save_seed(seed: &Seed) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load raw seed bytes (for deriving eth address etc).
|
||||||
|
pub fn load_seed_raw() -> anyhow::Result<[u8; 32]> {
|
||||||
|
let seed = load_seed()?;
|
||||||
|
Ok(seed.0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Load seed, decrypting if necessary.
|
/// Load seed, decrypting if necessary.
|
||||||
pub fn load_seed() -> anyhow::Result<Seed> {
|
pub fn load_seed() -> anyhow::Result<Seed> {
|
||||||
let path = seed_path();
|
let path = seed_path();
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ pub struct App {
|
|||||||
pub peer_fp: Option<String>,
|
pub peer_fp: Option<String>,
|
||||||
pub server_url: String,
|
pub server_url: String,
|
||||||
pub should_quit: bool,
|
pub should_quit: bool,
|
||||||
pub last_dm_peer: Option<String>,
|
pub last_dm_peer: Arc<Mutex<Option<String>>>,
|
||||||
/// Track receipt status for messages we sent, keyed by message ID.
|
/// Track receipt status for messages we sent, keyed by message ID.
|
||||||
pub receipts: Arc<Mutex<HashMap<String, ReceiptStatus>>>,
|
pub receipts: Arc<Mutex<HashMap<String, ReceiptStatus>>>,
|
||||||
/// Pending incoming file transfers, keyed by file ID.
|
/// Pending incoming file transfers, keyed by file ID.
|
||||||
@@ -112,7 +112,7 @@ impl App {
|
|||||||
peer_fp,
|
peer_fp,
|
||||||
server_url,
|
server_url,
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
last_dm_peer: None,
|
last_dm_peer: Arc::new(Mutex::new(None)),
|
||||||
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())),
|
||||||
}
|
}
|
||||||
@@ -285,9 +285,17 @@ impl App {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if text == "/eth" {
|
||||||
|
// Show ethereum address from seed
|
||||||
|
if let Ok(seed) = crate::keystore::load_seed_raw() {
|
||||||
|
let eth = warzone_protocol::ethereum::derive_eth_identity(&seed);
|
||||||
|
self.add_message(ChatLine { sender: "system".into(), text: format!("ETH: {}", eth.address.to_checksum()), is_system: true, is_self: false, message_id: None });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if text == "/r" || text == "/reply" {
|
if text == "/r" || text == "/reply" {
|
||||||
// Just switch to last DM peer
|
let last = self.last_dm_peer.lock().unwrap().clone();
|
||||||
if let Some(ref peer) = self.last_dm_peer.clone() {
|
if let Some(ref peer) = last {
|
||||||
self.peer_fp = Some(peer.clone());
|
self.peer_fp = Some(peer.clone());
|
||||||
self.add_message(ChatLine { sender: "system".into(), text: format!("→ switched to {}", &peer[..peer.len().min(16)]), is_system: true, is_self: false, message_id: None });
|
self.add_message(ChatLine { sender: "system".into(), text: format!("→ switched to {}", &peer[..peer.len().min(16)]), is_system: true, is_self: false, message_id: None });
|
||||||
} else {
|
} else {
|
||||||
@@ -1109,9 +1117,10 @@ fn process_incoming(
|
|||||||
pending_files: &Arc<Mutex<HashMap<String, PendingFileTransfer>>>,
|
pending_files: &Arc<Mutex<HashMap<String, PendingFileTransfer>>>,
|
||||||
our_fp: &str,
|
our_fp: &str,
|
||||||
client: &ServerClient,
|
client: &ServerClient,
|
||||||
|
last_dm_peer: &Arc<Mutex<Option<String>>>,
|
||||||
) {
|
) {
|
||||||
match bincode::deserialize::<WireMessage>(raw) {
|
match bincode::deserialize::<WireMessage>(raw) {
|
||||||
Ok(wire) => process_wire_message(wire, identity, db, messages, receipts, pending_files, our_fp, client),
|
Ok(wire) => process_wire_message(wire, identity, db, messages, receipts, pending_files, our_fp, client, last_dm_peer),
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1125,6 +1134,7 @@ fn process_wire_message(
|
|||||||
pending_files: &Arc<Mutex<HashMap<String, PendingFileTransfer>>>,
|
pending_files: &Arc<Mutex<HashMap<String, PendingFileTransfer>>>,
|
||||||
our_fp: &str,
|
our_fp: &str,
|
||||||
client: &ServerClient,
|
client: &ServerClient,
|
||||||
|
last_dm_peer: &Arc<Mutex<Option<String>>>,
|
||||||
) {
|
) {
|
||||||
match wire {
|
match wire {
|
||||||
WireMessage::KeyExchange {
|
WireMessage::KeyExchange {
|
||||||
@@ -1161,6 +1171,7 @@ fn process_wire_message(
|
|||||||
Ok(plaintext) => {
|
Ok(plaintext) => {
|
||||||
let text = String::from_utf8_lossy(&plaintext).to_string();
|
let text = String::from_utf8_lossy(&plaintext).to_string();
|
||||||
let _ = db.save_session(&sender_fp, &state);
|
let _ = db.save_session(&sender_fp, &state);
|
||||||
|
*last_dm_peer.lock().unwrap() = Some(sender_fingerprint.clone());
|
||||||
messages.lock().unwrap().push(ChatLine {
|
messages.lock().unwrap().push(ChatLine {
|
||||||
sender: sender_fingerprint[..sender_fingerprint.len().min(12)].to_string(),
|
sender: sender_fingerprint[..sender_fingerprint.len().min(12)].to_string(),
|
||||||
text,
|
text,
|
||||||
@@ -1168,7 +1179,6 @@ fn process_wire_message(
|
|||||||
is_self: false,
|
is_self: false,
|
||||||
message_id: None,
|
message_id: None,
|
||||||
});
|
});
|
||||||
// Send delivery receipt
|
|
||||||
send_receipt(our_fp, &sender_fingerprint, &id, ReceiptType::Delivered, client);
|
send_receipt(our_fp, &sender_fingerprint, &id, ReceiptType::Delivered, client);
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
@@ -1191,6 +1201,7 @@ fn process_wire_message(
|
|||||||
Ok(plaintext) => {
|
Ok(plaintext) => {
|
||||||
let text = String::from_utf8_lossy(&plaintext).to_string();
|
let text = String::from_utf8_lossy(&plaintext).to_string();
|
||||||
let _ = db.save_session(&sender_fp, &state);
|
let _ = db.save_session(&sender_fp, &state);
|
||||||
|
*last_dm_peer.lock().unwrap() = Some(sender_fingerprint.clone());
|
||||||
messages.lock().unwrap().push(ChatLine {
|
messages.lock().unwrap().push(ChatLine {
|
||||||
sender: sender_fingerprint[..sender_fingerprint.len().min(12)].to_string(),
|
sender: sender_fingerprint[..sender_fingerprint.len().min(12)].to_string(),
|
||||||
text,
|
text,
|
||||||
@@ -1414,6 +1425,7 @@ pub async fn poll_loop(
|
|||||||
identity: IdentityKeyPair,
|
identity: IdentityKeyPair,
|
||||||
db: Arc<LocalDb>,
|
db: Arc<LocalDb>,
|
||||||
client: ServerClient,
|
client: ServerClient,
|
||||||
|
last_dm_peer: Arc<Mutex<Option<String>>>,
|
||||||
) {
|
) {
|
||||||
let fp = normfp(&our_fp);
|
let fp = normfp(&our_fp);
|
||||||
|
|
||||||
@@ -1439,7 +1451,7 @@ pub async fn poll_loop(
|
|||||||
|
|
||||||
while let Some(Ok(msg)) = read.next().await {
|
while let Some(Ok(msg)) = read.next().await {
|
||||||
if let tokio_tungstenite::tungstenite::Message::Binary(data) = msg {
|
if let tokio_tungstenite::tungstenite::Message::Binary(data) = msg {
|
||||||
process_incoming(&data, &identity, &db, &messages, &receipts, &pending_files, &our_fp, &client);
|
process_incoming(&data, &identity, &db, &messages, &receipts, &pending_files, &our_fp, &client, &last_dm_peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1460,7 +1472,7 @@ pub async fn poll_loop(
|
|||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
for raw in &raw_msgs {
|
for raw in &raw_msgs {
|
||||||
process_incoming(raw, &identity, &db, &messages, &receipts, &pending_files, &our_fp, &client);
|
process_incoming(raw, &identity, &db, &messages, &receipts, &pending_files, &our_fp, &client, &last_dm_peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1487,12 +1499,13 @@ pub async fn run_tui(
|
|||||||
let poll_messages = app.messages.clone();
|
let poll_messages = app.messages.clone();
|
||||||
let poll_receipts = app.receipts.clone();
|
let poll_receipts = app.receipts.clone();
|
||||||
let poll_pending_files = app.pending_files.clone();
|
let poll_pending_files = app.pending_files.clone();
|
||||||
|
let poll_last_dm = app.last_dm_peer.clone();
|
||||||
let poll_client = client.clone();
|
let poll_client = client.clone();
|
||||||
let poll_db = db.clone();
|
let poll_db = db.clone();
|
||||||
let poll_fp = our_fp.clone();
|
let poll_fp = our_fp.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
poll_loop(poll_messages, poll_receipts, poll_pending_files, poll_fp, poll_identity, poll_db, poll_client).await;
|
poll_loop(poll_messages, poll_receipts, poll_pending_files, poll_fp, poll_identity, poll_db, poll_client, poll_last_dm).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ let pollTimer = null;
|
|||||||
let ws = null; // WebSocket connection
|
let ws = null; // WebSocket connection
|
||||||
let wasmReady = false;
|
let wasmReady = false;
|
||||||
|
|
||||||
const VERSION = '0.0.16';
|
const VERSION = '0.0.17';
|
||||||
let DEBUG = true; // toggle with /debug command
|
let DEBUG = true; // toggle with /debug command
|
||||||
|
|
||||||
// ── Receipt tracking ──
|
// ── Receipt tracking ──
|
||||||
|
|||||||
Reference in New Issue
Block a user