v1.2.50 - Fix NIP-42 relay URL verification and add onauth to all publish flows
This commit is contained in:
+303
-103
@@ -51,6 +51,12 @@ let pendingSqlQueries = new Map();
|
|||||||
let eventRateChart = null;
|
let eventRateChart = null;
|
||||||
let previousTotalEvents = 0; // Track previous total for rate calculation
|
let previousTotalEvents = 0; // Track previous total for rate calculation
|
||||||
|
|
||||||
|
// Temporary diagnostics toggle: disable external profile relay lookups
|
||||||
|
const DISABLE_EXTERNAL_PROFILE_RELAYS = false;
|
||||||
|
|
||||||
|
// Temporary diagnostics toggle: enable deep SimplePool diagnostics in nostr.bundle.js
|
||||||
|
window.__SIMPLE_POOL_DEBUG__ = true;
|
||||||
|
|
||||||
// Relay Events state - now handled by main subscription
|
// Relay Events state - now handled by main subscription
|
||||||
|
|
||||||
// DOM elements
|
// DOM elements
|
||||||
@@ -85,6 +91,47 @@ function log(message, type = 'INFO') {
|
|||||||
// UI logging removed - using console only
|
// UI logging removed - using console only
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeRelayUrlForPool(url) {
|
||||||
|
try {
|
||||||
|
let normalized = url || '';
|
||||||
|
if (!normalized.includes('://')) {
|
||||||
|
normalized = 'wss://' + normalized;
|
||||||
|
}
|
||||||
|
const parsed = new URL(normalized);
|
||||||
|
parsed.pathname = parsed.pathname.replace(/\/+/g, '/');
|
||||||
|
if (parsed.pathname.endsWith('/')) {
|
||||||
|
parsed.pathname = parsed.pathname.slice(0, -1);
|
||||||
|
}
|
||||||
|
if ((parsed.port === '80' && parsed.protocol === 'ws:') ||
|
||||||
|
(parsed.port === '443' && parsed.protocol === 'wss:')) {
|
||||||
|
parsed.port = '';
|
||||||
|
}
|
||||||
|
parsed.searchParams.sort();
|
||||||
|
parsed.hash = '';
|
||||||
|
return parsed.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to normalize relay URL for diagnostics:', url, error?.message || error);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleNip42Auth(url, challenge, context = 'SimplePool') {
|
||||||
|
console.log(`🔐 NIP-42 AUTH challenge during ${context}:`, challenge);
|
||||||
|
try {
|
||||||
|
if (!window.nostr) {
|
||||||
|
console.error(`NIP-42 auth required during ${context} but no nostr extension available`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authEvent = window.NostrTools.nip42.makeAuthEvent(url, challenge);
|
||||||
|
const signedAuthEvent = await window.nostr.signEvent(authEvent);
|
||||||
|
console.log(`✅ NIP-42 AUTH signed for ${context}`);
|
||||||
|
return signedAuthEvent;
|
||||||
|
} catch (authError) {
|
||||||
|
console.error(`NIP-42 AUTH failed during ${context}:`, authError.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
function log(message, type = 'INFO') {
|
function log(message, type = 'INFO') {
|
||||||
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
||||||
@@ -237,11 +284,40 @@ async function checkExistingAuthWithRetries() {
|
|||||||
console.log('Trying window.NOSTR_LOGIN_LITE.getAuthState()...');
|
console.log('Trying window.NOSTR_LOGIN_LITE.getAuthState()...');
|
||||||
const authState = window.NOSTR_LOGIN_LITE.getAuthState();
|
const authState = window.NOSTR_LOGIN_LITE.getAuthState();
|
||||||
if (authState && authState.pubkey) {
|
if (authState && authState.pubkey) {
|
||||||
|
// Extension auth must be actively restorable, otherwise treat as stale and continue.
|
||||||
|
if (authState.method === 'extension') {
|
||||||
|
console.log('Stored extension auth found, verifying live extension availability...');
|
||||||
|
let restoredExtensionAuth = null;
|
||||||
|
|
||||||
|
if (window.nostr && typeof window.nostr.restoreAuthState === 'function') {
|
||||||
|
restoredExtensionAuth = await window.nostr.restoreAuthState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restoredExtensionAuth && restoredExtensionAuth.pubkey) {
|
||||||
|
console.log('✅ Extension auth restored successfully:', restoredExtensionAuth.pubkey);
|
||||||
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (restored extension session)', {
|
||||||
|
method: 'extension',
|
||||||
|
pubkey: restoredExtensionAuth.pubkey
|
||||||
|
});
|
||||||
|
await restoreAuthenticationState(restoredExtensionAuth.pubkey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('⚠️ Stored extension auth is stale (no live extension), clearing and continuing');
|
||||||
|
if (typeof window.NOSTR_LOGIN_LITE.clearAuthState === 'function') {
|
||||||
|
window.NOSTR_LOGIN_LITE.clearAuthState();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
console.log('✅ Auth state found via NOSTR_LOGIN_LITE.getAuthState():', authState.pubkey);
|
console.log('✅ Auth state found via NOSTR_LOGIN_LITE.getAuthState():', authState.pubkey);
|
||||||
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (restored NOSTR_LOGIN_LITE auth state)', {
|
||||||
|
method: authState.method || 'unknown',
|
||||||
|
pubkey: authState.pubkey
|
||||||
|
});
|
||||||
await restoreAuthenticationState(authState.pubkey);
|
await restoreAuthenticationState(authState.pubkey);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Method 2: Try nlLite.getPublicKey()
|
// Method 2: Try nlLite.getPublicKey()
|
||||||
if (nlLite && typeof nlLite.getPublicKey === 'function') {
|
if (nlLite && typeof nlLite.getPublicKey === 'function') {
|
||||||
@@ -249,6 +325,10 @@ async function checkExistingAuthWithRetries() {
|
|||||||
const pubkey = await nlLite.getPublicKey();
|
const pubkey = await nlLite.getPublicKey();
|
||||||
if (pubkey && pubkey.length === 64) {
|
if (pubkey && pubkey.length === 64) {
|
||||||
console.log('✅ Pubkey found via nlLite.getPublicKey():', pubkey);
|
console.log('✅ Pubkey found via nlLite.getPublicKey():', pubkey);
|
||||||
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (restored nlLite session)', {
|
||||||
|
method: 'restored-nlLite',
|
||||||
|
pubkey
|
||||||
|
});
|
||||||
await restoreAuthenticationState(pubkey);
|
await restoreAuthenticationState(pubkey);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -260,6 +340,10 @@ async function checkExistingAuthWithRetries() {
|
|||||||
const pubkey = await window.nostr.getPublicKey();
|
const pubkey = await window.nostr.getPublicKey();
|
||||||
if (pubkey && pubkey.length === 64) {
|
if (pubkey && pubkey.length === 64) {
|
||||||
console.log('✅ Pubkey found via window.nostr.getPublicKey():', pubkey);
|
console.log('✅ Pubkey found via window.nostr.getPublicKey():', pubkey);
|
||||||
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (restored window.nostr session)', {
|
||||||
|
method: 'restored-window.nostr',
|
||||||
|
pubkey
|
||||||
|
});
|
||||||
await restoreAuthenticationState(pubkey);
|
await restoreAuthenticationState(pubkey);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -272,6 +356,10 @@ async function checkExistingAuthWithRetries() {
|
|||||||
const parsedData = JSON.parse(localStorageData);
|
const parsedData = JSON.parse(localStorageData);
|
||||||
if (parsedData.pubkey) {
|
if (parsedData.pubkey) {
|
||||||
console.log('✅ Pubkey found in localStorage:', parsedData.pubkey);
|
console.log('✅ Pubkey found in localStorage:', parsedData.pubkey);
|
||||||
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (restored localStorage session)', {
|
||||||
|
method: 'restored-localStorage',
|
||||||
|
pubkey: parsedData.pubkey
|
||||||
|
});
|
||||||
await restoreAuthenticationState(parsedData.pubkey);
|
await restoreAuthenticationState(parsedData.pubkey);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -295,6 +383,7 @@ async function checkExistingAuthWithRetries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.warn('❌ LOGIN ATTEMPT RESULT: NO STORED SESSION FOUND (showing login modal)');
|
||||||
console.log('🔍 Authentication detection completed - no existing auth found after all attempts');
|
console.log('🔍 Authentication detection completed - no existing auth found after all attempts');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -314,7 +403,12 @@ async function restoreAuthenticationState(pubkey) {
|
|||||||
await setupAutomaticRelayConnection(false);
|
await setupAutomaticRelayConnection(false);
|
||||||
|
|
||||||
// Verify admin access before showing admin sections
|
// Verify admin access before showing admin sections
|
||||||
verifyAdminAccess();
|
const adminCheckResult = await verifyAdminAccess('restore-auth-state');
|
||||||
|
if (adminCheckResult?.passed) {
|
||||||
|
console.log('✅ POST-LOGIN ADMIN CHECK PASSED (restore):', adminCheckResult.reason);
|
||||||
|
} else {
|
||||||
|
console.warn('❌ POST-LOGIN ADMIN CHECK FAILED (restore):', adminCheckResult?.reason || 'Unknown reason');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('✅ Authentication state restored successfully');
|
console.log('✅ Authentication state restored successfully');
|
||||||
}
|
}
|
||||||
@@ -447,7 +541,7 @@ async function initializeApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle authentication events
|
// Handle authentication events
|
||||||
function handleAuthEvent(event) {
|
async function handleAuthEvent(event) {
|
||||||
const { pubkey, method, error } = event.detail;
|
const { pubkey, method, error } = event.detail;
|
||||||
|
|
||||||
if (method && pubkey) {
|
if (method && pubkey) {
|
||||||
@@ -455,20 +549,34 @@ function handleAuthEvent(event) {
|
|||||||
isLoggedIn = true;
|
isLoggedIn = true;
|
||||||
console.log(`Login successful! Method: ${method}`);
|
console.log(`Login successful! Method: ${method}`);
|
||||||
console.log(`Public key: ${pubkey}`);
|
console.log(`Public key: ${pubkey}`);
|
||||||
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (interactive login)', { method, pubkey });
|
||||||
|
|
||||||
// Hide login modal and show profile in header
|
// Hide login modal and show profile in header
|
||||||
hideLoginModal();
|
hideLoginModal();
|
||||||
showProfileInHeader();
|
showProfileInHeader();
|
||||||
loadUserProfile();
|
loadUserProfile();
|
||||||
|
|
||||||
|
console.log('🔎 POST-LOGIN DEBUG: Starting admin verification flow', {
|
||||||
|
method,
|
||||||
|
pubkey,
|
||||||
|
isLoggedIn,
|
||||||
|
isRelayConnected
|
||||||
|
});
|
||||||
|
|
||||||
// Automatically set up relay connection
|
// Automatically set up relay connection
|
||||||
setupAutomaticRelayConnection(false); // Don't show sections yet - wait for admin verification
|
await setupAutomaticRelayConnection(false); // Don't show sections yet - wait for admin verification
|
||||||
|
|
||||||
// Verify admin access before showing admin sections
|
// Verify admin access before showing admin sections
|
||||||
verifyAdminAccess();
|
const adminCheckResult = await verifyAdminAccess('auth-event');
|
||||||
|
if (adminCheckResult?.passed) {
|
||||||
|
console.log('✅ POST-LOGIN ADMIN CHECK PASSED:', adminCheckResult.reason);
|
||||||
|
} else {
|
||||||
|
console.warn('❌ POST-LOGIN ADMIN CHECK FAILED:', adminCheckResult?.reason || 'Unknown reason');
|
||||||
|
}
|
||||||
|
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
console.log(`Authentication error: ${error}`);
|
console.log(`Authentication error: ${error}`);
|
||||||
|
console.warn('❌ LOGIN ATTEMPT RESULT: FAILED (interactive login)', { error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,22 +617,40 @@ function handleLogoutEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify admin access by sending a system_status command and waiting for response
|
// Verify admin access by sending a system_status command and waiting for response
|
||||||
async function verifyAdminAccess() {
|
async function verifyAdminAccess(source = 'unknown') {
|
||||||
console.log('=== VERIFYING ADMIN ACCESS (BYPASS MODE) ===');
|
console.log('=== VERIFYING ADMIN ACCESS (BYPASS MODE) ===', { source, isLoggedIn, isRelayConnected });
|
||||||
|
|
||||||
if (!isLoggedIn || !isRelayConnected) {
|
if (!isLoggedIn) {
|
||||||
console.log('Cannot verify admin access - not logged in or not connected to relay');
|
const reason = 'User is not logged in';
|
||||||
showAccessDeniedOverlay();
|
console.warn('❌ Admin check failed:', reason, { source });
|
||||||
return;
|
isAdminVerified = false;
|
||||||
|
pendingAdminVerification = false;
|
||||||
|
hideAccessDeniedOverlay();
|
||||||
|
updateAdminSectionsVisibility();
|
||||||
|
return { passed: false, reason, source };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRelayConnected) {
|
||||||
|
const reason = 'Relay connection is not ready yet';
|
||||||
|
console.warn('⚠️ Admin check deferred:', reason, { source });
|
||||||
|
pendingAdminVerification = false;
|
||||||
|
isAdminVerified = false;
|
||||||
|
hideAccessDeniedOverlay(); // Do not show ACCESS DENIED while connection is still establishing
|
||||||
|
updateAdminSectionsVisibility();
|
||||||
|
return { passed: false, reason, source, deferred: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMPORARY BYPASS: Skip admin verification and grant access immediately
|
// TEMPORARY BYPASS: Skip admin verification and grant access immediately
|
||||||
// TODO: Re-enable once idle_connection_timeout_sec is increased to 120+
|
// TODO: Re-enable once idle_connection_timeout_sec is increased to 120+
|
||||||
pendingAdminVerification = false;
|
pendingAdminVerification = false;
|
||||||
isAdminVerified = true;
|
isAdminVerified = true;
|
||||||
|
hideAccessDeniedOverlay();
|
||||||
hideAdminVerificationLoading();
|
hideAdminVerificationLoading();
|
||||||
updateAdminSectionsVisibility();
|
updateAdminSectionsVisibility();
|
||||||
console.log('✅ Admin verification bypassed (temporary)');
|
|
||||||
|
const reason = 'Bypass mode enabled (temporary)';
|
||||||
|
console.log('✅ Admin verification passed:', reason, { source });
|
||||||
|
return { passed: true, reason, source, bypass: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loading indicator while verifying admin access
|
// Show loading indicator while verifying admin access
|
||||||
@@ -749,6 +875,20 @@ async function loadUserProfile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (DISABLE_EXTERNAL_PROFILE_RELAYS) {
|
||||||
|
console.warn('🧪 PROFILE RELAY TEST: External profile relays disabled; skipping profile query');
|
||||||
|
if (headerUserName) {
|
||||||
|
headerUserName.textContent = 'External profile lookup disabled (test mode)';
|
||||||
|
}
|
||||||
|
if (persistentUserName) {
|
||||||
|
persistentUserName.textContent = 'External profile lookup disabled (test mode)';
|
||||||
|
}
|
||||||
|
if (persistentUserAbout) {
|
||||||
|
persistentUserAbout.textContent = 'No external relay query performed';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a SimplePool instance for profile loading
|
// Create a SimplePool instance for profile loading
|
||||||
const profilePool = new window.NostrTools.SimplePool();
|
const profilePool = new window.NostrTools.SimplePool();
|
||||||
const relays = ['wss://relay.damus.io',
|
const relays = ['wss://relay.damus.io',
|
||||||
@@ -975,40 +1115,17 @@ function attachWebSocketMonitoring(relayPool, url) {
|
|||||||
|
|
||||||
// SimplePool stores connections in _conn object
|
// SimplePool stores connections in _conn object
|
||||||
if (relayPool && relayPool._conn) {
|
if (relayPool && relayPool._conn) {
|
||||||
// Monitor when connections are created
|
// Diagnostics only: inspect existing connection without mutating _conn internals
|
||||||
const originalGetConnection = relayPool._conn[url];
|
const existingConn = relayPool._conn[url];
|
||||||
if (originalGetConnection) {
|
if (existingConn) {
|
||||||
console.log('📡 Found existing connection for URL:', url);
|
console.log('📡 Found existing connection for URL:', url);
|
||||||
|
if (existingConn.ws && !existingConn.ws._monitored) {
|
||||||
// Try to access the WebSocket if it's available
|
attachWebSocketEventListeners(existingConn.ws, url);
|
||||||
const conn = relayPool._conn[url];
|
existingConn.ws._monitored = true;
|
||||||
if (conn && conn.ws) {
|
|
||||||
attachWebSocketEventListeners(conn.ws, url);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log('📡 No existing _conn entry yet for URL:', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override the connection getter to monitor new connections
|
|
||||||
const originalConn = relayPool._conn;
|
|
||||||
relayPool._conn = new Proxy(originalConn, {
|
|
||||||
get(target, prop) {
|
|
||||||
const conn = target[prop];
|
|
||||||
if (conn && conn.ws && !conn.ws._monitored) {
|
|
||||||
console.log('🔗 New WebSocket connection detected for:', prop);
|
|
||||||
attachWebSocketEventListeners(conn.ws, prop);
|
|
||||||
conn.ws._monitored = true;
|
|
||||||
}
|
|
||||||
return conn;
|
|
||||||
},
|
|
||||||
set(target, prop, value) {
|
|
||||||
if (value && value.ws && !value.ws._monitored) {
|
|
||||||
console.log('🔗 WebSocket connection being set for:', prop);
|
|
||||||
attachWebSocketEventListeners(value.ws, prop);
|
|
||||||
value.ws._monitored = true;
|
|
||||||
}
|
|
||||||
target[prop] = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ WebSocket monitoring attached');
|
console.log('✅ WebSocket monitoring attached');
|
||||||
@@ -1160,7 +1277,32 @@ async function subscribeToConfiguration() {
|
|||||||
console.log(`📝 Generated subscription ID: ${subscriptionId}`);
|
console.log(`📝 Generated subscription ID: ${subscriptionId}`);
|
||||||
console.log(`👤 User pubkey: ${userPubkey}`);
|
console.log(`👤 User pubkey: ${userPubkey}`);
|
||||||
console.log(`🎯 About to call relayPool.subscribeMany with URL: ${url}`);
|
console.log(`🎯 About to call relayPool.subscribeMany with URL: ${url}`);
|
||||||
|
const normalizedUrl = normalizeRelayUrlForPool(url);
|
||||||
|
const relayMapKeysBefore = relayPool?.relays ? Array.from(relayPool.relays.keys()) : [];
|
||||||
console.log(`📊 relayPool._conn before subscribeMany:`, Object.keys(relayPool._conn || {}));
|
console.log(`📊 relayPool._conn before subscribeMany:`, Object.keys(relayPool._conn || {}));
|
||||||
|
console.log('🔎 ADMIN SUB DIAG: URL normalization', {
|
||||||
|
rawUrl: url,
|
||||||
|
normalizedUrl,
|
||||||
|
relayMapKeysBefore
|
||||||
|
});
|
||||||
|
const preSubConn = relayPool._conn?.[url] || relayPool._conn?.[normalizedUrl];
|
||||||
|
const preSubRelay = relayPool?.relays?.get?.(normalizedUrl);
|
||||||
|
console.log('🔎 ADMIN SUB DIAG: pre-subscribe connection snapshot', {
|
||||||
|
url,
|
||||||
|
normalizedUrl,
|
||||||
|
hasConn: !!preSubConn,
|
||||||
|
connType: preSubConn?.constructor?.name,
|
||||||
|
hasWs: !!preSubConn?.ws,
|
||||||
|
wsReadyState: preSubConn?.ws?.readyState,
|
||||||
|
wsUrl: preSubConn?.ws?.url,
|
||||||
|
wsProtocol: preSubConn?.ws?.protocol,
|
||||||
|
hasRelayObj: !!preSubRelay,
|
||||||
|
relayConnected: preSubRelay?.connected,
|
||||||
|
isLoggedIn,
|
||||||
|
isRelayConnected,
|
||||||
|
isSubscribed,
|
||||||
|
isSubscribing
|
||||||
|
});
|
||||||
|
|
||||||
// Mark as subscribed BEFORE calling subscribeMany to prevent race conditions
|
// Mark as subscribed BEFORE calling subscribeMany to prevent race conditions
|
||||||
isSubscribed = true;
|
isSubscribed = true;
|
||||||
@@ -1310,42 +1452,38 @@ async function subscribeToConfiguration() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onauth(challenge) {
|
async onauth(challenge) {
|
||||||
// NIP-42 authentication: relay sent AUTH challenge, sign and respond
|
const authUrl = relayConnectionUrl.value.trim();
|
||||||
console.log('🔐 NIP-42 AUTH challenge received:', challenge);
|
return handleNip42Auth(authUrl, challenge, 'subscription');
|
||||||
try {
|
|
||||||
if (!window.nostr) {
|
|
||||||
console.error('NIP-42 auth required but no nostr extension available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = relayConnectionUrl.value.trim();
|
|
||||||
const authEvent = window.NostrTools.nip42.makeAuthEvent(url, challenge);
|
|
||||||
const signedAuthEvent = await window.nostr.signEvent(authEvent);
|
|
||||||
console.log('✅ NIP-42 AUTH event signed, responding to challenge');
|
|
||||||
return signedAuthEvent;
|
|
||||||
} catch (authError) {
|
|
||||||
console.error('NIP-42 AUTH failed:', authError.message);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onclose(reason) {
|
onclose(reason) {
|
||||||
console.log('Subscription closed:', reason);
|
const closeNormalizedUrl = normalizeRelayUrlForPool(url);
|
||||||
// Only reset state if the admin relay itself closed (not external relays timing out)
|
const closeConn = relayPool?._conn?.[url] || relayPool?._conn?.[closeNormalizedUrl];
|
||||||
// reason is an array of close reasons, one per relay in the subscription
|
const closeRelay = relayPool?.relays?.get?.(closeNormalizedUrl);
|
||||||
const adminUrl = relayConnectionUrl.value.trim();
|
|
||||||
const reasons = Array.isArray(reason) ? reason : [reason];
|
const reasons = Array.isArray(reason) ? reason : [reason];
|
||||||
// Check if the admin relay's connection closed (not just external relays timing out)
|
console.log('Subscription closed:', reason);
|
||||||
// If all reasons are 'connection timed out', these are external relays, not our relay
|
console.log('🔎 ADMIN SUB DIAG: onclose details', {
|
||||||
const allTimeout = reasons.every(r => r === 'connection timed out');
|
url,
|
||||||
if (!allTimeout) {
|
closeNormalizedUrl,
|
||||||
// Admin relay actually closed — reset state
|
reasons,
|
||||||
|
allTimeout: reasons.length > 0 && reasons.every(r => String(r).includes('timeout')),
|
||||||
|
hasConn: !!closeConn,
|
||||||
|
hasWs: !!closeConn?.ws,
|
||||||
|
wsReadyState: closeConn?.ws?.readyState,
|
||||||
|
wsUrl: closeConn?.ws?.url,
|
||||||
|
hasRelayObj: !!closeRelay,
|
||||||
|
relayConnected: closeRelay?.connected,
|
||||||
|
isLoggedIn,
|
||||||
|
isRelayConnected,
|
||||||
|
isSubscribed,
|
||||||
|
isSubscribing
|
||||||
|
});
|
||||||
|
// This subscription is created only for the admin relay URL in subscribeMany([url], ...),
|
||||||
|
// so any close/timeout here must reset admin relay state to force clean reconnect.
|
||||||
isSubscribed = false;
|
isSubscribed = false;
|
||||||
isSubscribing = false;
|
isSubscribing = false;
|
||||||
isRelayConnected = false;
|
isRelayConnected = false;
|
||||||
updateConfigStatus(false);
|
updateConfigStatus(false);
|
||||||
log('WebSocket connection closed - subscription state reset', 'WARNING');
|
log('Admin relay subscription closed/timed out - connection state reset', 'WARNING');
|
||||||
} else {
|
|
||||||
// External relays timed out — keep admin relay state intact
|
|
||||||
log('External relay connections timed out (admin relay still connected)', 'INFO');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1356,6 +1494,27 @@ async function subscribeToConfiguration() {
|
|||||||
isSubscribed = true;
|
isSubscribed = true;
|
||||||
isSubscribing = false;
|
isSubscribing = false;
|
||||||
|
|
||||||
|
const normalizedUrlAfter = normalizeRelayUrlForPool(url);
|
||||||
|
const relayMapKeysAfter = relayPool?.relays ? Array.from(relayPool.relays.keys()) : [];
|
||||||
|
const postSubConn = relayPool._conn?.[url] || relayPool._conn?.[normalizedUrlAfter];
|
||||||
|
const postSubRelay = relayPool?.relays?.get?.(normalizedUrlAfter);
|
||||||
|
console.log('🔎 ADMIN SUB DIAG: post-subscribe connection snapshot', {
|
||||||
|
url,
|
||||||
|
normalizedUrl: normalizedUrlAfter,
|
||||||
|
relayMapKeysAfter,
|
||||||
|
hasConn: !!postSubConn,
|
||||||
|
connType: postSubConn?.constructor?.name,
|
||||||
|
hasWs: !!postSubConn?.ws,
|
||||||
|
wsReadyState: postSubConn?.ws?.readyState,
|
||||||
|
wsUrl: postSubConn?.ws?.url,
|
||||||
|
wsProtocol: postSubConn?.ws?.protocol,
|
||||||
|
hasRelayObj: !!postSubRelay,
|
||||||
|
relayConnected: postSubRelay?.connected,
|
||||||
|
isLoggedIn,
|
||||||
|
isRelayConnected,
|
||||||
|
isSubscribed,
|
||||||
|
isSubscribing
|
||||||
|
});
|
||||||
console.log('✅ Subscription established successfully');
|
console.log('✅ Subscription established successfully');
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -1479,9 +1638,9 @@ function initializeEventRateChart() {
|
|||||||
debug: false // Disable debug logging
|
debug: false // Disable debug logging
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('ASCIIBarChart instance created:', eventRateChart);
|
// console.log('ASCIIBarChart instance created:', eventRateChart);
|
||||||
console.log('Chart container content after init:', chartContainer.textContent);
|
// console.log('Chart container content after init:', chartContainer.textContent);
|
||||||
console.log('Chart container innerHTML after init:', chartContainer.innerHTML);
|
// console.log('Chart container innerHTML after init:', chartContainer.innerHTML);
|
||||||
|
|
||||||
// Force an initial render
|
// Force an initial render
|
||||||
if (eventRateChart && typeof eventRateChart.render === 'function') {
|
if (eventRateChart && typeof eventRateChart.render === 'function') {
|
||||||
@@ -1965,7 +2124,7 @@ async function loadWotStatus() {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
await relayPool.publish([url], signedEvent);
|
await relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
log('WoT status query sent successfully', 'INFO');
|
log('WoT status query sent successfully', 'INFO');
|
||||||
|
|
||||||
@@ -2011,7 +2170,7 @@ async function setWotLevel(level) {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
await relayPool.publish([url], signedEvent);
|
await relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
log(`WoT level set to ${level} - sync will trigger automatically`, 'INFO');
|
log(`WoT level set to ${level} - sync will trigger automatically`, 'INFO');
|
||||||
|
|
||||||
@@ -2060,7 +2219,7 @@ async function syncWot() {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
await relayPool.publish([url], signedEvent);
|
await relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
log('WoT sync command sent', 'INFO');
|
log('WoT sync command sent', 'INFO');
|
||||||
|
|
||||||
@@ -2243,7 +2402,7 @@ async function fetchConfiguration() {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -2268,6 +2427,13 @@ async function fetchConfiguration() {
|
|||||||
// Throw error if all relays failed
|
// Throw error if all relays failed
|
||||||
if (successCount === 0) {
|
if (successCount === 0) {
|
||||||
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
|
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
|
||||||
|
console.warn('❌ ADMIN COMMAND RESULT: FAILED (config_query)', {
|
||||||
|
relayUrl: url,
|
||||||
|
userPubkey,
|
||||||
|
relayPubkey,
|
||||||
|
reason: errorDetails,
|
||||||
|
hint: 'Login can be successful while admin commands fail (wrong admin key, relay-side auth rejection, or command timeout).'
|
||||||
|
});
|
||||||
throw new Error(`All relays rejected the event. Details: ${errorDetails}`);
|
throw new Error(`All relays rejected the event. Details: ${errorDetails}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2281,6 +2447,11 @@ async function fetchConfiguration() {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch configuration:', error);
|
console.error('Failed to fetch configuration:', error);
|
||||||
|
console.warn('❌ ADMIN COMMAND RESULT: FAILED (fetchConfiguration catch)', {
|
||||||
|
userPubkey,
|
||||||
|
relayPubkey,
|
||||||
|
message: error?.message || String(error)
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2488,7 +2659,7 @@ async function sendConfigUpdateCommand(configObjects) {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -2684,7 +2855,7 @@ async function loadAuthRules() {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -2901,7 +3072,7 @@ async function deleteAuthRule(index) {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3329,7 +3500,7 @@ async function addAuthRuleViaWebSocket(ruleData) {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3435,7 +3606,7 @@ async function testGetAuthRules() {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3509,7 +3680,7 @@ async function testClearAuthRules() {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3592,7 +3763,7 @@ async function testAddBlacklist() {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3675,7 +3846,7 @@ async function testAddWhitelist() {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3749,7 +3920,7 @@ async function testConfigQuery() {
|
|||||||
|
|
||||||
// Publish via SimplePool with detailed error diagnostics
|
// Publish via SimplePool with detailed error diagnostics
|
||||||
const url = relayUrl.value.trim();
|
const url = relayUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3819,7 +3990,7 @@ async function testPostEvent() {
|
|||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
logTestEvent('INFO', `Publishing to relay: ${url}`, 'INFO');
|
logTestEvent('INFO', `Publishing to relay: ${url}`, 'INFO');
|
||||||
|
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -3997,7 +4168,7 @@ async function sendNIP17DM() {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedGiftWrap);
|
const publishPromises = relayPool.publish([url], signedGiftWrap, { onauth: (challenge) => handleNip42Auth(url, challenge, 'nip17 dm publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes
|
// Use Promise.allSettled to capture per-relay outcomes
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -4235,7 +4406,7 @@ async function sendRestartCommand() {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes
|
// Use Promise.allSettled to capture per-relay outcomes
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -4286,6 +4457,12 @@ async function sendStatsQuery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Ensure admin subscription/connection is alive before publishing stats command
|
||||||
|
const subscriptionReady = await subscribeToConfiguration();
|
||||||
|
if (!subscriptionReady) {
|
||||||
|
throw new Error('Admin relay subscription is not ready (connection may have timed out)');
|
||||||
|
}
|
||||||
|
|
||||||
updateStatsStatus('loading', 'Querying database...');
|
updateStatsStatus('loading', 'Querying database...');
|
||||||
|
|
||||||
// Create command array for stats query
|
// Create command array for stats query
|
||||||
@@ -4316,7 +4493,28 @@ async function sendStatsQuery() {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const normalizedUrl = normalizeRelayUrlForPool(url);
|
||||||
|
const relayMapKeysBeforePublish = relayPool?.relays ? Array.from(relayPool.relays.keys()) : [];
|
||||||
|
const prePublishConn = relayPool._conn?.[url] || relayPool._conn?.[normalizedUrl];
|
||||||
|
const prePublishRelay = relayPool?.relays?.get?.(normalizedUrl);
|
||||||
|
console.log('🔎 ADMIN PUBLISH DIAG: pre-publish connection snapshot', {
|
||||||
|
url,
|
||||||
|
normalizedUrl,
|
||||||
|
relayMapKeysBeforePublish,
|
||||||
|
hasConn: !!prePublishConn,
|
||||||
|
connType: prePublishConn?.constructor?.name,
|
||||||
|
hasWs: !!prePublishConn?.ws,
|
||||||
|
wsReadyState: prePublishConn?.ws?.readyState,
|
||||||
|
wsUrl: prePublishConn?.ws?.url,
|
||||||
|
wsProtocol: prePublishConn?.ws?.protocol,
|
||||||
|
hasRelayObj: !!prePublishRelay,
|
||||||
|
relayConnected: prePublishRelay?.connected,
|
||||||
|
isLoggedIn,
|
||||||
|
isRelayConnected,
|
||||||
|
isSubscribed,
|
||||||
|
isSubscribing
|
||||||
|
});
|
||||||
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes
|
// Use Promise.allSettled to capture per-relay outcomes
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
@@ -4334,6 +4532,13 @@ async function sendStatsQuery() {
|
|||||||
|
|
||||||
if (successCount === 0) {
|
if (successCount === 0) {
|
||||||
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
|
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
|
||||||
|
console.warn('❌ ADMIN COMMAND RESULT: FAILED (stats_query)', {
|
||||||
|
relayUrl: url,
|
||||||
|
userPubkey,
|
||||||
|
relayPubkey,
|
||||||
|
reason: errorDetails,
|
||||||
|
hint: 'Login can be successful while admin commands fail (wrong admin key, relay-side auth rejection, or command timeout).'
|
||||||
|
});
|
||||||
throw new Error(`All relays rejected stats query event. Details: ${errorDetails}`);
|
throw new Error(`All relays rejected stats query event. Details: ${errorDetails}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4342,6 +4547,11 @@ async function sendStatsQuery() {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Failed to send stats query: ${error.message}`, 'ERROR');
|
log(`Failed to send stats query: ${error.message}`, 'ERROR');
|
||||||
|
console.warn('❌ ADMIN COMMAND RESULT: FAILED (sendStatsQuery catch)', {
|
||||||
|
userPubkey,
|
||||||
|
relayPubkey,
|
||||||
|
message: error?.message || String(error)
|
||||||
|
});
|
||||||
updateStatsStatus('error', error.message);
|
updateStatsStatus('error', error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5499,17 +5709,7 @@ async function sendAdminCommand(commandArray) {
|
|||||||
// Publish via SimplePool with NIP-42 auth support
|
// Publish via SimplePool with NIP-42 auth support
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent, {
|
const publishPromises = relayPool.publish([url], signedEvent, {
|
||||||
async onauth(challenge) {
|
onauth: (challenge) => handleNip42Auth(url, challenge, 'admin publish')
|
||||||
console.log('🔐 NIP-42 AUTH challenge during publish:', challenge);
|
|
||||||
try {
|
|
||||||
const authEvent = window.NostrTools.nip42.makeAuthEvent(url, challenge);
|
|
||||||
const signedAuthEvent = await window.nostr.signEvent(authEvent);
|
|
||||||
console.log('✅ NIP-42 AUTH signed for publish');
|
|
||||||
return signedAuthEvent;
|
|
||||||
} catch (authError) {
|
|
||||||
console.error('NIP-42 AUTH failed during publish:', authError.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes
|
// Use Promise.allSettled to capture per-relay outcomes
|
||||||
@@ -6067,7 +6267,7 @@ async function sendCreateRelayEventCommand(kind, eventData) {
|
|||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayConnectionUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent, { onauth: (challenge) => handleNip42Auth(url, challenge, 'publish') });
|
||||||
|
|
||||||
// Wait for publish results
|
// Wait for publish results
|
||||||
const results = await Promise.allSettled(publishPromises);
|
const results = await Promise.allSettled(publishPromises);
|
||||||
|
|||||||
+95
-1
@@ -3297,8 +3297,18 @@ var NostrTools = (() => {
|
|||||||
this.enablePing = opts.enablePing;
|
this.enablePing = opts.enablePing;
|
||||||
}
|
}
|
||||||
async ensureRelay(url, params) {
|
async ensureRelay(url, params) {
|
||||||
|
const rawUrl = url;
|
||||||
|
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||||
url = normalizeURL(url);
|
url = normalizeURL(url);
|
||||||
let relay = this.relays.get(url);
|
let relay = this.relays.get(url);
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL ensureRelay START", {
|
||||||
|
rawUrl,
|
||||||
|
normalizedUrl: url,
|
||||||
|
hadRelay: !!relay,
|
||||||
|
relayMapKeysBefore: Array.from(this.relays.keys())
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!relay) {
|
if (!relay) {
|
||||||
relay = new AbstractRelay(url, {
|
relay = new AbstractRelay(url, {
|
||||||
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
|
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
|
||||||
@@ -3306,13 +3316,38 @@ var NostrTools = (() => {
|
|||||||
enablePing: this.enablePing
|
enablePing: this.enablePing
|
||||||
});
|
});
|
||||||
relay.onclose = () => {
|
relay.onclose = () => {
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL ensureRelay onclose", {
|
||||||
|
normalizedUrl: url,
|
||||||
|
relayMapKeysBeforeDelete: Array.from(this.relays.keys())
|
||||||
|
});
|
||||||
|
}
|
||||||
this.relays.delete(url);
|
this.relays.delete(url);
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL ensureRelay onclose complete", {
|
||||||
|
normalizedUrl: url,
|
||||||
|
relayMapKeysAfterDelete: Array.from(this.relays.keys())
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (params?.connectionTimeout)
|
if (params?.connectionTimeout)
|
||||||
relay.connectionTimeout = params.connectionTimeout;
|
relay.connectionTimeout = params.connectionTimeout;
|
||||||
this.relays.set(url, relay);
|
this.relays.set(url, relay);
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL ensureRelay created relay", {
|
||||||
|
normalizedUrl: url,
|
||||||
|
relayMapKeysAfterCreate: Array.from(this.relays.keys())
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await relay.connect();
|
await relay.connect();
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL ensureRelay connected", {
|
||||||
|
normalizedUrl: url,
|
||||||
|
relayConnected: relay.connected,
|
||||||
|
relayMapKeysAfterConnect: Array.from(this.relays.keys())
|
||||||
|
});
|
||||||
|
}
|
||||||
return relay;
|
return relay;
|
||||||
}
|
}
|
||||||
close(relays) {
|
close(relays) {
|
||||||
@@ -3334,16 +3369,26 @@ var NostrTools = (() => {
|
|||||||
}
|
}
|
||||||
subscribeMany(relays, filters, params) {
|
subscribeMany(relays, filters, params) {
|
||||||
params.onauth = params.onauth || params.doauth;
|
params.onauth = params.onauth || params.doauth;
|
||||||
|
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||||
const request = [];
|
const request = [];
|
||||||
const uniqUrls = [];
|
const uniqUrls = [];
|
||||||
for (let i2 = 0; i2 < relays.length; i2++) {
|
for (let i2 = 0; i2 < relays.length; i2++) {
|
||||||
const url = normalizeURL(relays[i2]);
|
const url = normalizeURL(relays[i2]);
|
||||||
if (uniqUrls.indexOf(url) === -1) {
|
if (uniqUrls.indexOf(url) === -1) {
|
||||||
|
uniqUrls.push(url);
|
||||||
for (let f2 = 0; f2 < filters.length; f2++) {
|
for (let f2 = 0; f2 < filters.length; f2++) {
|
||||||
request.push({ url, filter: filters[f2] });
|
request.push({ url, filter: filters[f2] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL subscribeMany", {
|
||||||
|
relaysInput: relays,
|
||||||
|
uniqUrls,
|
||||||
|
filtersCount: filters.length,
|
||||||
|
requestsCount: request.length
|
||||||
|
});
|
||||||
|
}
|
||||||
return this.subscribeMap(request, params);
|
return this.subscribeMap(request, params);
|
||||||
}
|
}
|
||||||
subscribeMap(requests, params) {
|
subscribeMap(requests, params) {
|
||||||
@@ -3391,14 +3436,31 @@ var NostrTools = (() => {
|
|||||||
_knownIds.add(id);
|
_knownIds.add(id);
|
||||||
return have;
|
return have;
|
||||||
};
|
};
|
||||||
|
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||||
const allOpened = Promise.all(
|
const allOpened = Promise.all(
|
||||||
requests.map(async ({ url, filter }, i2) => {
|
requests.map(async ({ url, filter }, i2) => {
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL subscribeMap request", {
|
||||||
|
index: i2,
|
||||||
|
url,
|
||||||
|
filterKinds: filter?.kinds,
|
||||||
|
hasAuthorFilter: !!filter?.authors,
|
||||||
|
hasPTagFilter: !!filter?.["#p"]
|
||||||
|
});
|
||||||
|
}
|
||||||
let relay;
|
let relay;
|
||||||
try {
|
try {
|
||||||
relay = await this.ensureRelay(url, {
|
relay = await this.ensureRelay(url, {
|
||||||
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
|
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL subscribeMap ensureRelay FAILED", {
|
||||||
|
index: i2,
|
||||||
|
url,
|
||||||
|
error: err?.message || String(err)
|
||||||
|
});
|
||||||
|
}
|
||||||
handleClose(i2, err?.message || String(err));
|
handleClose(i2, err?.message || String(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3480,18 +3542,50 @@ var NostrTools = (() => {
|
|||||||
return events[0] || null;
|
return events[0] || null;
|
||||||
}
|
}
|
||||||
publish(relays, event, options) {
|
publish(relays, event, options) {
|
||||||
return relays.map(normalizeURL).map(async (url, i2, arr) => {
|
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
|
||||||
|
const normalizedRelays = relays.map(normalizeURL);
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL publish START", {
|
||||||
|
relaysInput: relays,
|
||||||
|
normalizedRelays,
|
||||||
|
eventKind: event?.kind,
|
||||||
|
eventId: event?.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return normalizedRelays.map(async (url, i2, arr) => {
|
||||||
if (arr.indexOf(url) !== i2) {
|
if (arr.indexOf(url) !== i2) {
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL publish duplicate URL", { url, index: i2 });
|
||||||
|
}
|
||||||
return Promise.reject("duplicate url");
|
return Promise.reject("duplicate url");
|
||||||
}
|
}
|
||||||
let r = await this.ensureRelay(url);
|
let r = await this.ensureRelay(url);
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL publish ensured relay", {
|
||||||
|
url,
|
||||||
|
relayConnected: r?.connected,
|
||||||
|
relayMapKeys: Array.from(this.relays.keys())
|
||||||
|
});
|
||||||
|
}
|
||||||
return r.publish(event).catch(async (err) => {
|
return r.publish(event).catch(async (err) => {
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL publish ERROR", {
|
||||||
|
url,
|
||||||
|
error: err?.message || String(err)
|
||||||
|
});
|
||||||
|
}
|
||||||
if (err instanceof Error && err.message.startsWith("auth-required: ") && options?.onauth) {
|
if (err instanceof Error && err.message.startsWith("auth-required: ") && options?.onauth) {
|
||||||
await r.auth(options.onauth);
|
await r.auth(options.onauth);
|
||||||
return r.publish(event);
|
return r.publish(event);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}).then((reason) => {
|
}).then((reason) => {
|
||||||
|
if (debugEnabled) {
|
||||||
|
console.log("🔎 SIMPLE_POOL publish SUCCESS", {
|
||||||
|
url,
|
||||||
|
reason
|
||||||
|
});
|
||||||
|
}
|
||||||
if (this.trackRelays) {
|
if (this.trackRelays) {
|
||||||
let set = this.seenOn.get(event.id);
|
let set = this.seenOn.get(event.id);
|
||||||
if (!set) {
|
if (!set) {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
key,value,data_type,description,category,requires_restart,created_at,updated_at
|
||||||
|
@@ -101,8 +101,8 @@ static const struct {
|
|||||||
|
|
||||||
// Idle Connection Ban Settings
|
// Idle Connection Ban Settings
|
||||||
// Ban IPs that connect but never send REQ or EVENT (idle or early disconnect)
|
// Ban IPs that connect but never send REQ or EVENT (idle or early disconnect)
|
||||||
{"idle_connection_timeout_sec", "30"}, // Seconds before idle connection is closed (0 = disabled)
|
{"idle_connection_timeout_sec", "0"}, // Seconds before idle connection is closed (0 = disabled)
|
||||||
{"idle_ban_enabled", "true"}, // Whether to ban IPs with idle failures
|
{"idle_ban_enabled", "false"}, // Whether to ban IPs with idle failures
|
||||||
{"idle_ban_threshold", "1"}, // Idle failures before ban (1 = ban on first offense)
|
{"idle_ban_threshold", "1"}, // Idle failures before ban (1 = ban on first offense)
|
||||||
{"idle_ban_window_sec", "30"}, // Window to count idle failures in
|
{"idle_ban_window_sec", "30"}, // Window to count idle failures in
|
||||||
{"idle_ban_duration_sec", "300"}, // Initial ban duration (doubles each time, max 24h)
|
{"idle_ban_duration_sec", "300"}, // Initial ban duration (doubles each time, max 24h)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+2
-2
@@ -13,8 +13,8 @@
|
|||||||
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
||||||
#define CRELAY_VERSION_MAJOR 1
|
#define CRELAY_VERSION_MAJOR 1
|
||||||
#define CRELAY_VERSION_MINOR 2
|
#define CRELAY_VERSION_MINOR 2
|
||||||
#define CRELAY_VERSION_PATCH 49
|
#define CRELAY_VERSION_PATCH 50
|
||||||
#define CRELAY_VERSION "v1.2.49"
|
#define CRELAY_VERSION "v1.2.50"
|
||||||
|
|
||||||
// Relay metadata (authoritative source for NIP-11 information)
|
// Relay metadata (authoritative source for NIP-11 information)
|
||||||
#define RELAY_NAME "C-Relay"
|
#define RELAY_NAME "C-Relay"
|
||||||
|
|||||||
+11
-1
@@ -12,6 +12,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "websockets.h"
|
#include "websockets.h"
|
||||||
#include "ip_ban.h"
|
#include "ip_ban.h"
|
||||||
|
|
||||||
@@ -94,8 +95,17 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
|
|||||||
|
|
||||||
// Verify authentication using existing request_validator function
|
// Verify authentication using existing request_validator function
|
||||||
// Note: nostr_nip42_verify_auth_event doesn't extract pubkey, we need to do that separately
|
// Note: nostr_nip42_verify_auth_event doesn't extract pubkey, we need to do that separately
|
||||||
|
char relay_url[RELAY_URL_MAX_LENGTH];
|
||||||
|
const char* configured_relay_url = get_config_value("relay_url");
|
||||||
|
if (configured_relay_url && configured_relay_url[0] != '\0') {
|
||||||
|
snprintf(relay_url, sizeof(relay_url), "%s", configured_relay_url);
|
||||||
|
} else {
|
||||||
|
int relay_port = (g_relay_port > 0) ? g_relay_port : get_config_int("relay_port", DEFAULT_PORT);
|
||||||
|
snprintf(relay_url, sizeof(relay_url), "ws://127.0.0.1:%d", relay_port);
|
||||||
|
}
|
||||||
|
|
||||||
int result = nostr_nip42_verify_auth_event(auth_event, challenge_copy,
|
int result = nostr_nip42_verify_auth_event(auth_event, challenge_copy,
|
||||||
"ws://localhost:8888", 600); // 10 minutes tolerance
|
relay_url, 600); // 10 minutes tolerance
|
||||||
|
|
||||||
char authenticated_pubkey[65] = {0};
|
char authenticated_pubkey[65] = {0};
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ extern struct lws_context *ws_context;
|
|||||||
// Global subscription manager
|
// Global subscription manager
|
||||||
struct subscription_manager g_subscription_manager;
|
struct subscription_manager g_subscription_manager;
|
||||||
|
|
||||||
|
// Actual relay port bound by libwebsockets at runtime (after fallback/retry logic)
|
||||||
|
int g_relay_port = DEFAULT_PORT;
|
||||||
|
|
||||||
// Global connection list for idle connection tracking
|
// Global connection list for idle connection tracking
|
||||||
// Tracks ALL WebSocket connections (not just subscribed ones)
|
// Tracks ALL WebSocket connections (not just subscribed ones)
|
||||||
// so the periodic timer can find and close idle connections
|
// so the periodic timer can find and close idle connections
|
||||||
@@ -2609,6 +2612,9 @@ int start_websocket_relay(int port_override, int strict_port) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist the actual bound port for components that need the runtime relay URL
|
||||||
|
g_relay_port = actual_port;
|
||||||
|
|
||||||
char startup_msg[256];
|
char startup_msg[256];
|
||||||
if (actual_port != configured_port) {
|
if (actual_port != configured_port) {
|
||||||
snprintf(startup_msg, sizeof(startup_msg),
|
snprintf(startup_msg, sizeof(startup_msg),
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ struct per_session_data {
|
|||||||
// Get current active WebSocket connection count
|
// Get current active WebSocket connection count
|
||||||
int get_active_connection_count(void);
|
int get_active_connection_count(void);
|
||||||
|
|
||||||
|
// Actual relay port bound by libwebsockets at runtime (after fallback/retry logic)
|
||||||
|
extern int g_relay_port;
|
||||||
|
|
||||||
// NIP-11 HTTP session data structure for managing buffer lifetime
|
// NIP-11 HTTP session data structure for managing buffer lifetime
|
||||||
struct nip11_session_data {
|
struct nip11_session_data {
|
||||||
int type; // 0 for NIP-11
|
int type; // 0 for NIP-11
|
||||||
|
|||||||
Reference in New Issue
Block a user