|
|
|
|
@ -96,28 +96,24 @@
@@ -96,28 +96,24 @@
|
|
|
|
|
const cached = await getProfile(pubkey); |
|
|
|
|
if (cached) { |
|
|
|
|
profileEvent = cached.event; |
|
|
|
|
// Load wall comments if we have the profile event |
|
|
|
|
if (profileEvent) { |
|
|
|
|
await loadWallComments(profileEvent.id); |
|
|
|
|
} |
|
|
|
|
// Don't load wall comments here - load them when Wall tab is clicked |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Fetch from relays if not in cache |
|
|
|
|
// Fetch from relays if not in cache - shorter timeout |
|
|
|
|
const relays = relayManager.getProfileReadRelays(); |
|
|
|
|
const events = await nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.METADATA], authors: [pubkey], limit: 1 }], |
|
|
|
|
relays, |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true } |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (events.length > 0 && isMounted) { |
|
|
|
|
profileEvent = events[0]; |
|
|
|
|
// Load wall comments |
|
|
|
|
await loadWallComments(profileEvent.id); |
|
|
|
|
// Don't load wall comments here - load them when Wall tab is clicked |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
// Failed to load profile |
|
|
|
|
// Failed to load profile event - non-critical |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -209,12 +205,12 @@
@@ -209,12 +205,12 @@
|
|
|
|
|
async function loadPins(pubkey: string) { |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
try { |
|
|
|
|
// Fetch the user's pin list (kind 10001) |
|
|
|
|
// Fetch the user's pin list (kind 10001) - cache first, short timeout |
|
|
|
|
const profileRelays = relayManager.getProfileReadRelays(); |
|
|
|
|
const pinLists = await nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.PIN_LIST], authors: [pubkey], limit: 1 }], |
|
|
|
|
profileRelays, |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (!isMounted || pinLists.length === 0) { |
|
|
|
|
@ -236,13 +232,14 @@
@@ -236,13 +232,14 @@
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Fetch the actual pinned events with cache-first and streaming |
|
|
|
|
// Use shorter timeout since cache should be fast |
|
|
|
|
const fetchPromise = nostrClient.fetchEvents( |
|
|
|
|
[{ ids: Array.from(pinnedIds), limit: config.feedLimit }], |
|
|
|
|
profileRelays, |
|
|
|
|
{ |
|
|
|
|
useCache: 'cache-first', // Load from cache first |
|
|
|
|
cacheResults: true, |
|
|
|
|
timeout: config.mediumTimeout, |
|
|
|
|
timeout: config.shortTimeout, // Shorter timeout for cache-first |
|
|
|
|
onUpdate: (newPins) => { |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
// Merge with existing pins |
|
|
|
|
@ -264,7 +261,7 @@
@@ -264,7 +261,7 @@
|
|
|
|
|
pins = pinnedEvents.sort((a, b) => b.created_at - a.created_at); |
|
|
|
|
} catch (error) { |
|
|
|
|
// Failed to load pins |
|
|
|
|
pins = []; |
|
|
|
|
if (isMounted) pins = []; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -273,18 +270,25 @@
@@ -273,18 +270,25 @@
|
|
|
|
|
try { |
|
|
|
|
const notificationRelays = relayManager.getFeedReadRelays(); |
|
|
|
|
|
|
|
|
|
// Fetch user's posts to find replies |
|
|
|
|
// Fetch user's posts to find replies - cache first, shorter timeout |
|
|
|
|
const userPosts = await nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [pubkey], limit: 100 }], |
|
|
|
|
notificationRelays, |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
const userPostIds = new Set(userPosts.map(p => p.id)); |
|
|
|
|
|
|
|
|
|
// Only fetch notifications if we have posts to check |
|
|
|
|
if (userPostIds.size === 0) { |
|
|
|
|
if (isMounted) notifications = []; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Fetch notifications: replies, mentions, reactions with cache-first and streaming |
|
|
|
|
// Use shorter timeout since cache should be fast |
|
|
|
|
const notificationEvents = await nostrClient.fetchEvents( |
|
|
|
|
[ |
|
|
|
|
{ kinds: [KIND.SHORT_TEXT_NOTE], '#e': Array.from(userPostIds).slice(0, 50), limit: 100 }, // Replies to user's posts |
|
|
|
|
@ -295,7 +299,7 @@
@@ -295,7 +299,7 @@
|
|
|
|
|
{ |
|
|
|
|
useCache: 'cache-first', // Load from cache first |
|
|
|
|
cacheResults: true, |
|
|
|
|
timeout: config.mediumTimeout, |
|
|
|
|
timeout: config.shortTimeout, // Shorter timeout for cache-first |
|
|
|
|
onUpdate: (newNotifications) => { |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
// Merge with existing notifications |
|
|
|
|
@ -323,7 +327,7 @@
@@ -323,7 +327,7 @@
|
|
|
|
|
.slice(0, 100); // Limit to 100 most recent |
|
|
|
|
} catch (error) { |
|
|
|
|
// Failed to load notifications |
|
|
|
|
notifications = []; |
|
|
|
|
if (isMounted) notifications = []; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -336,7 +340,7 @@
@@ -336,7 +340,7 @@
|
|
|
|
|
try { |
|
|
|
|
const interactionRelays = relayManager.getFeedResponseReadRelays(); |
|
|
|
|
|
|
|
|
|
// Fetch current user's posts from cache first (fast) |
|
|
|
|
// Fetch current user's posts from cache first (fast) - shorter timeout |
|
|
|
|
const fetchPromise1 = nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [currentUserPubkey], limit: config.mediumBatchLimit }], |
|
|
|
|
interactionRelays, |
|
|
|
|
@ -356,20 +360,17 @@
@@ -356,20 +360,17 @@
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Fetch interactions with cache-first and streaming |
|
|
|
|
// Fetch interactions with cache-first and streaming - shorter timeout |
|
|
|
|
const fetchPromise2 = nostrClient.fetchEvents( |
|
|
|
|
[ |
|
|
|
|
{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [profilePubkey], '#e': Array.from(currentUserPostIds).slice(0, config.smallBatchLimit), limit: config.smallBatchLimit }, // Limit IDs to avoid huge queries |
|
|
|
|
{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [profilePubkey], '#p': [currentUserPubkey], limit: config.smallBatchLimit } |
|
|
|
|
], |
|
|
|
|
interactionRelays, |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout } // Shorter timeout for cache-first |
|
|
|
|
); |
|
|
|
|
activeFetchPromises.add(fetchPromise2); |
|
|
|
|
const interactionEvents = await Promise.race([ |
|
|
|
|
fetchPromise2, |
|
|
|
|
new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 5000)) // 5s timeout |
|
|
|
|
]); |
|
|
|
|
const interactionEvents = await fetchPromise2; |
|
|
|
|
activeFetchPromises.delete(fetchPromise2); |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
@ -587,6 +588,7 @@
@@ -587,6 +588,7 @@
|
|
|
|
|
loading = true; |
|
|
|
|
try { |
|
|
|
|
// Step 1: Load profile and status first (fast from cache) - display immediately |
|
|
|
|
// fetchProfile uses parseProfile which prioritizes tags over JSON content |
|
|
|
|
const profilePromise = fetchProfile(pubkey); |
|
|
|
|
const statusPromise = fetchUserStatus(pubkey); |
|
|
|
|
const statusEventPromise = fetchUserStatusEvent(pubkey); |
|
|
|
|
@ -608,9 +610,6 @@
@@ -608,9 +610,6 @@
|
|
|
|
|
userStatusEvent = statusEvent; |
|
|
|
|
loading = false; // Show profile immediately, even if posts are still loading |
|
|
|
|
|
|
|
|
|
// Load the kind 0 profile event for the wall |
|
|
|
|
await loadProfileEvent(pubkey); |
|
|
|
|
|
|
|
|
|
// Validate NIP-05 addresses in background (non-blocking) |
|
|
|
|
if (profileData?.nip05 && profileData.nip05.length > 0) { |
|
|
|
|
for (const nip05 of profileData.nip05) { |
|
|
|
|
@ -624,32 +623,57 @@
@@ -624,32 +623,57 @@
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Step 2: Load pins for the profile being viewed |
|
|
|
|
await loadPins(pubkey); |
|
|
|
|
// Load pins and notifications/interactions in parallel (non-blocking) |
|
|
|
|
// Don't wait for these - they'll update the UI as they load |
|
|
|
|
const loadPromises: Promise<void>[] = []; |
|
|
|
|
|
|
|
|
|
// Always load pins (for both own and other profiles) |
|
|
|
|
loadPromises.push(loadPins(pubkey).catch(() => { |
|
|
|
|
// Failed to load pins - non-critical |
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
// Step 3: Load notifications or interactions |
|
|
|
|
// Load notifications or interactions based on profile type |
|
|
|
|
if (isOwnProfile) { |
|
|
|
|
await loadNotifications(pubkey); |
|
|
|
|
loadPromises.push(loadNotifications(pubkey).catch(() => { |
|
|
|
|
// Failed to load notifications - non-critical |
|
|
|
|
})); |
|
|
|
|
interactionsWithMe = []; |
|
|
|
|
// Set default tab: if no pins, default to notifications |
|
|
|
|
if (pins.length === 0 && notifications.length > 0) { |
|
|
|
|
activeTab = 'notifications'; |
|
|
|
|
} else if (pins.length > 0) { |
|
|
|
|
activeTab = 'pins'; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
notifications = []; |
|
|
|
|
// Load interactions if logged in and viewing another user's profile |
|
|
|
|
if (currentUserPubkey) { |
|
|
|
|
await loadInteractionsWithMe(pubkey, currentUserPubkey); |
|
|
|
|
loadPromises.push(loadInteractionsWithMe(pubkey, currentUserPubkey).catch(() => { |
|
|
|
|
// Failed to load interactions - non-critical |
|
|
|
|
})); |
|
|
|
|
} else { |
|
|
|
|
interactionsWithMe = []; |
|
|
|
|
} |
|
|
|
|
// Set default tab to pins if available |
|
|
|
|
if (pins.length > 0) { |
|
|
|
|
activeTab = 'pins'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Wait for initial loads to complete to set default tab |
|
|
|
|
Promise.all(loadPromises).then(() => { |
|
|
|
|
if (!isMounted || abortController.signal.aborted || currentLoadPubkey !== pubkey) return; |
|
|
|
|
|
|
|
|
|
if (isOwnProfile) { |
|
|
|
|
// Set default tab: if no pins, default to notifications |
|
|
|
|
if (pins.length === 0 && notifications.length > 0) { |
|
|
|
|
activeTab = 'notifications'; |
|
|
|
|
} else if (pins.length > 0) { |
|
|
|
|
activeTab = 'pins'; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// Set default tab to pins if available |
|
|
|
|
if (pins.length > 0) { |
|
|
|
|
activeTab = 'pins'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Load profile event in background (only needed for wall tab) |
|
|
|
|
// Don't await - load it when user clicks Wall tab |
|
|
|
|
loadProfileEvent(pubkey).catch(() => { |
|
|
|
|
// Failed to load profile event - non-critical |
|
|
|
|
}); |
|
|
|
|
} catch (error) { |
|
|
|
|
// Only update state if this load wasn't aborted |
|
|
|
|
if (!abortController.signal.aborted && currentLoadPubkey === pubkey) { |
|
|
|
|
@ -759,6 +783,10 @@
@@ -759,6 +783,10 @@
|
|
|
|
|
<button |
|
|
|
|
onclick={async () => { |
|
|
|
|
activeTab = 'wall'; |
|
|
|
|
// Load profile event if not loaded yet |
|
|
|
|
if (!profileEvent && profilePubkey) { |
|
|
|
|
await loadProfileEvent(profilePubkey); |
|
|
|
|
} |
|
|
|
|
// Load wall comments if profile event is available and not already loaded |
|
|
|
|
if (profileEvent && wallComments.length === 0 && !loadingWall) { |
|
|
|
|
await loadWallComments(profileEvent.id); |
|
|
|
|
|