Aliases: human-readable names mapped to fingerprints
Server: - POST /v1/alias/register — claim an alias (one per fingerprint) - GET /v1/alias/resolve/:name — alias → fingerprint - GET /v1/alias/whois/:fingerprint — fingerprint → alias (reverse) - GET /v1/alias/list — list all aliases - Bidirectional mapping in sled (a:name→fp, fp:fp→name) - One alias per person, re-registering replaces old alias Web client: - /alias <name> — register your alias - /aliases — list all registered aliases - /info — now shows alias alongside fingerprint - Peer input accepts @alias (resolved before sending) - Received messages show @alias instead of fingerprint - DM: paste @alias or fingerprint in peer input CLI TUI: - /alias <name> — register alias - /aliases — list all aliases - /peer @alias — resolves alias to fingerprint - Alias resolution displayed in system messages Addressing model: - @manwe (local) → server resolves → fingerprint - @manwe.b1.example.com (federated) → DNS resolve (Phase 3) - Raw fingerprint → always works, no resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -301,7 +301,13 @@ async function pollMessages() {
|
||||
const aesKey = await fetchPeerKey(env.from);
|
||||
const ct = fromHex(env.ciphertext);
|
||||
const text = await aesDecrypt(aesKey, ct);
|
||||
const fromLabel = formatFP(fromHex(env.from)).slice(0, 19);
|
||||
let fromLabel = env.from.slice(0, 12);
|
||||
// Try to resolve alias
|
||||
try {
|
||||
const ar = await fetch(SERVER + '/v1/alias/whois/' + env.from);
|
||||
const ad = await ar.json();
|
||||
if (ad.alias) fromLabel = '@' + ad.alias;
|
||||
} catch(e) {}
|
||||
const groupTag = env.group ? ' [' + env.group + ']' : '';
|
||||
addMsg(fromLabel + groupTag, text, false);
|
||||
continue;
|
||||
@@ -383,9 +389,8 @@ async function enterChat() {
|
||||
await registerKey();
|
||||
addSys('Identity loaded: ' + myFingerprint);
|
||||
addSys('Key registered with server');
|
||||
addSys('DM: paste peer fingerprint above');
|
||||
addSys('Groups: /gcreate <name> · /gjoin <name> · /g <name> · /glist');
|
||||
addSys('Other: /info · /clear · /dm (switch back to DM mode)');
|
||||
addSys('DM: paste peer fingerprint or @alias above');
|
||||
addSys('/alias <name> · /g <group> · /glist · /info · /clear');
|
||||
|
||||
const savedPeer = localStorage.getItem('wz-peer');
|
||||
if (savedPeer) $peerInput.value = savedPeer;
|
||||
@@ -489,12 +494,36 @@ async function doSend() {
|
||||
if (!text) return;
|
||||
|
||||
// Commands
|
||||
if (text === '/info') { addSys('Fingerprint: ' + myFingerprint); return; }
|
||||
if (text === '/info') {
|
||||
const aliasResp = await fetch(SERVER + '/v1/alias/whois/' + normFP(myFingerprint));
|
||||
const aliasData = await aliasResp.json();
|
||||
const aliasStr = aliasData.alias ? ' (@' + aliasData.alias + ')' : '';
|
||||
addSys('Fingerprint: ' + myFingerprint + aliasStr);
|
||||
return;
|
||||
}
|
||||
if (text === '/clear') { $messages.innerHTML = ''; return; }
|
||||
if (text === '/quit') { window.close(); return; }
|
||||
if (text === '/glist') { await groupList(); return; }
|
||||
if (text === '/dm') { currentGroup = null; addSys('Switched to DM mode'); $peerInput.value = localStorage.getItem('wz-peer') || ''; return; }
|
||||
if (text === '/aliases') {
|
||||
const resp = await fetch(SERVER + '/v1/alias/list');
|
||||
const data = await resp.json();
|
||||
if (data.aliases.length === 0) { addSys('No aliases registered'); }
|
||||
else { for (const a of data.aliases) addSys(' @' + a.alias + ' → ' + a.fingerprint.slice(0,16) + '...'); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (text.startsWith('/alias ')) {
|
||||
const name = text.slice(7).trim();
|
||||
const resp = await fetch(SERVER + '/v1/alias/register', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ alias: name, fingerprint: normFP(myFingerprint) })
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.error) { addSys('Error: ' + data.error); }
|
||||
else { addSys('Alias @' + data.alias + ' registered'); }
|
||||
return;
|
||||
}
|
||||
if (text.startsWith('/gcreate ')) { await groupCreate(text.slice(9).trim()); return; }
|
||||
if (text.startsWith('/gjoin ')) { await groupJoin(text.slice(7).trim()); return; }
|
||||
if (text.startsWith('/g ')) { await groupSwitch(text.slice(3).trim()); return; }
|
||||
@@ -509,10 +538,20 @@ async function doSend() {
|
||||
return;
|
||||
}
|
||||
|
||||
// DM
|
||||
const peer = $peerInput.value.trim();
|
||||
if (!peer || peer.startsWith('#')) { addSys('Set a peer fingerprint or use /g <group>'); return; }
|
||||
localStorage.setItem('wz-peer', peer);
|
||||
// DM — resolve @alias if needed
|
||||
let peer = $peerInput.value.trim();
|
||||
if (!peer || peer.startsWith('#')) { addSys('Set a peer fingerprint/@alias or use /g <group>'); return; }
|
||||
|
||||
if (peer.startsWith('@')) {
|
||||
const aliasName = peer.slice(1);
|
||||
const resp = await fetch(SERVER + '/v1/alias/resolve/' + aliasName);
|
||||
const data = await resp.json();
|
||||
if (data.error) { addSys('Unknown alias @' + aliasName); return; }
|
||||
peer = data.fingerprint;
|
||||
addSys('Resolved @' + aliasName + ' → ' + peer.slice(0,16) + '...');
|
||||
}
|
||||
|
||||
localStorage.setItem('wz-peer', $peerInput.value.trim());
|
||||
|
||||
try {
|
||||
await sendEncrypted(peer, text);
|
||||
|
||||
Reference in New Issue
Block a user