diff --git a/app.js b/app.js index 60689a0..09c7a0f 100644 --- a/app.js +++ b/app.js @@ -34,6 +34,9 @@ let debugOverlay; let frameCount = 0, lastFPSTime = 0, fps = 0; let chatOpen = true; let loadProgress = 0; +let nostrPubkey = null; + +const NOSTR_STORAGE_KEY = 'nexus_nostr_pubkey'; // ═══ INIT ═══ function init() { @@ -95,6 +98,9 @@ function init() { setupControls(); window.addEventListener('resize', onResize); + // NIP-07 identity (after DOM is ready) + initNostrIdentity(); + // Debug overlay ref debugOverlay = document.getElementById('debug-overlay'); @@ -866,7 +872,8 @@ function addChatMessage(type, text) { const container = document.getElementById('chat-messages'); const div = document.createElement('div'); div.className = `chat-msg chat-msg-${type}`; - const prefixes = { user: '[ALEXANDER]', timmy: '[TIMMY]', system: '[NEXUS]', error: '[ERROR]' }; + const userPrefix = nostrPubkey ? `[${formatPubkey(nostrPubkey)}]` : '[VISITOR]'; + const prefixes = { user: userPrefix, timmy: '[TIMMY]', system: '[NEXUS]', error: '[ERROR]' }; div.innerHTML = `${prefixes[type] || '[???]'} ${text}`; container.appendChild(div); container.scrollTop = container.scrollHeight; @@ -980,5 +987,71 @@ function onResize() { composer.setSize(w, h); } +// ═══ NIP-07 NOSTR IDENTITY ═══ +function formatPubkey(hex) { + return hex.slice(0, 8) + '…' + hex.slice(-4); +} + +function setNostrIdentity(pubkey) { + nostrPubkey = pubkey; + localStorage.setItem(NOSTR_STORAGE_KEY, pubkey); + document.getElementById('nostr-anon').style.display = 'none'; + document.getElementById('nostr-connect-btn').style.display = 'none'; + document.getElementById('nostr-pubkey-display').textContent = formatPubkey(pubkey); + document.getElementById('nostr-connected').style.display = 'flex'; + addChatMessage('system', `Identity connected: ${formatPubkey(pubkey)}. Timmy recognizes you.`); +} + +function clearNostrIdentity() { + nostrPubkey = null; + localStorage.removeItem(NOSTR_STORAGE_KEY); + document.getElementById('nostr-connected').style.display = 'none'; + document.getElementById('nostr-anon').style.display = 'flex'; + if (window.nostr) { + document.getElementById('nostr-connect-btn').style.display = 'flex'; + } + addChatMessage('system', 'Identity disconnected. Visitor is anonymous.'); +} + +function initNostrIdentity() { + const connectBtn = document.getElementById('nostr-connect-btn'); + const disconnectBtn = document.getElementById('nostr-disconnect-btn'); + + connectBtn.addEventListener('click', async () => { + try { + connectBtn.disabled = true; + connectBtn.textContent = 'Connecting…'; + const pubkey = await window.nostr.getPublicKey(); + if (pubkey && /^[0-9a-f]{64}$/i.test(pubkey)) { + setNostrIdentity(pubkey); + } else { + throw new Error('Invalid pubkey returned'); + } + } catch (err) { + addChatMessage('error', 'Identity request declined or extension unavailable.'); + connectBtn.disabled = false; + connectBtn.innerHTML = ' Connect Identity'; + } + }); + + disconnectBtn.addEventListener('click', () => { + clearNostrIdentity(); + }); + + // Check for saved identity in localStorage + const saved = localStorage.getItem(NOSTR_STORAGE_KEY); + if (saved && /^[0-9a-f]{64}$/i.test(saved)) { + nostrPubkey = saved; + document.getElementById('nostr-anon').style.display = 'none'; + document.getElementById('nostr-pubkey-display').textContent = formatPubkey(saved); + document.getElementById('nostr-connected').style.display = 'flex'; + } else if (window.nostr) { + // Extension available, offer connect + document.getElementById('nostr-anon').style.display = 'none'; + connectBtn.style.display = 'flex'; + } + // else: stays anonymous (default) +} + // ═══ START ═══ init(); diff --git a/index.html b/index.html index 3a2c6ea..6e6b908 100644 --- a/index.html +++ b/index.html @@ -86,7 +86,7 @@ [NEXUS] Sovereign space initialized. Timmy is observing.