From fd3b26741da7404e14912f58b8da40ec809b3c5c Mon Sep 17 00:00:00 2001 From: silberengel Date: Fri, 18 Jul 2025 22:21:19 +0200 Subject: [PATCH] login working --- src/lib/components/util/Profile.svelte | 172 +++++++++++++++++++------ src/lib/stores/userStore.ts | 73 +++++++++-- src/lib/utils/nostrUtils.ts | 35 ++++- 3 files changed, 224 insertions(+), 56 deletions(-) diff --git a/src/lib/components/util/Profile.svelte b/src/lib/components/util/Profile.svelte index 399539a..ad52084 100644 --- a/src/lib/components/util/Profile.svelte +++ b/src/lib/components/util/Profile.svelte @@ -20,6 +20,8 @@ import { goto } from "$app/navigation"; import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; import { onMount } from "svelte"; + import { getUserMetadata } from "$lib/utils/nostrUtils"; + import { activeInboxRelays } from "$lib/ndk"; let { pubkey, isNav = false } = $props<{ pubkey?: string, isNav?: boolean }>(); @@ -35,7 +37,7 @@ let profileAvatarId = "profile-avatar-btn"; let showAmberFallback = $state(false); let fallbackCheckInterval: ReturnType | null = null; - let isProfileLoading = $state(false); + let isRefreshingProfile = $state(false); onMount(() => { if (localStorage.getItem("alexandria/amber/fallback") === "1") { @@ -44,7 +46,7 @@ } }); - // Use profile data from userStore instead of fetching separately + // Use profile data from userStore let userState = $derived($userStore); let profile = $derived(userState.profile); let pfp = $derived(profile?.picture); @@ -52,6 +54,14 @@ let tag = $derived(profile?.name); let npub = $derived(userState.npub); + // Debug logging + $effect(() => { + console.log("Profile component - userState:", userState); + console.log("Profile component - profile:", profile); + console.log("Profile component - pfp:", pfp); + console.log("Profile component - username:", username); + }); + // Handle user state changes with effects $effect(() => { const currentUser = userState; @@ -87,51 +97,131 @@ } }); - // Fetch profile when user signs in or when pubkey changes + // Auto-refresh profile when user signs in $effect(() => { const currentUser = userState; - // If user is signed in but profile is not available, fetch it - if (currentUser.signedIn && !profile && currentUser.npub) { - const ndk = get(ndkInstance); - if (!ndk) return; + // If user is signed in and we have an npub but no profile data, refresh it + if (currentUser.signedIn && currentUser.npub && !profile?.name && !isRefreshingProfile) { + console.log("Profile: User signed in but no profile data, refreshing..."); + refreshProfile(); + } + }); - isProfileLoading = true; + // Debug activeInboxRelays + $effect(() => { + const inboxRelays = get(activeInboxRelays); + console.log("Profile component - activeInboxRelays:", inboxRelays); + }); + + // Manual trigger to refresh profile when user signs in + $effect(() => { + const currentUser = userState; + + if (currentUser.signedIn && currentUser.npub && !isRefreshingProfile) { + console.log("Profile: User signed in, triggering profile refresh..."); + // Add a small delay to ensure relays are ready + setTimeout(() => { + refreshProfile(); + }, 1000); + } + }); + + // Refresh profile when login method changes (e.g., Amber to read-only) + $effect(() => { + const currentUser = userState; + + if (currentUser.signedIn && currentUser.npub && currentUser.loginMethod) { + console.log("Profile: Login method detected:", currentUser.loginMethod); - // Use the current user's npub to fetch profile - const user = ndk.getUser({ npub: currentUser.npub }); + // If switching to read-only mode (npub), refresh profile + if (currentUser.loginMethod === "npub" && !isRefreshingProfile) { + console.log("Profile: Switching to read-only mode, refreshing profile..."); + setTimeout(() => { + refreshProfile(); + }, 500); + } + } + }); + + // Track login method changes and refresh profile when switching from Amber to npub + let previousLoginMethod = $state(null); + + $effect(() => { + const currentUser = userState; + + if (currentUser.signedIn && currentUser.loginMethod !== previousLoginMethod) { + console.log("Profile: Login method changed from", previousLoginMethod, "to", currentUser.loginMethod); - user.fetchProfile().then((userProfile: NDKUserProfile | null) => { - if (userProfile && !profile) { - // Only update if we don't already have profile data - profile = userProfile; - } - isProfileLoading = false; - }).catch(() => { - isProfileLoading = false; - }); + // If switching from Amber to npub (read-only), refresh profile + if (previousLoginMethod === "amber" && currentUser.loginMethod === "npub") { + console.log("Profile: Switching from Amber to read-only mode, refreshing profile..."); + setTimeout(() => { + refreshProfile(); + }, 1000); + } + + previousLoginMethod = currentUser.loginMethod; } + }); + + // Function to refresh profile data + async function refreshProfile() { + if (!userState.signedIn || !userState.npub) return; - // Fallback to fetching profile if not available in userStore and pubkey prop is provided - if (!profile && pubkey) { + isRefreshingProfile = true; + try { + console.log("Refreshing profile for npub:", userState.npub); + + // Try using NDK's built-in profile fetching first const ndk = get(ndkInstance); - if (!ndk) return; - - isProfileLoading = true; + if (ndk && userState.ndkUser) { + console.log("Using NDK's built-in profile fetching"); + const userProfile = await userState.ndkUser.fetchProfile(); + console.log("NDK profile fetch result:", userProfile); + + if (userProfile) { + const profileData = { + name: userProfile.name, + displayName: userProfile.displayName, + nip05: userProfile.nip05, + picture: userProfile.image, + about: userProfile.bio, + banner: userProfile.banner, + website: userProfile.website, + lud16: userProfile.lud16, + }; + + console.log("Converted profile data:", profileData); + + // Update the userStore with fresh profile data + userStore.update(currentState => ({ + ...currentState, + profile: profileData + })); + + return; + } + } - const user = ndk.getUser({ pubkey: pubkey ?? undefined }); + // Fallback to getUserMetadata + console.log("Falling back to getUserMetadata"); + const freshProfile = await getUserMetadata(userState.npub, true); // Force fresh fetch + console.log("Fresh profile data from getUserMetadata:", freshProfile); - user.fetchProfile().then((userProfile: NDKUserProfile | null) => { - if (userProfile && !profile) { - // Only update if we don't already have profile data - profile = userProfile; - } - isProfileLoading = false; - }).catch(() => { - isProfileLoading = false; - }); + // Update the userStore with fresh profile data + userStore.update(currentState => ({ + ...currentState, + profile: freshProfile + })); + } catch (error) { + console.error("Failed to refresh profile:", error); + } finally { + isRefreshingProfile = false; } - }); + } + + // Generate QR code const generateQrCode = async (text: string): Promise => { @@ -237,7 +327,6 @@ localStorage.removeItem("amber/nsec"); localStorage.removeItem("alexandria/amber/fallback"); logoutUser(); - profile = null; } function handleViewProfile() { @@ -255,6 +344,12 @@ function handleAmberFallbackDismiss() { showAmberFallback = false; localStorage.removeItem("alexandria/amber/fallback"); + + // Refresh profile when switching to read-only mode + setTimeout(() => { + console.log("Profile: Amber fallback dismissed, refreshing profile for read-only mode..."); + refreshProfile(); + }, 500); } function shortenNpub(long: string | null | undefined) { @@ -337,7 +432,7 @@ type="button" aria-label="Open profile menu" > - {#if isProfileLoading && !pfp} + {#if !pfp}
{:else} {username} {#if isNav}

@{tag}

{/if} - {:else if isProfileLoading} + {:else if !pfp}

Loading profile...

{:else}

Loading...

@@ -381,6 +476,7 @@ />View profile +
  • {#if userState.loginMethod === "extension"} Logged in with extension diff --git a/src/lib/stores/userStore.ts b/src/lib/stores/userStore.ts index 6e8ef07..4e2b067 100644 --- a/src/lib/stores/userStore.ts +++ b/src/lib/stores/userStore.ts @@ -163,10 +163,14 @@ export async function loginWithExtension() { const user = await signer.user(); const npub = user.npub; + console.log("Login with extension - fetching profile for npub:", npub); + // Try to fetch user metadata, but don't fail if it times out let profile: NostrProfile | null = null; try { - profile = await getUserMetadata(npub); + console.log("Login with extension - attempting to fetch profile..."); + profile = await getUserMetadata(npub, true); // Force fresh fetch + console.log("Login with extension - fetched profile:", profile); } catch (error) { console.warn("Failed to fetch user metadata during login:", error); // Continue with login even if metadata fetch fails @@ -174,6 +178,7 @@ export async function loginWithExtension() { name: npub.slice(0, 8) + "..." + npub.slice(-4), displayName: npub.slice(0, 8) + "..." + npub.slice(-4), }; + console.log("Login with extension - using fallback profile:", profile); } // Fetch user's preferred relays @@ -185,7 +190,8 @@ export async function loginWithExtension() { persistRelays(user, inboxes, outboxes); ndk.signer = signer; ndk.activeUser = user; - userStore.set({ + + const userState = { pubkey: user.pubkey, npub, profile, @@ -195,11 +201,14 @@ export async function loginWithExtension() { (relay) => relay.url, ), }, - loginMethod: "extension", + loginMethod: "extension" as const, ndkUser: user, signer, signedIn: true, - }); + }; + + console.log("Login with extension - setting userStore with:", userState); + userStore.set(userState); clearLogin(); localStorage.removeItem("alexandria/logout/flag"); persistLogin(user, "extension"); @@ -213,7 +222,23 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) { if (!ndk) throw new Error("NDK not initialized"); // Only clear previous login state after successful login const npub = user.npub; - const profile = await getUserMetadata(npub, true); // Force fresh fetch + + console.log("Login with Amber - fetching profile for npub:", npub); + + let profile: NostrProfile | null = null; + try { + profile = await getUserMetadata(npub, true); // Force fresh fetch + console.log("Login with Amber - fetched profile:", profile); + } catch (error) { + console.warn("Failed to fetch user metadata during Amber login:", error); + // Continue with login even if metadata fetch fails + profile = { + name: npub.slice(0, 8) + "..." + npub.slice(-4), + displayName: npub.slice(0, 8) + "..." + npub.slice(-4), + }; + console.log("Login with Amber - using fallback profile:", profile); + } + const [persistedInboxes, persistedOutboxes] = getPersistedRelays(user); for (const relay of persistedInboxes) { ndk.addExplicitRelay(relay); @@ -222,7 +247,8 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) { persistRelays(user, inboxes, outboxes); ndk.signer = amberSigner; ndk.activeUser = user; - userStore.set({ + + const userState = { pubkey: user.pubkey, npub, profile, @@ -232,11 +258,14 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) { (relay) => relay.url, ), }, - loginMethod: "amber", + loginMethod: "amber" as const, ndkUser: user, signer: amberSigner, signedIn: true, - }); + }; + + console.log("Login with Amber - setting userStore with:", userState); + userStore.set(userState); clearLogin(); localStorage.removeItem("alexandria/logout/flag"); persistLogin(user, "amber"); @@ -267,20 +296,40 @@ export async function loginWithNpub(pubkeyOrNpub: string) { console.error("Failed to encode npub from hex pubkey:", hexPubkey, e); throw e; } + + console.log("Login with npub - fetching profile for npub:", npub); + const user = ndk.getUser({ npub }); - const profile = await getUserMetadata(npub); + let profile: NostrProfile | null = null; + try { + profile = await getUserMetadata(npub, true); // Force fresh fetch + console.log("Login with npub - fetched profile:", profile); + } catch (error) { + console.warn("Failed to fetch user metadata during npub login:", error); + // Continue with login even if metadata fetch fails + profile = { + name: npub.slice(0, 8) + "..." + npub.slice(-4), + displayName: npub.slice(0, 8) + "..." + npub.slice(-4), + }; + console.log("Login with npub - using fallback profile:", profile); + } + ndk.signer = undefined; ndk.activeUser = user; - userStore.set({ + + const userState = { pubkey: user.pubkey, npub, profile, relays: { inbox: [], outbox: [] }, - loginMethod: "npub", + loginMethod: "npub" as const, ndkUser: user, signer: null, signedIn: true, - }); + }; + + console.log("Login with npub - setting userStore with:", userState); + userStore.set(userState); clearLogin(); localStorage.removeItem("alexandria/logout/flag"); persistLogin(user, "npub"); diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 85e9e8d..f70d139 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -60,8 +60,12 @@ export async function getUserMetadata( // Remove nostr: prefix if present const cleanId = identifier.replace(/^nostr:/, ""); + console.log("getUserMetadata called with identifier:", identifier, "force:", force); + if (!force && npubCache.has(cleanId)) { - return npubCache.get(cleanId)!; + const cached = npubCache.get(cleanId)!; + console.log("getUserMetadata returning cached profile:", cached); + return cached; } const fallback = { name: `${cleanId.slice(0, 8)}...${cleanId.slice(-4)}` }; @@ -69,12 +73,14 @@ export async function getUserMetadata( try { const ndk = get(ndkInstance); if (!ndk) { + console.warn("getUserMetadata: No NDK instance available"); npubCache.set(cleanId, fallback); return fallback; } const decoded = nip19.decode(cleanId); if (!decoded) { + console.warn("getUserMetadata: Failed to decode identifier:", cleanId); npubCache.set(cleanId, fallback); return fallback; } @@ -86,19 +92,27 @@ export async function getUserMetadata( } else if (decoded.type === "nprofile") { pubkey = decoded.data.pubkey; } else { + console.warn("getUserMetadata: Unsupported identifier type:", decoded.type); npubCache.set(cleanId, fallback); return fallback; } + console.log("getUserMetadata: Fetching profile for pubkey:", pubkey); + const profileEvent = await fetchEventWithFallback(ndk, { kinds: [0], authors: [pubkey], }); + + console.log("getUserMetadata: Profile event found:", profileEvent); + const profile = profileEvent && profileEvent.content ? JSON.parse(profileEvent.content) : null; + console.log("getUserMetadata: Parsed profile:", profile); + const metadata: NostrProfile = { name: profile?.name || fallback.name, displayName: profile?.displayName || profile?.display_name, @@ -110,9 +124,11 @@ export async function getUserMetadata( lud16: profile?.lud16, }; + console.log("getUserMetadata: Final metadata:", metadata); npubCache.set(cleanId, metadata); return metadata; } catch (e) { + console.error("getUserMetadata: Error fetching profile:", e); npubCache.set(cleanId, fallback); return fallback; } @@ -426,9 +442,11 @@ export async function fetchEventWithFallback( // Use the active inbox relays from the relay management system const inboxRelays = get(activeInboxRelays); + console.log("fetchEventWithFallback: Using inbox relays:", inboxRelays); + // Check if we have any relays available if (inboxRelays.length === 0) { - console.warn("No inbox relays available for event fetch"); + console.warn("fetchEventWithFallback: No inbox relays available for event fetch"); return null; } @@ -437,10 +455,14 @@ export async function fetchEventWithFallback( try { if (relaySet.relays.size === 0) { - console.warn("No relays in relay set for event fetch"); + console.warn("fetchEventWithFallback: No relays in relay set for event fetch"); return null; } + console.log("fetchEventWithFallback: Relay set size:", relaySet.relays.size); + console.log("fetchEventWithFallback: Filter:", filterOrId); + console.log("fetchEventWithFallback: Relay URLs:", Array.from(relaySet.relays).map((r: any) => r.url)); + let found: NDKEvent | null = null; if ( @@ -465,11 +487,12 @@ export async function fetchEventWithFallback( const timeoutSeconds = timeoutMs / 1000; const relayUrls = Array.from(relaySet.relays).map((r: any) => r.url).join(", "); console.warn( - `Event not found after ${timeoutSeconds}s timeout. Tried inbox relays: ${relayUrls}. Some relays may be offline or slow.`, + `fetchEventWithFallback: Event not found after ${timeoutSeconds}s timeout. Tried inbox relays: ${relayUrls}. Some relays may be offline or slow.`, ); return null; } + console.log("fetchEventWithFallback: Found event:", found.id); // Always wrap as NDKEvent return found instanceof NDKEvent ? found : new NDKEvent(ndk, found); } catch (err) { @@ -477,10 +500,10 @@ export async function fetchEventWithFallback( const timeoutSeconds = timeoutMs / 1000; const relayUrls = Array.from(relaySet.relays).map((r: any) => r.url).join(", "); console.warn( - `Event fetch timed out after ${timeoutSeconds}s. Tried inbox relays: ${relayUrls}. Some relays may be offline or slow.`, + `fetchEventWithFallback: Event fetch timed out after ${timeoutSeconds}s. Tried inbox relays: ${relayUrls}. Some relays may be offline or slow.`, ); } else { - console.error("Error in fetchEventWithFallback:", err); + console.error("fetchEventWithFallback: Error in fetchEventWithFallback:", err); } return null; }