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.