From 8893f1012d8b5e67d588951503d9d3801fa6629f Mon Sep 17 00:00:00 2001 From: ANIR1604 Date: Sat, 4 Apr 2026 04:40:39 +0530 Subject: [PATCH 1/3] fix: stabilize user-profile save and hydration flow Await relay reconnection before nostr publish/subscribe to prevent CLOSED/CLOSING websocket errors during profile saves. Improve user-profile loading by relying on profile context hydration and prefetching the signed-in user profile earlier in app initialization to avoid blank form states. --- pages/_app.tsx | 11 ++++++++++- pages/settings/user-profile.tsx | 9 +++++---- utils/nostr/nostr-manager.ts | 28 +++++++++++++++------------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 2547b24e..b916d497 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -511,6 +511,15 @@ function Shopstr({ props }: { props: AppProps }) { editBlossomContext([], false); } + const userPubkey = isLoggedIn ? (await signer?.getPubKey()) : undefined; + const initialUserProfileFetch = userPubkey + ? fetchProfile(nostr!, allRelays, [userPubkey], editProfileContext).catch( + (error) => { + console.error("Error fetching current user profile:", error); + } + ) + : Promise.resolve(); + // Fetch products and collect profile pubkeys let productEvents: NostrEvent[] = []; let profileSetFromProducts = new Set(); @@ -529,7 +538,6 @@ function Shopstr({ props }: { props: AppProps }) { // Handle profile fetching let pubkeysToFetchProfilesFor = [...profileSetFromProducts]; - const userPubkey = (await signer?.getPubKey()) || undefined; const profileSetFromChats = new Set(); if (isLoggedIn) { @@ -566,6 +574,7 @@ function Shopstr({ props }: { props: AppProps }) { } try { + await initialUserProfileFetch; await fetchProfile( nostr!, allRelays, diff --git a/pages/settings/user-profile.tsx b/pages/settings/user-profile.tsx index 85040eb3..97d57dee 100644 --- a/pages/settings/user-profile.tsx +++ b/pages/settings/user-profile.tsx @@ -29,7 +29,6 @@ import ShopstrSpinner from "@/components/utility-components/shopstr-spinner"; const UserProfilePage = () => { const { nostr } = useContext(NostrContext); const [isUploadingProfile, setIsUploadingProfile] = useState(false); - const [isFetchingProfile, setIsFetchingProfile] = useState(false); const { signer, pubkey: userPubkey, @@ -58,13 +57,16 @@ const UserProfilePage = () => { const watchBanner = watch("banner"); const watchPicture = watch("picture"); + const hasCurrentUserProfile = + !!userPubkey && profileContext.profileData.has(userPubkey); + const isFetchingProfile = + !userPubkey || (profileContext.isLoading && !hasCurrentUserProfile); const defaultImage = useMemo(() => { return "https://robohash.org/" + userPubkey; }, [userPubkey]); useEffect(() => { - if (!userPubkey) return; - setIsFetchingProfile(true); + if (!userPubkey || profileContext.isLoading) return; const profileMap = profileContext.profileData; const profile = profileMap.has(userPubkey) ? profileMap.get(userPubkey) @@ -72,7 +74,6 @@ const UserProfilePage = () => { if (profile) { reset(profile.content); } - setIsFetchingProfile(false); if (signer instanceof NostrNSecSigner) { const nsecSigner = signer as NostrNSecSigner; diff --git a/utils/nostr/nostr-manager.ts b/utils/nostr/nostr-manager.ts index bff5a405..7a3e7b6f 100644 --- a/utils/nostr/nostr-manager.ts +++ b/utils/nostr/nostr-manager.ts @@ -82,18 +82,20 @@ export class NostrManager { return signer; } - private keepAlive(relays: NostrRelay[]) { - for (const relay of relays) { - if (relay.sleeping) { - try { - relay.connect(); - relay.sleeping = false; - } catch (e) { - console.error(e); + private async keepAlive(relays: NostrRelay[]) { + await Promise.all( + relays.map(async (relay) => { + if (relay.sleeping) { + try { + await relay.connect(); + relay.sleeping = false; + } catch (e) { + console.error(e); + } } - } - relay.lastActive = Date.now(); - } + relay.lastActive = Date.now(); + }) + ); } private async gc() { @@ -162,7 +164,7 @@ export class NostrManager { for (const relay of relays) { relay.activeSubs.push(sub); } - this.keepAlive(relays); + await this.keepAlive(relays); return sub; } @@ -214,7 +216,7 @@ export class NostrManager { const relays = relayUrls ? this.relays.filter((r) => relayUrls.includes(r.url)) : this.relays; - this.keepAlive(relays); + await this.keepAlive(relays); await Promise.allSettled( this.pool.publish( relays.map((r) => r.url), From 6138632c3cc0ea77845ae72b6b18c1a478c516f9 Mon Sep 17 00:00:00 2001 From: ANIR1604 Date: Sat, 4 Apr 2026 14:08:46 +0530 Subject: [PATCH 2/3] fix: prevent nostr fetch EOSE race and stabilize profile loading Await relay reconnects before publish/subscribe and guard fetch EOSE handling so sub.close is never called before subscription initialization. Improve user-profile hydration by using context-driven loading and early current-user profile prefetch to avoid blank fields and delayed updates. --- docker-compose.yml | 2 +- utils/nostr/nostr-manager.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2fd8e1ad..56184b50 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: shopstr POSTGRES_DB: shopstr ports: - - "5432:5432" + - "5433:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: diff --git a/utils/nostr/nostr-manager.ts b/utils/nostr/nostr-manager.ts index 7a3e7b6f..0a8b934f 100644 --- a/utils/nostr/nostr-manager.ts +++ b/utils/nostr/nostr-manager.ts @@ -189,6 +189,15 @@ export class NostrManager { const onEvent = params.onevent; const onEose = params.oneose; const fetchedEvents: Array = []; + let sub: NostrSub | undefined; + let didCloseSub = false; + let didResolve = false; + + const closeSubIfNeeded = async () => { + if (!sub || didCloseSub) return; + didCloseSub = true; + await sub.close(); + }; params.onevent = (event: NostrEvent) => { fetchedEvents.push(event); @@ -196,12 +205,15 @@ export class NostrManager { }; params.oneose = () => { - sub!.close(); - resolve(fetchedEvents); + closeSubIfNeeded().catch(console.error); + if (!didResolve) { + didResolve = true; + resolve(fetchedEvents); + } return onEose!(); }; - const sub = await this.subscribe(filters, params, relayUrls); + sub = await this.subscribe(filters, params, relayUrls); }); } From 77df0354de01a3a1ce86e56a03e9b3d083d5d207 Mon Sep 17 00:00:00 2001 From: Anirban Biswas <139000437+Rustix69@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:12:16 +0530 Subject: [PATCH 3/3] Change PostgreSQL port mapping to default 5432 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 56184b50..2fd8e1ad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: shopstr POSTGRES_DB: shopstr ports: - - "5433:5432" + - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: