|
|
|
@@ -51,6 +51,12 @@ let pendingSqlQueries = new Map();
|
|
|
|
|
let eventRateChart = null;
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
// DOM elements
|
|
|
|
@@ -85,6 +91,47 @@ function log(message, type = 'INFO') {
|
|
|
|
|
// 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
|
|
|
|
|
function log(message, type = 'INFO') {
|
|
|
|
|
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
|
|
|
@@ -237,9 +284,38 @@ async function checkExistingAuthWithRetries() {
|
|
|
|
|
console.log('Trying window.NOSTR_LOGIN_LITE.getAuthState()...');
|
|
|
|
|
const authState = window.NOSTR_LOGIN_LITE.getAuthState();
|
|
|
|
|
if (authState && authState.pubkey) {
|
|
|
|
|
console.log('✅ Auth state found via NOSTR_LOGIN_LITE.getAuthState():', authState.pubkey);
|
|
|
|
|
await restoreAuthenticationState(authState.pubkey);
|
|
|
|
|
return true;
|
|
|
|
|
// 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('✅ LOGIN ATTEMPT RESULT: SUCCESS (restored NOSTR_LOGIN_LITE auth state)', {
|
|
|
|
|
method: authState.method || 'unknown',
|
|
|
|
|
pubkey: authState.pubkey
|
|
|
|
|
});
|
|
|
|
|
await restoreAuthenticationState(authState.pubkey);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -249,6 +325,10 @@ async function checkExistingAuthWithRetries() {
|
|
|
|
|
const pubkey = await nlLite.getPublicKey();
|
|
|
|
|
if (pubkey && pubkey.length === 64) {
|
|
|
|
|
console.log('✅ Pubkey found via nlLite.getPublicKey():', pubkey);
|
|
|
|
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (restored nlLite session)', {
|
|
|
|
|
method: 'restored-nlLite',
|
|
|
|
|
pubkey
|
|
|
|
|
});
|
|
|
|
|
await restoreAuthenticationState(pubkey);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@@ -260,6 +340,10 @@ async function checkExistingAuthWithRetries() {
|
|
|
|
|
const pubkey = await window.nostr.getPublicKey();
|
|
|
|
|
if (pubkey && pubkey.length === 64) {
|
|
|
|
|
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);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@@ -272,6 +356,10 @@ async function checkExistingAuthWithRetries() {
|
|
|
|
|
const parsedData = JSON.parse(localStorageData);
|
|
|
|
|
if (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);
|
|
|
|
|
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');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
@@ -314,7 +403,12 @@ async function restoreAuthenticationState(pubkey) {
|
|
|
|
|
await setupAutomaticRelayConnection(false);
|
|
|
|
|
|
|
|
|
|
// 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');
|
|
|
|
|
}
|
|
|
|
@@ -447,7 +541,7 @@ async function initializeApp() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle authentication events
|
|
|
|
|
function handleAuthEvent(event) {
|
|
|
|
|
async function handleAuthEvent(event) {
|
|
|
|
|
const { pubkey, method, error } = event.detail;
|
|
|
|
|
|
|
|
|
|
if (method && pubkey) {
|
|
|
|
@@ -455,20 +549,34 @@ function handleAuthEvent(event) {
|
|
|
|
|
isLoggedIn = true;
|
|
|
|
|
console.log(`Login successful! Method: ${method}`);
|
|
|
|
|
console.log(`Public key: ${pubkey}`);
|
|
|
|
|
console.log('✅ LOGIN ATTEMPT RESULT: SUCCESS (interactive login)', { method, pubkey });
|
|
|
|
|
|
|
|
|
|
// Hide login modal and show profile in header
|
|
|
|
|
hideLoginModal();
|
|
|
|
|
showProfileInHeader();
|
|
|
|
|
loadUserProfile();
|
|
|
|
|
|
|
|
|
|
console.log('🔎 POST-LOGIN DEBUG: Starting admin verification flow', {
|
|
|
|
|
method,
|
|
|
|
|
pubkey,
|
|
|
|
|
isLoggedIn,
|
|
|
|
|
isRelayConnected
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
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) {
|
|
|
|
|
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
|
|
|
|
|
async function verifyAdminAccess() {
|
|
|
|
|
console.log('=== VERIFYING ADMIN ACCESS (BYPASS MODE) ===');
|
|
|
|
|
async function verifyAdminAccess(source = 'unknown') {
|
|
|
|
|
console.log('=== VERIFYING ADMIN ACCESS (BYPASS MODE) ===', { source, isLoggedIn, isRelayConnected });
|
|
|
|
|
|
|
|
|
|
if (!isLoggedIn || !isRelayConnected) {
|
|
|
|
|
console.log('Cannot verify admin access - not logged in or not connected to relay');
|
|
|
|
|
showAccessDeniedOverlay();
|
|
|
|
|
return;
|
|
|
|
|
if (!isLoggedIn) {
|
|
|
|
|
const reason = 'User is not logged in';
|
|
|
|
|
console.warn('❌ Admin check failed:', reason, { source });
|
|
|
|
|
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
|
|
|
|
|
// TODO: Re-enable once idle_connection_timeout_sec is increased to 120+
|
|
|
|
|
pendingAdminVerification = false;
|
|
|
|
|
isAdminVerified = true;
|
|
|
|
|
hideAccessDeniedOverlay();
|
|
|
|
|
hideAdminVerificationLoading();
|
|
|
|
|
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
|
|
|
|
@@ -749,6 +875,20 @@ async function loadUserProfile() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
const profilePool = new window.NostrTools.SimplePool();
|
|
|
|
|
const relays = ['wss://relay.damus.io',
|
|
|
|
@@ -975,40 +1115,17 @@ function attachWebSocketMonitoring(relayPool, url) {
|
|
|
|
|
|
|
|
|
|
// SimplePool stores connections in _conn object
|
|
|
|
|
if (relayPool && relayPool._conn) {
|
|
|
|
|
// Monitor when connections are created
|
|
|
|
|
const originalGetConnection = relayPool._conn[url];
|
|
|
|
|
if (originalGetConnection) {
|
|
|
|
|
// Diagnostics only: inspect existing connection without mutating _conn internals
|
|
|
|
|
const existingConn = relayPool._conn[url];
|
|
|
|
|
if (existingConn) {
|
|
|
|
|
console.log('📡 Found existing connection for URL:', url);
|
|
|
|
|
|
|
|
|
|
// Try to access the WebSocket if it's available
|
|
|
|
|
const conn = relayPool._conn[url];
|
|
|
|
|
if (conn && conn.ws) {
|
|
|
|
|
attachWebSocketEventListeners(conn.ws, url);
|
|
|
|
|
if (existingConn.ws && !existingConn.ws._monitored) {
|
|
|
|
|
attachWebSocketEventListeners(existingConn.ws, url);
|
|
|
|
|
existingConn.ws._monitored = true;
|
|
|
|
|
}
|
|
|
|
|
} 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');
|
|
|
|
@@ -1160,7 +1277,32 @@ async function subscribeToConfiguration() {
|
|
|
|
|
console.log(`📝 Generated subscription ID: ${subscriptionId}`);
|
|
|
|
|
console.log(`👤 User pubkey: ${userPubkey}`);
|
|
|
|
|
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('🔎 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
|
|
|
|
|
isSubscribed = true;
|
|
|
|
@@ -1310,42 +1452,38 @@ async function subscribeToConfiguration() {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async onauth(challenge) {
|
|
|
|
|
// NIP-42 authentication: relay sent AUTH challenge, sign and respond
|
|
|
|
|
console.log('🔐 NIP-42 AUTH challenge received:', challenge);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
const authUrl = relayConnectionUrl.value.trim();
|
|
|
|
|
return handleNip42Auth(authUrl, challenge, 'subscription');
|
|
|
|
|
},
|
|
|
|
|
onclose(reason) {
|
|
|
|
|
console.log('Subscription closed:', reason);
|
|
|
|
|
// Only reset state if the admin relay itself closed (not external relays timing out)
|
|
|
|
|
// reason is an array of close reasons, one per relay in the subscription
|
|
|
|
|
const adminUrl = relayConnectionUrl.value.trim();
|
|
|
|
|
const closeNormalizedUrl = normalizeRelayUrlForPool(url);
|
|
|
|
|
const closeConn = relayPool?._conn?.[url] || relayPool?._conn?.[closeNormalizedUrl];
|
|
|
|
|
const closeRelay = relayPool?.relays?.get?.(closeNormalizedUrl);
|
|
|
|
|
const reasons = Array.isArray(reason) ? reason : [reason];
|
|
|
|
|
// Check if the admin relay's connection closed (not just external relays timing out)
|
|
|
|
|
// If all reasons are 'connection timed out', these are external relays, not our relay
|
|
|
|
|
const allTimeout = reasons.every(r => r === 'connection timed out');
|
|
|
|
|
if (!allTimeout) {
|
|
|
|
|
// Admin relay actually closed — reset state
|
|
|
|
|
isSubscribed = false;
|
|
|
|
|
isSubscribing = false;
|
|
|
|
|
isRelayConnected = false;
|
|
|
|
|
updateConfigStatus(false);
|
|
|
|
|
log('WebSocket connection closed - subscription state reset', 'WARNING');
|
|
|
|
|
} else {
|
|
|
|
|
// External relays timed out — keep admin relay state intact
|
|
|
|
|
log('External relay connections timed out (admin relay still connected)', 'INFO');
|
|
|
|
|
}
|
|
|
|
|
console.log('Subscription closed:', reason);
|
|
|
|
|
console.log('🔎 ADMIN SUB DIAG: onclose details', {
|
|
|
|
|
url,
|
|
|
|
|
closeNormalizedUrl,
|
|
|
|
|
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;
|
|
|
|
|
isSubscribing = false;
|
|
|
|
|
isRelayConnected = false;
|
|
|
|
|
updateConfigStatus(false);
|
|
|
|
|
log('Admin relay subscription closed/timed out - connection state reset', 'WARNING');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@@ -1356,6 +1494,27 @@ async function subscribeToConfiguration() {
|
|
|
|
|
isSubscribed = true;
|
|
|
|
|
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');
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
@@ -1479,9 +1638,9 @@ function initializeEventRateChart() {
|
|
|
|
|
debug: false // Disable debug logging
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('ASCIIBarChart instance created:', eventRateChart);
|
|
|
|
|
console.log('Chart container content after init:', chartContainer.textContent);
|
|
|
|
|
console.log('Chart container innerHTML after init:', chartContainer.innerHTML);
|
|
|
|
|
// console.log('ASCIIBarChart instance created:', eventRateChart);
|
|
|
|
|
// console.log('Chart container content after init:', chartContainer.textContent);
|
|
|
|
|
// console.log('Chart container innerHTML after init:', chartContainer.innerHTML);
|
|
|
|
|
|
|
|
|
|
// Force an initial render
|
|
|
|
|
if (eventRateChart && typeof eventRateChart.render === 'function') {
|
|
|
|
@@ -1965,7 +2124,7 @@ async function loadWotStatus() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
@@ -2011,7 +2170,7 @@ async function setWotLevel(level) {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
@@ -2060,7 +2219,7 @@ async function syncWot() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
@@ -2243,7 +2402,7 @@ async function fetchConfiguration() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -2268,6 +2427,13 @@ async function fetchConfiguration() {
|
|
|
|
|
// Throw error if all relays failed
|
|
|
|
|
if (successCount === 0) {
|
|
|
|
|
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}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2281,6 +2447,11 @@ async function fetchConfiguration() {
|
|
|
|
|
|
|
|
|
|
} catch (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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -2488,7 +2659,7 @@ async function sendConfigUpdateCommand(configObjects) {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -2684,7 +2855,7 @@ async function loadAuthRules() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -2901,7 +3072,7 @@ async function deleteAuthRule(index) {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3329,7 +3500,7 @@ async function addAuthRuleViaWebSocket(ruleData) {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3435,7 +3606,7 @@ async function testGetAuthRules() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3509,7 +3680,7 @@ async function testClearAuthRules() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3592,7 +3763,7 @@ async function testAddBlacklist() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3675,7 +3846,7 @@ async function testAddWhitelist() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3749,7 +3920,7 @@ async function testConfigQuery() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool with detailed error diagnostics
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3819,7 +3990,7 @@ async function testPostEvent() {
|
|
|
|
|
const url = relayConnectionUrl.value.trim();
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -3997,7 +4168,7 @@ async function sendNIP17DM() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -4235,7 +4406,7 @@ async function sendRestartCommand() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -4286,6 +4457,12 @@ async function sendStatsQuery() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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...');
|
|
|
|
|
|
|
|
|
|
// Create command array for stats query
|
|
|
|
@@ -4316,7 +4493,28 @@ async function sendStatsQuery() {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
@@ -4334,6 +4532,13 @@ async function sendStatsQuery() {
|
|
|
|
|
|
|
|
|
|
if (successCount === 0) {
|
|
|
|
|
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}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -4342,6 +4547,11 @@ async function sendStatsQuery() {
|
|
|
|
|
|
|
|
|
|
} catch (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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -5499,17 +5709,7 @@ async function sendAdminCommand(commandArray) {
|
|
|
|
|
// Publish via SimplePool with NIP-42 auth support
|
|
|
|
|
const url = relayConnectionUrl.value.trim();
|
|
|
|
|
const publishPromises = relayPool.publish([url], signedEvent, {
|
|
|
|
|
async onauth(challenge) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onauth: (challenge) => handleNip42Auth(url, challenge, 'admin publish')
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Use Promise.allSettled to capture per-relay outcomes
|
|
|
|
@@ -6067,7 +6267,7 @@ async function sendCreateRelayEventCommand(kind, eventData) {
|
|
|
|
|
|
|
|
|
|
// Publish via SimplePool
|
|
|
|
|
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
|
|
|
|
|
const results = await Promise.allSettled(publishPromises);
|
|
|
|
|