v0.0.35: WASM create_call_signal, selectable identity, web sections
FC-P3-T2: WASM create_call_signal() export - Accepts signal_type string (offer/answer/hangup/etc), payload, target - Returns bincode WireMessage::CallSignal bytes for WS send FC-P3-T9: Selectable identity display in web - ETH address shown in code-style block, click to copy - addSys() gains rawHtml parameter for rich content FC-P3-T5: Section navigation comments in web.rs - 5 section markers: State, Crypto, Network, UI, 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.34"
|
version = "0.0.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2989,7 +2989,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-mule"
|
name = "warzone-mule"
|
||||||
version = "0.0.34"
|
version = "0.0.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2998,7 +2998,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.34"
|
version = "0.0.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -3023,7 +3023,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-server"
|
name = "warzone-server"
|
||||||
version = "0.0.34"
|
version = "0.0.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -3053,7 +3053,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-wasm"
|
name = "warzone-wasm"
|
||||||
version = "0.0.34"
|
version = "0.0.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.34"
|
version = "0.0.35"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.34"
|
version = "0.0.35"
|
||||||
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-v16';
|
const CACHE = 'wz-v17';
|
||||||
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 => {
|
||||||
@@ -173,6 +173,8 @@ const WEB_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
.msg .ts { color: #333; margin-right: 4px; }
|
.msg .ts { color: #333; margin-right: 4px; }
|
||||||
.msg .from-self { color: #4ade80; font-weight: bold; }
|
.msg .from-self { color: #4ade80; font-weight: bold; }
|
||||||
.msg .from-sys { color: #5e9ca0; font-style: italic; }
|
.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; }
|
.msg .lock { color: #ff6b9d; }
|
||||||
|
|
||||||
#bottom { display: flex; padding: 6px; gap: 6px; border-top: 1px solid #222; background: #111;
|
#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 $input = document.getElementById('msg-input');
|
||||||
const $peerInput = document.getElementById('peer-input');
|
const $peerInput = document.getElementById('peer-input');
|
||||||
|
|
||||||
// ── State ──
|
// ═══════════════════════════════════════════════
|
||||||
|
// SECTION: State & Config
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
let wasmIdentity = null; // WasmIdentity from WASM
|
let wasmIdentity = null; // WasmIdentity from WASM
|
||||||
let myFingerprint = '';
|
let myFingerprint = '';
|
||||||
let myEthAddress = '';
|
let myEthAddress = '';
|
||||||
@@ -255,7 +259,7 @@ let pollTimer = null;
|
|||||||
let ws = null; // WebSocket connection
|
let ws = null; // WebSocket connection
|
||||||
let wasmReady = false;
|
let wasmReady = false;
|
||||||
|
|
||||||
const VERSION = '0.0.34';
|
const VERSION = '0.0.35';
|
||||||
let DEBUG = true; // toggle with /debug command
|
let DEBUG = true; // toggle with /debug command
|
||||||
|
|
||||||
// ── Receipt tracking ──
|
// ── 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() {
|
async function initWasm() {
|
||||||
await init('/wasm/warzone_wasm_bg.wasm');
|
await init('/wasm/warzone_wasm_bg.wasm');
|
||||||
@@ -488,6 +494,10 @@ async function sendEncrypted(peerFP, plaintext) {
|
|||||||
return msgId;
|
return msgId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
// SECTION: Network & WebSocket
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
// URL deep links: /message/@alias, /message/0xABC, /group/#ops
|
// URL deep links: /message/@alias, /message/0xABC, /group/#ops
|
||||||
function handleDeepLink() {
|
function handleDeepLink() {
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
@@ -739,7 +749,9 @@ try {
|
|||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// ── UI ──
|
// ═══════════════════════════════════════════════
|
||||||
|
// SECTION: UI & Message Display
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
function ts() {
|
function ts() {
|
||||||
return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
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');
|
const d = document.createElement('div');
|
||||||
d.className = 'msg';
|
d.className = 'msg';
|
||||||
d.innerHTML = '<span class="ts">' + ts() + '</span> <span class="from-sys">' + esc(text) + '</span>';
|
if (rawHtml) {
|
||||||
|
d.innerHTML = '<span class="ts">' + ts() + '</span> <span class="from-sys">' + text + '</span>';
|
||||||
|
} else {
|
||||||
|
d.innerHTML = '<span class="ts">' + ts() + '</span> <span class="from-sys">' + esc(text) + '</span>';
|
||||||
|
}
|
||||||
$messages.appendChild(d);
|
$messages.appendChild(d);
|
||||||
$messages.scrollTop = $messages.scrollHeight;
|
$messages.scrollTop = $messages.scrollHeight;
|
||||||
}
|
}
|
||||||
@@ -922,7 +938,7 @@ async function enterChat() {
|
|||||||
hdrFp.textContent = (myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19));
|
hdrFp.textContent = (myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19));
|
||||||
hdrFp.title = myFingerprint;
|
hdrFp.title = myFingerprint;
|
||||||
}
|
}
|
||||||
addSys('Identity: ' + (myEthAddress || myFingerprint));
|
addSys('Identity: <code class="identity-code" onclick="navigator.clipboard.writeText(this.textContent)">' + esc(myEthAddress || myFingerprint) + '</code>', true);
|
||||||
addSys('Key registered with server');
|
addSys('Key registered with server');
|
||||||
|
|
||||||
addSys('v' + VERSION + ' | DM: paste peer fingerprint or @alias above');
|
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);
|
addMsg((myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19)) + ' [' + groupName + ']', text, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Send handler ──
|
// ═══════════════════════════════════════════════
|
||||||
|
// SECTION: Command Handlers
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
async function doSend() {
|
async function doSend() {
|
||||||
const text = $input.value.trim();
|
const text = $input.value.trim();
|
||||||
|
|||||||
@@ -627,6 +627,46 @@ pub fn create_sender_key_from_distribution(
|
|||||||
Ok(hex::encode(encoded))
|
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<Vec<u8>, 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.
|
// Tests live in warzone-protocol to avoid js-sys dependency issues.
|
||||||
// See warzone-protocol/src/x3dh.rs tests for web-client simulation.
|
// See warzone-protocol/src/x3dh.rs tests for web-client simulation.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user