diff --git a/warzone/Cargo.lock b/warzone/Cargo.lock index af3744a..75d31aa 100644 --- a/warzone/Cargo.lock +++ b/warzone/Cargo.lock @@ -2956,7 +2956,7 @@ dependencies = [ [[package]] name = "warzone-client" -version = "0.0.34" +version = "0.0.35" dependencies = [ "anyhow", "argon2", @@ -2989,7 +2989,7 @@ dependencies = [ [[package]] name = "warzone-mule" -version = "0.0.34" +version = "0.0.35" dependencies = [ "anyhow", "clap", @@ -2998,7 +2998,7 @@ dependencies = [ [[package]] name = "warzone-protocol" -version = "0.0.34" +version = "0.0.35" dependencies = [ "base64", "bincode", @@ -3023,7 +3023,7 @@ dependencies = [ [[package]] name = "warzone-server" -version = "0.0.34" +version = "0.0.35" dependencies = [ "anyhow", "axum", @@ -3053,7 +3053,7 @@ dependencies = [ [[package]] name = "warzone-wasm" -version = "0.0.34" +version = "0.0.35" dependencies = [ "base64", "bincode", diff --git a/warzone/Cargo.toml b/warzone/Cargo.toml index c448f78..ef6f039 100644 --- a/warzone/Cargo.toml +++ b/warzone/Cargo.toml @@ -9,7 +9,7 @@ members = [ ] [workspace.package] -version = "0.0.34" +version = "0.0.35" edition = "2021" license = "MIT" rust-version = "1.75" diff --git a/warzone/crates/warzone-protocol/Cargo.toml b/warzone/crates/warzone-protocol/Cargo.toml index 433f9cd..9ebbc55 100644 --- a/warzone/crates/warzone-protocol/Cargo.toml +++ b/warzone/crates/warzone-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "warzone-protocol" -version = "0.0.34" +version = "0.0.35" edition = "2021" license = "MIT" description = "Core crypto & wire protocol for featherChat (Warzone messenger)" diff --git a/warzone/crates/warzone-server/src/routes/web.rs b/warzone/crates/warzone-server/src/routes/web.rs index 27b3210..d2bb8c8 100644 --- a/warzone/crates/warzone-server/src/routes/web.rs +++ b/warzone/crates/warzone-server/src/routes/web.rs @@ -50,7 +50,7 @@ async fn pwa_manifest() -> impl IntoResponse { async fn service_worker() -> impl IntoResponse { ([(header::CONTENT_TYPE, "application/javascript")], r##" -const CACHE = 'wz-v16'; +const CACHE = 'wz-v17'; const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json']; self.addEventListener('install', e => { @@ -173,6 +173,8 @@ const WEB_HTML: &str = r##" .msg .ts { color: #333; margin-right: 4px; } .msg .from-self { color: #4ade80; font-weight: bold; } .msg .from-sys { color: #5e9ca0; font-style: italic; } + .identity-code { user-select: all; cursor: pointer; background: #1a1a3e; padding: 2px 6px; border-radius: 3px; color: #4ade80; font-family: monospace; } + .identity-code:hover { background: #252550; } .msg .lock { color: #ff6b9d; } #bottom { display: flex; padding: 6px; gap: 6px; border-top: 1px solid #222; background: #111; @@ -244,7 +246,9 @@ const $messages = document.getElementById('messages'); const $input = document.getElementById('msg-input'); const $peerInput = document.getElementById('peer-input'); -// ── State ── +// ═══════════════════════════════════════════════ +// SECTION: State & Config +// ═══════════════════════════════════════════════ let wasmIdentity = null; // WasmIdentity from WASM let myFingerprint = ''; let myEthAddress = ''; @@ -255,7 +259,7 @@ let pollTimer = null; let ws = null; // WebSocket connection let wasmReady = false; -const VERSION = '0.0.34'; +const VERSION = '0.0.35'; let DEBUG = true; // toggle with /debug command // ── Receipt tracking ── @@ -343,7 +347,9 @@ function handleAddrClick(addr) { } } -// ── WASM-based crypto (same as CLI: X25519 + ChaCha20 + Double Ratchet) ── +// ═══════════════════════════════════════════════ +// SECTION: Crypto & Identity +// ═══════════════════════════════════════════════ async function initWasm() { await init('/wasm/warzone_wasm_bg.wasm'); @@ -488,6 +494,10 @@ async function sendEncrypted(peerFP, plaintext) { return msgId; } +// ═══════════════════════════════════════════════ +// SECTION: Network & WebSocket +// ═══════════════════════════════════════════════ + // URL deep links: /message/@alias, /message/0xABC, /group/#ops function handleDeepLink() { const path = window.location.pathname; @@ -739,7 +749,9 @@ try { } } catch(e) {} -// ── UI ── +// ═══════════════════════════════════════════════ +// SECTION: UI & Message Display +// ═══════════════════════════════════════════════ function ts() { return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); @@ -871,10 +883,14 @@ function addMsg(from, text, isSelf, messageId, rawHtml) { } } -function addSys(text) { +function addSys(text, rawHtml) { const d = document.createElement('div'); d.className = 'msg'; - d.innerHTML = '' + ts() + ' ' + esc(text) + ''; + if (rawHtml) { + d.innerHTML = '' + ts() + ' ' + text + ''; + } else { + d.innerHTML = '' + ts() + ' ' + esc(text) + ''; + } $messages.appendChild(d); $messages.scrollTop = $messages.scrollHeight; } @@ -922,7 +938,7 @@ async function enterChat() { hdrFp.textContent = (myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19)); hdrFp.title = myFingerprint; } - addSys('Identity: ' + (myEthAddress || myFingerprint)); + addSys('Identity: ' + esc(myEthAddress || myFingerprint) + '', true); addSys('Key registered with server'); addSys('v' + VERSION + ' | DM: paste peer fingerprint or @alias above'); @@ -1074,7 +1090,9 @@ async function sendToGroup(groupName, text) { addMsg((myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19)) + ' [' + groupName + ']', text, true, null); } -// ── Send handler ── +// ═══════════════════════════════════════════════ +// SECTION: Command Handlers +// ═══════════════════════════════════════════════ async function doSend() { const text = $input.value.trim(); diff --git a/warzone/crates/warzone-wasm/src/lib.rs b/warzone/crates/warzone-wasm/src/lib.rs index 0c84ae4..396c07a 100644 --- a/warzone/crates/warzone-wasm/src/lib.rs +++ b/warzone/crates/warzone-wasm/src/lib.rs @@ -627,6 +627,46 @@ pub fn create_sender_key_from_distribution( Ok(hex::encode(encoded)) } +/// Create a CallSignal WireMessage for sending via WebSocket. +/// +/// Arguments: +/// - identity: the WasmIdentity of the sender +/// - signal_type: "offer" | "answer" | "ice_candidate" | "hangup" | "reject" | "ringing" | "busy" +/// - payload: SDP offer/answer, ICE candidate JSON, or empty string +/// - target: recipient fingerprint or group name +/// +/// Returns: bincode-serialized WireMessage bytes +#[wasm_bindgen] +pub fn create_call_signal( + identity: &WasmIdentity, + signal_type: &str, + payload: &str, + target: &str, +) -> Result, JsValue> { + use warzone_protocol::message::{CallSignalType, WireMessage}; + + let st = match signal_type.to_lowercase().as_str() { + "offer" => CallSignalType::Offer, + "answer" => CallSignalType::Answer, + "ice_candidate" | "icecandidate" => CallSignalType::IceCandidate, + "hangup" => CallSignalType::Hangup, + "reject" => CallSignalType::Reject, + "ringing" => CallSignalType::Ringing, + "busy" => CallSignalType::Busy, + _ => return Err(JsValue::from_str(&format!("unknown signal type: {}", signal_type))), + }; + + let wire = WireMessage::CallSignal { + id: uuid::Uuid::new_v4().to_string(), + sender_fingerprint: identity.pub_id.fingerprint.to_string(), + signal_type: st, + payload: payload.to_string(), + target: target.to_string(), + }; + + bincode::serialize(&wire).map_err(|e| JsValue::from_str(&format!("serialize: {}", e))) +} + // Tests live in warzone-protocol to avoid js-sys dependency issues. // See warzone-protocol/src/x3dh.rs tests for web-client simulation.