diff --git a/src/lib/components/EventInput.svelte b/src/lib/components/EventInput.svelte index 1b9cb6b..cc2e2a3 100644 --- a/src/lib/components/EventInput.svelte +++ b/src/lib/components/EventInput.svelte @@ -16,6 +16,7 @@ import { get } from "svelte/store"; import { ndkInstance } from "$lib/ndk"; import { userPubkey } from "$lib/stores/authStore.Svelte"; + import { userStore } from "$lib/stores/userStore"; import { NDKEvent as NDKEventClass } from "@nostr-dev-kit/ndk"; import type { NDKEvent } from "$lib/utils/nostrUtils"; import { prefixNostrAddresses } from "$lib/utils/nostrUtils"; @@ -99,8 +100,12 @@ function validate(): { valid: boolean; reason?: string } { const currentUserPubkey = get(userPubkey as any); - if (!currentUserPubkey) return { valid: false, reason: "Not logged in." }; - const pubkey = String(currentUserPubkey); + const userState = get(userStore); + + // Try userPubkey first, then fallback to userStore + const pubkey = currentUserPubkey || userState.pubkey; + if (!pubkey) return { valid: false, reason: "Not logged in." }; + if (!content.trim()) return { valid: false, reason: "Content required." }; if (kind === 30023) { const v = validateNotAsciidoc(content); @@ -137,14 +142,18 @@ try { const ndk = get(ndkInstance); const currentUserPubkey = get(userPubkey as any); - if (!ndk || !currentUserPubkey) { + const userState = get(userStore); + + // Try userPubkey first, then fallback to userStore + const pubkey = currentUserPubkey || userState.pubkey; + if (!ndk || !pubkey) { error = "NDK or pubkey missing."; loading = false; return; } - const pubkey = String(currentUserPubkey); + const pubkeyString = String(pubkey); - if (!/^[a-fA-F0-9]{64}$/.test(pubkey)) { + if (!/^[a-fA-F0-9]{64}$/.test(pubkeyString)) { error = "Invalid public key: must be a 64-character hex string."; loading = false; return; @@ -158,7 +167,7 @@ return; } - const baseEvent = { pubkey, created_at: createdAt }; + const baseEvent = { pubkey: pubkeyString, created_at: createdAt }; let events: NDKEvent[] = []; console.log("Publishing event with kind:", kind); @@ -235,7 +244,7 @@ kind, content: prefixedContent, tags: eventTags, - pubkey, + pubkey: pubkeyString, created_at: createdAt, }; @@ -520,7 +529,7 @@ Event ID: {lastPublishedEventId} diff --git a/src/lib/components/EventSearch.svelte b/src/lib/components/EventSearch.svelte index cb71ade..10f888b 100644 --- a/src/lib/components/EventSearch.svelte +++ b/src/lib/components/EventSearch.svelte @@ -13,6 +13,8 @@ import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk"; import { getMatchingTags, toNpub } from "$lib/utils/nostrUtils"; import type { SearchResult } from '$lib/utils/search_types'; + import { userStore } from "$lib/stores/userStore"; + import { get } from "svelte/store"; // Props definition let { @@ -492,7 +494,7 @@ // Wait for relays to be available (with timeout) let retryCount = 0; - const maxRetries = 10; // Wait up to 5 seconds (10 * 500ms) + const maxRetries = 20; // Wait up to 10 seconds (20 * 500ms) for user login to complete while ($activeInboxRelays.length === 0 && $activeOutboxRelays.length === 0 && retryCount < maxRetries) { console.debug(`EventSearch: Waiting for relays... (attempt ${retryCount + 1}/${maxRetries})`); @@ -500,6 +502,19 @@ retryCount++; } + // Additional wait for user-specific relays if user is logged in + const currentUser = get(userStore); + if (currentUser.signedIn && currentUser.pubkey) { + console.debug(`EventSearch: User is logged in (${currentUser.pubkey}), waiting for user-specific relays...`); + retryCount = 0; + while ($activeOutboxRelays.length <= 9 && retryCount < maxRetries) { + // If we still have the default relay count (9), wait for user-specific relays + console.debug(`EventSearch: Waiting for user-specific relays... (attempt ${retryCount + 1}/${maxRetries})`); + await new Promise(resolve => setTimeout(resolve, 500)); + retryCount++; + } + } + // Check if we have any relays available if ($activeInboxRelays.length === 0 && $activeOutboxRelays.length === 0) { console.warn("EventSearch: No relays available after waiting, failing search"); diff --git a/src/lib/components/util/Profile.svelte b/src/lib/components/util/Profile.svelte index ad52084..cc5ff4a 100644 --- a/src/lib/components/util/Profile.svelte +++ b/src/lib/components/util/Profile.svelte @@ -114,12 +114,24 @@ console.log("Profile component - activeInboxRelays:", inboxRelays); }); - // Manual trigger to refresh profile when user signs in + // Track if we've already refreshed the profile for this session + let hasRefreshedProfile = $state(false); + + // Reset the refresh flag when user logs out + $effect(() => { + const currentUser = userState; + if (!currentUser.signedIn) { + hasRefreshedProfile = false; + } + }); + + // Manual trigger to refresh profile when user signs in (only once) $effect(() => { const currentUser = userState; - if (currentUser.signedIn && currentUser.npub && !isRefreshingProfile) { + if (currentUser.signedIn && currentUser.npub && !isRefreshingProfile && !hasRefreshedProfile) { console.log("Profile: User signed in, triggering profile refresh..."); + hasRefreshedProfile = true; // Add a small delay to ensure relays are ready setTimeout(() => { refreshProfile(); @@ -131,12 +143,13 @@ $effect(() => { const currentUser = userState; - if (currentUser.signedIn && currentUser.npub && currentUser.loginMethod) { + if (currentUser.signedIn && currentUser.npub && currentUser.loginMethod && !isRefreshingProfile) { console.log("Profile: Login method detected:", currentUser.loginMethod); // If switching to read-only mode (npub), refresh profile - if (currentUser.loginMethod === "npub" && !isRefreshingProfile) { + if (currentUser.loginMethod === "npub" && !hasRefreshedProfile) { console.log("Profile: Switching to read-only mode, refreshing profile..."); + hasRefreshedProfile = true; setTimeout(() => { refreshProfile(); }, 500); @@ -150,12 +163,13 @@ $effect(() => { const currentUser = userState; - if (currentUser.signedIn && currentUser.loginMethod !== previousLoginMethod) { + if (currentUser.signedIn && currentUser.loginMethod !== previousLoginMethod && !isRefreshingProfile) { console.log("Profile: Login method changed from", previousLoginMethod, "to", currentUser.loginMethod); // If switching from Amber to npub (read-only), refresh profile - if (previousLoginMethod === "amber" && currentUser.loginMethod === "npub") { + if (previousLoginMethod === "amber" && currentUser.loginMethod === "npub" && !hasRefreshedProfile) { console.log("Profile: Switching from Amber to read-only mode, refreshing profile..."); + hasRefreshedProfile = true; setTimeout(() => { refreshProfile(); }, 1000); diff --git a/src/lib/ndk.ts b/src/lib/ndk.ts index 6e1120e..7b9745e 100644 --- a/src/lib/ndk.ts +++ b/src/lib/ndk.ts @@ -386,10 +386,13 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { */ export async function getActiveRelaySet(ndk: NDK): Promise<{ inboxRelays: string[]; outboxRelays: string[] }> { const user = get(userStore); + console.debug('[NDK.ts] getActiveRelaySet: User state:', { signedIn: user.signedIn, hasNdkUser: !!user.ndkUser, pubkey: user.pubkey }); if (user.signedIn && user.ndkUser) { + console.debug('[NDK.ts] getActiveRelaySet: Building relay set for authenticated user:', user.ndkUser.pubkey); return await buildCompleteRelaySet(ndk, user.ndkUser); } else { + console.debug('[NDK.ts] getActiveRelaySet: Building relay set for anonymous user'); return await buildCompleteRelaySet(ndk, null); } } @@ -400,25 +403,33 @@ export async function getActiveRelaySet(ndk: NDK): Promise<{ inboxRelays: string */ export async function updateActiveRelayStores(ndk: NDK): Promise { try { + console.debug('[NDK.ts] updateActiveRelayStores: Starting relay store update'); + // Get the active relay set from the relay management system const relaySet = await getActiveRelaySet(ndk); + console.debug('[NDK.ts] updateActiveRelayStores: Got relay set:', relaySet); // Update the stores with the new relay configuration activeInboxRelays.set(relaySet.inboxRelays); activeOutboxRelays.set(relaySet.outboxRelays); + console.debug('[NDK.ts] updateActiveRelayStores: Updated stores with inbox:', relaySet.inboxRelays.length, 'outbox:', relaySet.outboxRelays.length); // Add relays to NDK pool (deduplicated) const allRelayUrls = deduplicateRelayUrls([...relaySet.inboxRelays, ...relaySet.outboxRelays]); + console.debug('[NDK.ts] updateActiveRelayStores: Adding', allRelayUrls.length, 'relays to NDK pool'); + for (const url of allRelayUrls) { try { const relay = createRelayWithAuth(url, ndk); ndk.pool?.addRelay(relay); } catch (error) { - // Silently ignore relay addition failures + console.debug('[NDK.ts] updateActiveRelayStores: Failed to add relay', url, ':', error); } } + + console.debug('[NDK.ts] updateActiveRelayStores: Relay store update completed'); } catch (error) { - // Silently ignore relay store update errors + console.warn('[NDK.ts] updateActiveRelayStores: Error updating relay stores:', error); } } diff --git a/src/lib/stores/userStore.ts b/src/lib/stores/userStore.ts index 4e2b067..376367c 100644 --- a/src/lib/stores/userStore.ts +++ b/src/lib/stores/userStore.ts @@ -8,9 +8,10 @@ import { NDKRelay, } from "@nostr-dev-kit/ndk"; import { getUserMetadata } from "$lib/utils/nostrUtils"; -import { ndkInstance, activeInboxRelays, activeOutboxRelays } from "$lib/ndk"; +import { ndkInstance, activeInboxRelays, activeOutboxRelays, updateActiveRelayStores } from "$lib/ndk"; import { loginStorageKey } from "$lib/consts"; import { nip19 } from "nostr-tools"; +import { userPubkey } from "$lib/stores/authStore.Svelte"; export interface UserState { pubkey: string | null; @@ -209,6 +210,15 @@ export async function loginWithExtension() { console.log("Login with extension - setting userStore with:", userState); userStore.set(userState); + + // Update relay stores with the new user's relays + try { + console.debug('[userStore.ts] loginWithExtension: Updating relay stores for authenticated user'); + await updateActiveRelayStores(ndk); + } catch (error) { + console.warn('[userStore.ts] loginWithExtension: Failed to update relay stores:', error); + } + clearLogin(); localStorage.removeItem("alexandria/logout/flag"); persistLogin(user, "extension"); @@ -266,6 +276,16 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) { console.log("Login with Amber - setting userStore with:", userState); userStore.set(userState); + userPubkey.set(user.pubkey); + + // Update relay stores with the new user's relays + try { + console.debug('[userStore.ts] loginWithAmber: Updating relay stores for authenticated user'); + await updateActiveRelayStores(ndk); + } catch (error) { + console.warn('[userStore.ts] loginWithAmber: Failed to update relay stores:', error); + } + clearLogin(); localStorage.removeItem("alexandria/logout/flag"); persistLogin(user, "amber"); @@ -330,6 +350,16 @@ export async function loginWithNpub(pubkeyOrNpub: string) { console.log("Login with npub - setting userStore with:", userState); userStore.set(userState); + userPubkey.set(user.pubkey); + + // Update relay stores with the new user's relays + try { + console.debug('[userStore.ts] loginWithNpub: Updating relay stores for authenticated user'); + await updateActiveRelayStores(ndk); + } catch (error) { + console.warn('[userStore.ts] loginWithNpub: Failed to update relay stores:', error); + } + clearLogin(); localStorage.removeItem("alexandria/logout/flag"); persistLogin(user, "npub"); @@ -393,6 +423,7 @@ export function logoutUser() { signer: null, signedIn: false, }); + userPubkey.set(null); const ndk = get(ndkInstance); if (ndk) { diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index f70d139..da8bb1b 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -5,7 +5,7 @@ import { npubCache } from "./npubCache"; import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk"; import type { NDKFilter, NDKKind } from "@nostr-dev-kit/ndk"; import { communityRelays, secondaryRelays, anonymousRelays } from "$lib/consts"; -import { activeInboxRelays } from "$lib/ndk"; +import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk"; import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk"; import { sha256 } from "@noble/hashes/sha256"; import { schnorr } from "@noble/curves/secp256k1"; @@ -439,19 +439,22 @@ export async function fetchEventWithFallback( filterOrId: string | NDKFilter, timeoutMs: number = 3000, ): Promise { - // Use the active inbox relays from the relay management system + // Use both inbox and outbox relays for better event discovery const inboxRelays = get(activeInboxRelays); + const outboxRelays = get(activeOutboxRelays); + const allRelays = [...(inboxRelays || []), ...(outboxRelays || [])]; console.log("fetchEventWithFallback: Using inbox relays:", inboxRelays); + console.log("fetchEventWithFallback: Using outbox relays:", outboxRelays); // Check if we have any relays available - if (inboxRelays.length === 0) { - console.warn("fetchEventWithFallback: No inbox relays available for event fetch"); + if (allRelays.length === 0) { + console.warn("fetchEventWithFallback: No relays available for event fetch"); return null; } - // Create relay set from active inbox relays - const relaySet = NDKRelaySetFromNDK.fromRelayUrls(inboxRelays, ndk); + // Create relay set from all available relays + const relaySet = NDKRelaySetFromNDK.fromRelayUrls(allRelays, ndk); try { if (relaySet.relays.size === 0) { diff --git a/src/lib/utils/relay_management.ts b/src/lib/utils/relay_management.ts index 09aa5ac..2787938 100644 --- a/src/lib/utils/relay_management.ts +++ b/src/lib/utils/relay_management.ts @@ -287,6 +287,7 @@ export async function getUserBlockedRelays(ndk: NDK, user: NDKUser): Promise { try { + console.debug('[relay_management.ts] Fetching outbox relays for user:', user.pubkey); const relayList = await ndk.fetchEvent( { kinds: [10002], @@ -300,16 +301,29 @@ export async function getUserOutboxRelays(ndk: NDK, user: NDKUser): Promise { + console.debug('[relay_management.ts] Processing tag:', tag); if (tag[0] === 'w' && tag[1]) { outboxRelays.push(tag[1]); + console.debug('[relay_management.ts] Added outbox relay:', tag[1]); + } else if (tag[0] === 'r' && tag[1]) { + // Some relay lists use 'r' for both inbox and outbox + outboxRelays.push(tag[1]); + console.debug('[relay_management.ts] Added relay (r tag):', tag[1]); + } else { + console.debug('[relay_management.ts] Skipping tag:', tag[0], 'value:', tag[1]); } }); + console.debug('[relay_management.ts] Final outbox relays:', outboxRelays); return outboxRelays; } catch (error) { console.info('[relay_management.ts] Error fetching user outbox relays:', error); @@ -317,6 +331,56 @@ export async function getUserOutboxRelays(ndk: NDK, user: NDKUser): Promise { + try { + // Check if we're in a browser environment with extension support + if (typeof window === 'undefined' || !window.nostr) { + console.debug('[relay_management.ts] No window.nostr available'); + return []; + } + + console.debug('[relay_management.ts] Extension available, checking for getRelays()'); + const extensionRelays: string[] = []; + + // Try to get relays from the extension's API + // Different extensions may expose their relay config differently + if (window.nostr.getRelays) { + console.debug('[relay_management.ts] getRelays() method found, calling it...'); + try { + const relays = await window.nostr.getRelays(); + console.debug('[relay_management.ts] getRelays() returned:', relays); + if (relays && typeof relays === 'object') { + // Convert relay object to array of URLs + const relayUrls = Object.keys(relays); + extensionRelays.push(...relayUrls); + console.debug('[relay_management.ts] Got relays from extension:', relayUrls); + } + } catch (error) { + console.debug('[relay_management.ts] Extension getRelays() failed:', error); + } + } else { + console.debug('[relay_management.ts] getRelays() method not found on window.nostr'); + } + + // If getRelays() didn't work, try alternative methods + if (extensionRelays.length === 0) { + // Some extensions might expose relays through other methods + // This is a fallback for extensions that don't expose getRelays() + console.debug('[relay_management.ts] Extension does not expose relay configuration'); + } + + console.debug('[relay_management.ts] Final extension relays:', extensionRelays); + return extensionRelays; + } catch (error) { + console.debug('[relay_management.ts] Error getting extension relays:', error); + return []; + } +} + /** * Tests a set of relays in batches to avoid overwhelming them * @param relayUrls Array of relay URLs to test @@ -361,37 +425,55 @@ export async function buildCompleteRelaySet( ndk: NDK, user: NDKUser | null ): Promise<{ inboxRelays: string[]; outboxRelays: string[] }> { + console.debug('[relay_management.ts] buildCompleteRelaySet: Starting with user:', user?.pubkey || 'null'); + // Discover local relays first const discoveredLocalRelays = await discoverLocalRelays(ndk); + console.debug('[relay_management.ts] buildCompleteRelaySet: Discovered local relays:', discoveredLocalRelays); // Get user-specific relays if available let userOutboxRelays: string[] = []; let userLocalRelays: string[] = []; let blockedRelays: string[] = []; + let extensionRelays: string[] = []; if (user) { + console.debug('[relay_management.ts] buildCompleteRelaySet: Fetching user-specific relays for:', user.pubkey); + try { userOutboxRelays = await getUserOutboxRelays(ndk, user); + console.debug('[relay_management.ts] buildCompleteRelaySet: User outbox relays:', userOutboxRelays); } catch (error) { console.debug('[relay_management.ts] Error fetching user outbox relays:', error); } try { userLocalRelays = await getUserLocalRelays(ndk, user); + console.debug('[relay_management.ts] buildCompleteRelaySet: User local relays:', userLocalRelays); } catch (error) { console.debug('[relay_management.ts] Error fetching user local relays:', error); } try { blockedRelays = await getUserBlockedRelays(ndk, user); + console.debug('[relay_management.ts] buildCompleteRelaySet: User blocked relays:', blockedRelays); } catch (error) { // Silently ignore blocked relay fetch errors } + + try { + extensionRelays = await getExtensionRelays(); + console.debug('[relay_management.ts] Extension relays gathered:', extensionRelays); + } catch (error) { + console.debug('[relay_management.ts] Error fetching extension relays:', error); + } + } else { + console.debug('[relay_management.ts] buildCompleteRelaySet: No user provided, skipping user-specific relays'); } // Build initial relay sets and deduplicate const finalInboxRelays = deduplicateRelayUrls([...discoveredLocalRelays, ...userLocalRelays]); - const finalOutboxRelays = deduplicateRelayUrls([...discoveredLocalRelays, ...userOutboxRelays]); + const finalOutboxRelays = deduplicateRelayUrls([...discoveredLocalRelays, ...userOutboxRelays, ...extensionRelays]); // Test relays and filter out non-working ones let testedInboxRelays: string[] = []; @@ -441,5 +523,9 @@ export async function buildCompleteRelaySet( }; } + console.debug('[relay_management.ts] buildCompleteRelaySet: Final relay sets - inbox:', finalRelaySet.inboxRelays.length, 'outbox:', finalRelaySet.outboxRelays.length); + console.debug('[relay_management.ts] buildCompleteRelaySet: Final inbox relays:', finalRelaySet.inboxRelays); + console.debug('[relay_management.ts] buildCompleteRelaySet: Final outbox relays:', finalRelaySet.outboxRelays); + return finalRelaySet; } \ No newline at end of file