455 lines
15 KiB
Markdown
455 lines
15 KiB
Markdown
# NIP-11 Relay Connection Implementation Plan
|
|
|
|
## Overview
|
|
Implement NIP-11 relay information fetching in the web admin interface to replace hardcoded relay pubkey and provide proper relay connection flow.
|
|
|
|
## Current Issues
|
|
1. **Hardcoded Relay Pubkey**: `getRelayPubkey()` returns hardcoded value `'4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa'`
|
|
2. **Relay URL in Debug Section**: Currently in "DEBUG - TEST FETCH WITHOUT LOGIN" section (lines 336-385)
|
|
3. **No Relay Verification**: Users can attempt admin operations without verifying relay identity
|
|
4. **Missing NIP-11 Support**: No fetching of relay information document
|
|
|
|
## Implementation Plan
|
|
|
|
### 1. New Relay Connection Section (HTML Structure)
|
|
|
|
Add after User Info section (around line 332):
|
|
|
|
```html
|
|
<!-- Relay Connection Section -->
|
|
<div class="section">
|
|
<h2>RELAY CONNECTION</h2>
|
|
<div class="input-group">
|
|
<label for="relay-url-input">Relay URL:</label>
|
|
<input type="text" id="relay-url-input" value="ws://localhost:8888" placeholder="ws://localhost:8888 or wss://relay.example.com">
|
|
</div>
|
|
<div class="inline-buttons">
|
|
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
|
|
<button type="button" id="disconnect-relay-btn" style="display: none;">DISCONNECT</button>
|
|
</div>
|
|
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
|
|
|
|
<!-- Relay Information Display -->
|
|
<div id="relay-info-display" class="hidden">
|
|
<h3>Relay Information</h3>
|
|
<div class="user-info">
|
|
<div><strong>Name:</strong> <span id="relay-name">-</span></div>
|
|
<div><strong>Description:</strong> <span id="relay-description">-</span></div>
|
|
<div><strong>Public Key:</strong>
|
|
<div class="user-pubkey" id="relay-pubkey-display">-</div>
|
|
</div>
|
|
<div><strong>Software:</strong> <span id="relay-software">-</span></div>
|
|
<div><strong>Version:</strong> <span id="relay-version">-</span></div>
|
|
<div><strong>Contact:</strong> <span id="relay-contact">-</span></div>
|
|
<div><strong>Supported NIPs:</strong> <span id="relay-nips">-</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### 2. JavaScript Implementation
|
|
|
|
#### Global State Variables
|
|
Add to global state section (around line 535):
|
|
|
|
```javascript
|
|
// Relay connection state
|
|
let relayInfo = null;
|
|
let isRelayConnected = false;
|
|
let relayWebSocket = null;
|
|
```
|
|
|
|
#### NIP-11 Fetching Function
|
|
Add new function:
|
|
|
|
```javascript
|
|
// Fetch relay information using NIP-11
|
|
async function fetchRelayInfo(relayUrl) {
|
|
try {
|
|
console.log('=== FETCHING RELAY INFO VIA NIP-11 ===');
|
|
console.log('Relay URL:', relayUrl);
|
|
|
|
// Convert WebSocket URL to HTTP URL for NIP-11
|
|
let httpUrl = relayUrl;
|
|
if (relayUrl.startsWith('ws://')) {
|
|
httpUrl = relayUrl.replace('ws://', 'http://');
|
|
} else if (relayUrl.startsWith('wss://')) {
|
|
httpUrl = relayUrl.replace('wss://', 'https://');
|
|
}
|
|
|
|
console.log('HTTP URL for NIP-11:', httpUrl);
|
|
|
|
// Fetch relay information document
|
|
const response = await fetch(httpUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/nostr+json'
|
|
},
|
|
// Add timeout
|
|
signal: AbortSignal.timeout(10000) // 10 second timeout
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (!contentType || !contentType.includes('application/json')) {
|
|
throw new Error(`Invalid content type: ${contentType}. Expected application/json or application/nostr+json`);
|
|
}
|
|
|
|
const relayInfoData = await response.json();
|
|
console.log('Fetched relay info:', relayInfoData);
|
|
|
|
// Validate required fields
|
|
if (!relayInfoData.pubkey) {
|
|
throw new Error('Relay information missing required pubkey field');
|
|
}
|
|
|
|
// Validate pubkey format (64 hex characters)
|
|
if (!/^[0-9a-fA-F]{64}$/.test(relayInfoData.pubkey)) {
|
|
throw new Error(`Invalid relay pubkey format: ${relayInfoData.pubkey}`);
|
|
}
|
|
|
|
return relayInfoData;
|
|
|
|
} catch (error) {
|
|
console.error('Failed to fetch relay info:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Relay Connection Function
|
|
Add new function:
|
|
|
|
```javascript
|
|
// Connect to relay and fetch information
|
|
async function connectToRelay() {
|
|
try {
|
|
const relayUrlInput = document.getElementById('relay-url-input');
|
|
const connectBtn = document.getElementById('connect-relay-btn');
|
|
const disconnectBtn = document.getElementById('disconnect-relay-btn');
|
|
const statusDiv = document.getElementById('relay-connection-status');
|
|
const infoDisplay = document.getElementById('relay-info-display');
|
|
|
|
const url = relayUrlInput.value.trim();
|
|
if (!url) {
|
|
throw new Error('Please enter a relay URL');
|
|
}
|
|
|
|
// Update UI to show connecting state
|
|
connectBtn.disabled = true;
|
|
statusDiv.textContent = 'CONNECTING...';
|
|
statusDiv.className = 'status connected';
|
|
|
|
console.log('Connecting to relay:', url);
|
|
|
|
// Fetch relay information via NIP-11
|
|
console.log('Fetching relay information...');
|
|
const fetchedRelayInfo = await fetchRelayInfo(url);
|
|
|
|
// Test WebSocket connection
|
|
console.log('Testing WebSocket connection...');
|
|
await testWebSocketConnection(url);
|
|
|
|
// Store relay information
|
|
relayInfo = fetchedRelayInfo;
|
|
isRelayConnected = true;
|
|
|
|
// Update UI with relay information
|
|
displayRelayInfo(relayInfo);
|
|
|
|
// Update connection status
|
|
statusDiv.textContent = 'CONNECTED';
|
|
statusDiv.className = 'status connected';
|
|
|
|
// Update button states
|
|
connectBtn.style.display = 'none';
|
|
disconnectBtn.style.display = 'inline-block';
|
|
relayUrlInput.disabled = true;
|
|
|
|
// Show relay info
|
|
infoDisplay.classList.remove('hidden');
|
|
|
|
console.log('Successfully connected to relay:', relayInfo.name || url);
|
|
log(`Connected to relay: ${relayInfo.name || url}`, 'INFO');
|
|
|
|
} catch (error) {
|
|
console.error('Failed to connect to relay:', error);
|
|
|
|
// Reset UI state
|
|
const connectBtn = document.getElementById('connect-relay-btn');
|
|
const statusDiv = document.getElementById('relay-connection-status');
|
|
|
|
connectBtn.disabled = false;
|
|
statusDiv.textContent = `CONNECTION FAILED: ${error.message}`;
|
|
statusDiv.className = 'status error';
|
|
|
|
// Clear any partial state
|
|
relayInfo = null;
|
|
isRelayConnected = false;
|
|
|
|
log(`Failed to connect to relay: ${error.message}`, 'ERROR');
|
|
}
|
|
}
|
|
```
|
|
|
|
#### WebSocket Connection Test
|
|
Add new function:
|
|
|
|
```javascript
|
|
// Test WebSocket connection to relay
|
|
async function testWebSocketConnection(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
ws.close();
|
|
reject(new Error('WebSocket connection timeout'));
|
|
}, 5000);
|
|
|
|
const ws = new WebSocket(url);
|
|
|
|
ws.onopen = () => {
|
|
clearTimeout(timeout);
|
|
console.log('WebSocket connection successful');
|
|
ws.close();
|
|
resolve();
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
clearTimeout(timeout);
|
|
console.error('WebSocket connection failed:', error);
|
|
reject(new Error('WebSocket connection failed'));
|
|
};
|
|
|
|
ws.onclose = (event) => {
|
|
if (event.code !== 1000) {
|
|
clearTimeout(timeout);
|
|
reject(new Error(`WebSocket closed with code ${event.code}: ${event.reason}`));
|
|
}
|
|
};
|
|
});
|
|
}
|
|
```
|
|
|
|
#### Display Relay Information
|
|
Add new function:
|
|
|
|
```javascript
|
|
// Display relay information in the UI
|
|
function displayRelayInfo(info) {
|
|
document.getElementById('relay-name').textContent = info.name || 'Unknown';
|
|
document.getElementById('relay-description').textContent = info.description || 'No description';
|
|
document.getElementById('relay-pubkey-display').textContent = info.pubkey || 'Unknown';
|
|
document.getElementById('relay-software').textContent = info.software || 'Unknown';
|
|
document.getElementById('relay-version').textContent = info.version || 'Unknown';
|
|
document.getElementById('relay-contact').textContent = info.contact || 'No contact info';
|
|
|
|
// Format supported NIPs
|
|
let nipsText = 'None specified';
|
|
if (info.supported_nips && Array.isArray(info.supported_nips) && info.supported_nips.length > 0) {
|
|
nipsText = info.supported_nips.map(nip => `NIP-${nip.toString().padStart(2, '0')}`).join(', ');
|
|
}
|
|
document.getElementById('relay-nips').textContent = nipsText;
|
|
}
|
|
```
|
|
|
|
#### Disconnect Function
|
|
Add new function:
|
|
|
|
```javascript
|
|
// Disconnect from relay
|
|
function disconnectFromRelay() {
|
|
console.log('Disconnecting from relay...');
|
|
|
|
// Clear relay state
|
|
relayInfo = null;
|
|
isRelayConnected = false;
|
|
|
|
// Close any existing connections
|
|
if (relayPool) {
|
|
const url = document.getElementById('relay-url-input').value.trim();
|
|
if (url) {
|
|
relayPool.close([url]);
|
|
}
|
|
relayPool = null;
|
|
subscriptionId = null;
|
|
}
|
|
|
|
// Reset UI
|
|
const connectBtn = document.getElementById('connect-relay-btn');
|
|
const disconnectBtn = document.getElementById('disconnect-relay-btn');
|
|
const statusDiv = document.getElementById('relay-connection-status');
|
|
const infoDisplay = document.getElementById('relay-info-display');
|
|
const relayUrlInput = document.getElementById('relay-url-input');
|
|
|
|
connectBtn.style.display = 'inline-block';
|
|
disconnectBtn.style.display = 'none';
|
|
connectBtn.disabled = false;
|
|
relayUrlInput.disabled = false;
|
|
|
|
statusDiv.textContent = 'NOT CONNECTED';
|
|
statusDiv.className = 'status disconnected';
|
|
|
|
infoDisplay.classList.add('hidden');
|
|
|
|
// Reset configuration status
|
|
updateConfigStatus(false);
|
|
|
|
log('Disconnected from relay', 'INFO');
|
|
}
|
|
```
|
|
|
|
#### Update getRelayPubkey Function
|
|
Replace existing function (around line 3142):
|
|
|
|
```javascript
|
|
// Helper function to get relay pubkey from connected relay info
|
|
function getRelayPubkey() {
|
|
if (relayInfo && relayInfo.pubkey) {
|
|
return relayInfo.pubkey;
|
|
}
|
|
|
|
// Fallback to hardcoded value if no relay connected (for testing)
|
|
console.warn('No relay connected, using fallback pubkey');
|
|
return '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa';
|
|
}
|
|
```
|
|
|
|
### 3. Event Handlers
|
|
|
|
Add event handlers in the DOMContentLoaded section:
|
|
|
|
```javascript
|
|
// Relay connection event handlers
|
|
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
|
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
|
|
|
|
if (connectRelayBtn) {
|
|
connectRelayBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
connectToRelay().catch(error => {
|
|
console.error('Connect to relay failed:', error);
|
|
});
|
|
});
|
|
}
|
|
|
|
if (disconnectRelayBtn) {
|
|
disconnectRelayBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
disconnectFromRelay();
|
|
});
|
|
}
|
|
```
|
|
|
|
### 4. Update Existing Functions
|
|
|
|
#### Update fetchConfiguration Function
|
|
Add relay connection check at the beginning:
|
|
|
|
```javascript
|
|
async function fetchConfiguration() {
|
|
try {
|
|
console.log('=== FETCHING CONFIGURATION VIA ADMIN API ===');
|
|
|
|
// Check if relay is connected
|
|
if (!isRelayConnected || !relayInfo) {
|
|
throw new Error('Must be connected to relay first. Please connect to relay in the Relay Connection section.');
|
|
}
|
|
|
|
// ... rest of existing function
|
|
} catch (error) {
|
|
// ... existing error handling
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Update subscribeToConfiguration Function
|
|
Add relay connection check:
|
|
|
|
```javascript
|
|
async function subscribeToConfiguration() {
|
|
try {
|
|
console.log('=== STARTING SIMPLEPOOL CONFIGURATION SUBSCRIPTION ===');
|
|
|
|
if (!isRelayConnected || !relayInfo) {
|
|
console.error('Must be connected to relay first');
|
|
return false;
|
|
}
|
|
|
|
// Use the relay URL from the connection section instead of the debug section
|
|
const url = document.getElementById('relay-url-input').value.trim();
|
|
|
|
// ... rest of existing function
|
|
} catch (error) {
|
|
// ... existing error handling
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Update UI Flow
|
|
|
|
#### Modify showMainInterface Function
|
|
Update to show relay connection requirement:
|
|
|
|
```javascript
|
|
function showMainInterface() {
|
|
loginSection.classList.add('hidden');
|
|
mainInterface.classList.remove('hidden');
|
|
userPubkeyDisplay.textContent = userPubkey;
|
|
|
|
// Show message about relay connection requirement
|
|
if (!isRelayConnected) {
|
|
log('Please connect to a relay to access admin functions', 'INFO');
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. Remove/Update Debug Section
|
|
|
|
#### Option 1: Remove Debug Section Entirely
|
|
Remove the "DEBUG - TEST FETCH WITHOUT LOGIN" section (lines 335-385) since relay URL is now in the proper connection section.
|
|
|
|
#### Option 2: Keep Debug Section for Testing
|
|
Update the debug section to use the connected relay URL and add a note that it's for testing purposes.
|
|
|
|
### 7. Error Handling
|
|
|
|
Add comprehensive error handling for:
|
|
- Network timeouts
|
|
- Invalid relay URLs
|
|
- Missing NIP-11 support
|
|
- Invalid relay pubkey format
|
|
- WebSocket connection failures
|
|
- CORS issues
|
|
|
|
### 8. Security Considerations
|
|
|
|
- Validate relay pubkey format (64 hex characters)
|
|
- Verify relay identity before admin operations
|
|
- Handle CORS properly for NIP-11 requests
|
|
- Sanitize relay information display
|
|
- Warn users about connecting to untrusted relays
|
|
|
|
## Testing Plan
|
|
|
|
1. **NIP-11 Fetching**: Test with various relay URLs (localhost, remote relays)
|
|
2. **Error Handling**: Test with invalid URLs, non-Nostr servers, network failures
|
|
3. **WebSocket Connection**: Verify WebSocket connectivity after NIP-11 fetch
|
|
4. **Admin API Integration**: Ensure admin commands use correct relay pubkey
|
|
5. **UI Flow**: Test complete user journey from login → relay connection → admin operations
|
|
|
|
## Benefits
|
|
|
|
1. **Proper Relay Identification**: Uses actual relay pubkey instead of hardcoded value
|
|
2. **Better UX**: Clear connection flow and relay information display
|
|
3. **Protocol Compliance**: Implements NIP-11 standard for relay discovery
|
|
4. **Security**: Verifies relay identity before admin operations
|
|
5. **Flexibility**: Works with any NIP-11 compliant relay
|
|
|
|
## Migration Notes
|
|
|
|
- Existing users will need to connect to relay after this update
|
|
- Debug section can be kept for development/testing purposes
|
|
- All admin functions will require relay connection
|
|
- Relay pubkey will be dynamically fetched instead of hardcoded |