Raw HTML Demo - NIP-42 AUTH with NIP-07 Extension

This page displays the raw HTML code for a standalone NIP-42 authentication demo that connects to ws://localhost:3334.

📋 How to Use

  1. Click the "Copy HTML Code" button below
  2. Create a new file called nip42-demo.html (or any name you prefer)
  3. Paste the copied code into the file
  4. Save the file and open it in your web browser
  5. Make sure you have a NIP-07 extension installed (like Alby or nos2x)
  6. Ensure your local relay is running on ws://localhost:3334
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>NIP-42 AUTH with NIP-07 Extension Demo</title> <script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .button { background-color: #4CAF50; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 4px; } .button:disabled { background-color: #cccccc; cursor: not-allowed; } .log { background-color: #f8f9fa; border: 1px solid #e9ecef; padding: 15px; margin: 10px 0; border-radius: 4px; font-family: monospace; white-space: pre-wrap; max-height: 400px; overflow-y: auto; } .status { padding: 10px; margin: 10px 0; border-radius: 4px; font-weight: bold; } .status.connected { background-color: #d4edda; color: #155724; } .status.disconnected { background-color: #f8d7da; color: #721c24; } .status.authenticating { background-color: #fff3cd; color: #856404; } .status.authenticated { background-color: #d1ecf1; color: #0c5460; } .key-info { background-color: #e9ecef; padding: 10px; margin: 10px 0; border-radius: 4px; font-family: monospace; font-size: 12px; } </style> </head> <body> <div class="container"> <h1>NIP-42 AUTH with NIP-07 Extension Demo</h1> <p>This demo connects to ws://localhost:3334 using a NIP-07 browser extension (like Alby or nos2x), performs NIP-42 authentication, and publishes a kind 30078 event.</p> <div class="key-info"> <strong>NIP-07 Extension Signer:</strong><br> Public Key: <span id="pubkey">Not connected yet</span><br> Extension: <span id="extensionName">Not detected</span> </div> <div class="status disconnected" id="status">Disconnected</div> <button class="button" id="connectExtensionBtn" onclick="connectExtension()">Connect Extension</button> <button class="button" id="connectBtn" onclick="connectAndAuth()" disabled>Connect & Authenticate</button> <button class="button" id="publishBtn" onclick="publishEvent()" disabled>Publish Kind 30078 Event</button> <button class="button" onclick="clearLog()">Clear Log</button> <div class="log" id="log">Ready to connect...\n</div> </div> <script> let relay = null; let publicKey = null; let isAuthenticated = false; let authChallenge = null; let extensionConnected = false; function log(message, statusText = null, statusClass = null) { const timestamp = new Date().toLocaleTimeString(); console.log(`[${timestamp}] ${message}`); // Update UI log if element exists const logElement = document.getElementById('log'); if (logElement) { logElement.textContent += `[${timestamp}] ${message}\n`; logElement.scrollTop = logElement.scrollHeight; } // Update status if provided and element exists if (statusText && statusClass) { updateStatusUI(statusText, statusClass); } } function updateStatusUI(status, className) { const statusElement = document.getElementById('status'); if (statusElement) { statusElement.textContent = status; statusElement.className = `status ${className}`; } } function clearLog() { document.getElementById('log').textContent = ''; } async function connectExtension() { log('🔌 Connecting to NIP-07 extension...'); if (!window.nostr) { log('❌ No NIP-07 extension found. Please install Alby, nos2x, or another compatible extension.'); return; } try { // Get public key from extension publicKey = await window.nostr.getPublicKey(); extensionConnected = true; // Update UI document.getElementById('pubkey').textContent = publicKey; document.getElementById('extensionName').textContent = getExtensionName(); document.getElementById('connectBtn').disabled = false; document.getElementById('connectExtensionBtn').textContent = 'Extension Connected ✓'; document.getElementById('connectExtensionBtn').disabled = true; log(`✅ Connected to extension. Public key: ${publicKey}`); } catch (error) { log(`❌ Failed to connect to extension: ${error.message}`); } } function getExtensionName() { if (window.nostr) { if (window.nostr.constructor.name === 'Alby') return 'Alby'; if (window.nostr._metadata?.name) return window.nostr._metadata.name; if (window.alby) return 'Alby'; if (window.nos2x) return 'nos2x'; return 'Unknown NIP-07 Extension'; } return 'Not detected'; } async function signEvent(eventTemplate) { return await window.nostr.signEvent(eventTemplate); } function connectToRelay() { return new Promise((resolve, reject) => { log('🌐 Connecting to relay: ws://localhost:3334'); relay = new WebSocket('ws://localhost:3334'); relay.onopen = function(event) { log('✅ Connected to relay', 'Connected', 'connected'); // Enable publish button once connected and authenticated if (isAuthenticated) { document.getElementById('publishBtn').disabled = false; } resolve(); }; relay.onmessage = function(event) { const message = JSON.parse(event.data); log(`📥 Received: ${JSON.stringify(message)}`); if (message[0] === 'AUTH') { authChallenge = message[1]; log(`🔐 Received AUTH challenge: ${authChallenge}`); performAuth(); } else if (message[0] === 'OK' && message[2] === true) { if (message[3] && message[3].includes('auth-required')) { log('✅ Authentication successful!', 'Authenticated', 'authenticated'); isAuthenticated = true; document.getElementById('publishBtn').disabled = false; } else { log(`✅ Event accepted: ${message[1]}`); } } else if (message[0] === 'OK' && message[2] === false) { log(`❌ Event rejected: ${message[3]}`); } }; relay.onerror = function(error) { log(`❌ WebSocket error: ${error}`); reject(error); }; relay.onclose = function() { log('🔌 Connection closed', 'Disconnected', 'disconnected'); // Reset state isAuthenticated = false; authChallenge = null; // Update UI const connectBtn = document.getElementById('connectBtn'); const publishBtn = document.getElementById('publishBtn'); connectBtn.disabled = false; connectBtn.textContent = 'Connect & Authenticate'; publishBtn.disabled = true; }; }); } async function connectAndAuth() { if (!extensionConnected) { log('❌ Please connect to NIP-07 extension first'); return; } const connectBtn = document.getElementById('connectBtn'); connectBtn.disabled = true; connectBtn.textContent = 'Connecting...'; try { await connectToRelay(); connectBtn.textContent = 'Connected'; } catch (error) { connectBtn.disabled = false; connectBtn.textContent = 'Connect & Authenticate'; log(`❌ Failed to connect: ${error.message}`); } } async function performAuth() { if (!authChallenge) { log('❌ No auth challenge received'); return; } log('🔐 Creating authentication event...', 'Authenticating...', 'authenticating'); try { // Create the kind 22242 AUTH event template const authEventTemplate = { kind: 22242, created_at: Math.floor(Date.now() / 1000), tags: [ ['relay', 'ws://localhost:3334'], ['challenge', authChallenge] ], content: '' }; // Sign the auth event using the selected signer const signedAuthEvent = await signEvent(authEventTemplate); log(`🔏 Signed AUTH event: ${signedAuthEvent.id}`); // Send the AUTH message const authMessage = JSON.stringify(['AUTH', signedAuthEvent]); relay.send(authMessage); log('📤 Sent AUTH event to relay'); } catch (error) { log(`❌ Auth error: ${error.message}`, 'Auth Failed', 'disconnected'); } } async function publishEvent() { if (!relay || !isAuthenticated) { log('❌ Not connected or not authenticated'); return; } const publishBtn = document.getElementById('publishBtn'); publishBtn.disabled = true; publishBtn.textContent = 'Publishing...'; try { log('📝 Creating kind 30078 event...'); // Create a kind 30078 event template (Application-specific data) const event30078Template = { kind: 30078, created_at: Math.floor(Date.now() / 1000), tags: [ ['d', `demo-${Date.now()}`], // identifier tag for kind 30078 ['title', 'NIP-42 AUTH Demo Event'], ['description', 'This event was published after successful NIP-42 authentication'] ], content: JSON.stringify({ message: 'Hello from authenticated client!', timestamp: new Date().toISOString(), demo: true, auth_method: 'NIP-42', signer_type: 'NIP-07' }) }; // Sign the event using the selected signer const signedEvent = await signEvent(event30078Template); log(`📄 Created kind 30078 event: ${signedEvent.id}`); // Send the EVENT message const eventMessage = JSON.stringify(['EVENT', signedEvent]); relay.send(eventMessage); log('📤 Sent kind 30078 event to relay'); // Set up timeout for response setTimeout(() => { publishBtn.disabled = false; publishBtn.textContent = 'Publish Kind 30078 Event'; }, 3000); } catch (error) { log(`❌ Publish error: ${error.message}`); publishBtn.disabled = false; publishBtn.textContent = 'Publish Kind 30078 Event'; } } </script> </body> </html>