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
- Click the "Copy HTML Code" button below
- Create a new file called
nip42-demo.html
(or any name you prefer) - Paste the copied code into the file
- Save the file and open it in your web browser
- Make sure you have a NIP-07 extension installed (like Alby or nos2x)
- 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>