v0.0.23: ETH display everywhere, local build, web UX fixes

Version: 0.0.22 → 0.0.23, SW cache wz-v3 → wz-v4

TUI:
- Own messages show ETH address (0x...) instead of fingerprint
- Received messages: async ETH cache lookup (resolve on first sight)
- /info shows Identity + Fingerprint
- Welcome message shows ETH address

Web:
- Header shows only ETH address (single element, click to copy)
- Own messages show ETH format
- Received messages resolve sender ETH via /v1/resolve/
- /peer 0x... resolves via /v1/resolve/ endpoint
- Click messages area → focuses text input

Client:
- register_bundle sends eth_address to server
- ETH↔fingerprint mapping stored on registration

Build:
- --local: build on current machine (auto-detect apt/dnf/pacman/brew)
- --local-ship: build locally + deploy to all servers
- --local-clean: build + clean cargo cache

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-29 08:50:31 +04:00
parent 2aa58a4319
commit ea04405199
9 changed files with 226 additions and 55 deletions

View File

@@ -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-v3';
const CACHE = 'wz-v4';
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
self.addEventListener('install', e => {
@@ -209,8 +209,8 @@ const WEB_HTML: &str = r##"<!DOCTYPE html>
<!-- Chat screen -->
<div id="chat" class="screen">
<div id="chat-header">
<span class="tag tag-fp" id="hdr-fp"></span>
<span class="tag" id="hdr-eth" style="background:#1a1a3e;color:#4fc3f7;font-size:0.8em;cursor:pointer" title=""></span>
<span class="tag tag-fp" id="hdr-fp" style="cursor:pointer" title="Click to copy"></span>
<span id="hdr-eth" style="display:none"></span>
<span>→</span>
<input id="peer-input" placeholder="Paste peer fingerprint..." autocomplete="off">
<span class="tag-server" id="hdr-server"></span>
@@ -242,7 +242,7 @@ let pollTimer = null;
let ws = null; // WebSocket connection
let wasmReady = false;
const VERSION = '0.0.22';
const VERSION = '0.0.23';
let DEBUG = true; // toggle with /debug command
// ── Receipt tracking ──
@@ -616,13 +616,16 @@ async function handleIncomingMessage(bytes) {
let fromLabel = result.sender.slice(0, 19);
try {
const ar = await fetch(SERVER + '/v1/alias/whois/' + senderFP);
const ar = await fetch(SERVER + '/v1/resolve/' + senderFP);
const ad = await ar.json();
if (ad.alias) fromLabel = '@' + ad.alias;
if (ad.eth_address) fromLabel = ad.eth_address.slice(0, 12) + '...';
// Alias overrides ETH
const aw = await fetch(SERVER + '/v1/alias/whois/' + senderFP);
const adata = await aw.json();
if (adata.alias) fromLabel = '@' + adata.alias;
} catch(e) {}
addMsg(fromLabel, result.text, false);
// Send delivery receipt
if (result.message_id) sendReceipt(result.sender, result.message_id, 'delivered');
lastDmPeer = normFP(result.sender);
return;
@@ -653,13 +656,16 @@ async function handleIncomingMessage(bytes) {
let fromLabel = result.sender.slice(0, 19);
try {
const ar = await fetch(SERVER + '/v1/alias/whois/' + normFP(result.sender));
const rfp = normFP(result.sender);
const ar = await fetch(SERVER + '/v1/resolve/' + rfp);
const ad = await ar.json();
if (ad.alias) fromLabel = '@' + ad.alias;
if (ad.eth_address) fromLabel = ad.eth_address.slice(0, 12) + '...';
const aw = await fetch(SERVER + '/v1/alias/whois/' + rfp);
const adata = await aw.json();
if (adata.alias) fromLabel = '@' + adata.alias;
} catch(e2) {}
addMsg(fromLabel, result.text, false);
// Send delivery receipt
if (result.message_id) sendReceipt(result.sender, result.message_id, 'delivered');
lastDmPeer = normFP(result.sender);
return;
@@ -859,18 +865,21 @@ let pendingFiles = {}; // file_id -> { filename, chunks: [], total, received,
async function enterChat() {
document.getElementById('setup').classList.remove('active');
document.getElementById('chat').classList.add('active');
document.getElementById('hdr-fp').textContent = myFingerprint.slice(0, 19);
document.getElementById('hdr-server').textContent = SERVER;
await registerKey();
addSys('Identity: ' + myEthAddress);
addSys('Fingerprint: ' + myFingerprint);
addSys('Key registered with server');
// Show ETH in header, fallback to fingerprint
const hdrFp = document.getElementById('hdr-fp');
if (myEthAddress) {
document.getElementById('hdr-eth').textContent = myEthAddress.slice(0, 10) + '...';
document.getElementById('hdr-eth').title = myEthAddress;
hdrFp.textContent = myEthAddress.slice(0, 12) + '...';
hdrFp.title = myEthAddress;
hdrFp.onclick = function() { navigator.clipboard.writeText(myEthAddress); addSys('Copied: ' + myEthAddress); };
} else {
hdrFp.textContent = (myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19));
hdrFp.title = myFingerprint;
}
addSys('Identity: ' + (myEthAddress || myFingerprint));
addSys('Key registered with server');
addSys('v' + VERSION + ' | DM: paste peer fingerprint or @alias above');
addSys('/alias · /g · /gleave · /gkick · /gmembers · /glist · /friend · /file · /info');
@@ -986,7 +995,7 @@ async function sendToGroup(groupName, text) {
body: JSON.stringify({ from: myFP, messages })
});
addMsg(myFingerprint.slice(0, 19) + ' [' + groupName + ']', text, true, null);
addMsg((myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19)) + ' [' + groupName + ']', text, true, null);
}
// ── Send handler ──
@@ -1092,18 +1101,19 @@ async function doSend() {
$peerInput.value = lastDmPeer;
try {
await sendEncrypted(lastDmPeer, replyText.trim());
addMsg(myFingerprint.slice(0, 19), replyText.trim(), true);
addMsg((myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19)), replyText.trim(), true);
} catch(e) { addSys('Reply failed: ' + e.message); }
return;
}
if (text.startsWith('/p ') || text.startsWith('/peer ')) {
let val = text.startsWith('/p ') ? text.slice(3).trim() : text.slice(6).trim();
if (val.startsWith('@')) {
const resp = await fetch(SERVER + '/v1/alias/resolve/' + val.slice(1));
if (val.startsWith('@') || val.startsWith('0x') || val.startsWith('0X')) {
const endpoint = val.startsWith('@') ? '/v1/alias/resolve/' + val.slice(1) : '/v1/resolve/' + val;
const resp = await fetch(SERVER + endpoint);
const data = await resp.json();
if (data.error) { addSys('Unknown alias ' + val); return; }
if (data.error) { addSys('Cannot resolve ' + val + ': ' + data.error); return; }
$peerInput.value = data.fingerprint;
addSys(val + ' ' + data.fingerprint.slice(0,16) + '...');
addSys(val + ' \u2192 ' + data.fingerprint.slice(0,16) + '...');
} else {
$peerInput.value = val;
}
@@ -1197,7 +1207,7 @@ async function doSend() {
try {
const msgId = await sendEncrypted(peer, text);
sentMsgReceipts[msgId] = { status: 'sent', el: null };
addMsg(myFingerprint.slice(0, 19), text, true, msgId);
addMsg((myEthAddress ? myEthAddress.slice(0,12) + '...' : myFingerprint.slice(0,19)), text, true, msgId);
} catch(e) {
addSys('Send failed: ' + e.message);
}
@@ -1218,6 +1228,7 @@ document.getElementById('btn-show-recover').onclick = () => document.getElementB
document.getElementById('btn-recover').onclick = () => doRecover();
document.getElementById('btn-enter').onclick = () => enterChat();
document.getElementById('send-btn').onclick = () => doSend();
document.getElementById('messages').onclick = () => document.getElementById('msg-input').focus();
document.getElementById('hdr-eth').onclick = function() {
if (myEthAddress) navigator.clipboard.writeText(myEthAddress).then(() => addSys('Copied ETH address'));
};