Browse Source

reinstated subscription search

master
silberengel 7 months ago
parent
commit
cd25047213
  1. 163
      src/lib/utils/subscription_search.ts
  2. 24
      src/routes/events/+page.svelte

163
src/lib/utils/subscription_search.ts

@ -93,16 +93,28 @@ export async function searchBySubscription(
tTagEvents: tTagEventsWithCreatedAt tTagEvents: tTagEventsWithCreatedAt
}; };
// AI-NOTE: 2025-01-24 - For profile searches, return cached results immediately // AI-NOTE: 2025-01-24 - Return cached results immediately but trigger second-order search in background
// The EventSearch component now handles cache checking before calling this function // This ensures we get fast results while still updating second-order data
if (searchType === "n") { console.log("subscription_search: Returning cached result immediately, triggering background second-order search");
console.log(
"subscription_search: Returning cached profile result immediately", // Trigger second-order search in background for all search types
); if (ndk) {
return resultWithCreatedAt; // Start second-order search in background for n and d searches only
} else { if (searchType === "n" || searchType === "d") {
return resultWithCreatedAt; console.log("subscription_search: Triggering background second-order search for cached result");
} performSecondOrderSearchInBackground(
searchType as "n" | "d",
eventsWithCreatedAt,
cachedResult.eventIds || new Set(),
cachedResult.addresses || new Set(),
ndk,
searchType === "n" ? eventsWithCreatedAt[0]?.pubkey : undefined,
callbacks
);
}
}
return resultWithCreatedAt;
} }
if (!ndk) { if (!ndk) {
@ -161,6 +173,27 @@ export async function searchBySubscription(
normalizedSearchTerm, normalizedSearchTerm,
); );
searchCache.set(searchType, normalizedSearchTerm, immediateResult); searchCache.set(searchType, normalizedSearchTerm, immediateResult);
// AI-NOTE: 2025-01-24 - For profile searches, start background second-order search even for preloaded events
if (searchType === "n") {
console.log(
"subscription_search: Profile found from preloaded events, starting background second-order search",
);
// Start Phase 2 in background for second-order results
searchOtherRelaysInBackground(
searchType,
searchFilter,
searchState,
ndk,
callbacks,
cleanup,
);
// Clear the main timeout since we're returning early
cleanup();
}
return immediateResult; return immediateResult;
} }
} }
@ -172,12 +205,20 @@ export async function searchBySubscription(
"subscription_search: Searching primary relay with filter:", "subscription_search: Searching primary relay with filter:",
searchFilter.filter, searchFilter.filter,
); );
const primaryEvents = await ndk.fetchEvents(
// Add timeout to primary relay search
const primaryEventsPromise = ndk.fetchEvents(
searchFilter.filter, searchFilter.filter,
{ closeOnEose: true }, { closeOnEose: true },
primaryRelaySet, primaryRelaySet,
); );
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Primary relay search timeout")), TIMEOUTS.SUBSCRIPTION_SEARCH);
});
const primaryEvents = await Promise.race([primaryEventsPromise, timeoutPromise]) as any;
console.log( console.log(
"subscription_search: Primary relay returned", "subscription_search: Primary relay returned",
primaryEvents.size, primaryEvents.size,
@ -226,6 +267,9 @@ export async function searchBySubscription(
console.log( console.log(
`subscription_search: Profile search completed in ${elapsed}ms`, `subscription_search: Profile search completed in ${elapsed}ms`,
); );
// Clear the main timeout since we're returning early
cleanup();
return immediateResult; return immediateResult;
} }
@ -239,6 +283,8 @@ export async function searchBySubscription(
cleanup, cleanup,
); );
// Clear the main timeout since we're returning early
cleanup();
return immediateResult; return immediateResult;
} else { } else {
console.log( console.log(
@ -257,12 +303,19 @@ export async function searchBySubscription(
ndk, ndk,
); );
try { try {
const fallbackEvents = await ndk.fetchEvents( // Add timeout to fallback search
const fallbackEventsPromise = ndk.fetchEvents(
searchFilter.filter, searchFilter.filter,
{ closeOnEose: true }, { closeOnEose: true },
allRelaySet, allRelaySet,
); );
const fallbackTimeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Fallback search timeout")), TIMEOUTS.SUBSCRIPTION_SEARCH);
});
const fallbackEvents = await Promise.race([fallbackEventsPromise, fallbackTimeoutPromise]) as any;
console.log( console.log(
"subscription_search: Fallback search returned", "subscription_search: Fallback search returned",
fallbackEvents.size, fallbackEvents.size,
@ -293,6 +346,9 @@ export async function searchBySubscription(
console.log( console.log(
`subscription_search: Profile search completed in ${elapsed}ms (fallback)`, `subscription_search: Profile search completed in ${elapsed}ms (fallback)`,
); );
// Clear the main timeout since we're returning early
cleanup();
return fallbackResult; return fallbackResult;
} }
} catch (fallbackError) { } catch (fallbackError) {
@ -300,6 +356,11 @@ export async function searchBySubscription(
"subscription_search: Fallback search failed:", "subscription_search: Fallback search failed:",
fallbackError, fallbackError,
); );
// If it's a timeout error, continue to return empty result
if (fallbackError instanceof Error && fallbackError.message.includes("timeout")) {
console.log("subscription_search: Fallback search timed out, returning empty result");
}
} }
console.log( console.log(
@ -315,6 +376,9 @@ export async function searchBySubscription(
console.log( console.log(
`subscription_search: Profile search completed in ${elapsed}ms (not found)`, `subscription_search: Profile search completed in ${elapsed}ms (not found)`,
); );
// Clear the main timeout since we're returning early
cleanup();
return emptyResult; return emptyResult;
} else { } else {
console.log( console.log(
@ -327,6 +391,14 @@ export async function searchBySubscription(
`subscription_search: Error searching primary relay:`, `subscription_search: Error searching primary relay:`,
error, error,
); );
// If it's a timeout error, continue to Phase 2 instead of failing
if (error instanceof Error && error.message.includes("timeout")) {
console.log("subscription_search: Primary relay search timed out, continuing to Phase 2");
} else {
// For other errors, we might want to fail the search
throw error;
}
} }
} else { } else {
console.log( console.log(
@ -352,6 +424,8 @@ export async function searchBySubscription(
); );
} }
// Clear the main timeout since we're completing normally
cleanup();
return result; return result;
} }
@ -793,17 +867,42 @@ function searchOtherRelaysInBackground(
}); });
return new Promise<SearchResult>((resolve) => { return new Promise<SearchResult>((resolve) => {
let resolved = false;
// Add timeout to prevent hanging
const timeoutId = setTimeout(() => {
if (!resolved) {
console.log("subscription_search: Background search timeout, resolving with current results");
resolved = true;
sub.stop();
const result = processEoseResults(
searchType,
searchState,
searchFilter,
ndk,
callbacks,
);
searchCache.set(searchType, searchState.normalizedSearchTerm, result);
cleanup?.();
resolve(result);
}
}, TIMEOUTS.SUBSCRIPTION_SEARCH);
sub.on("eose", () => { sub.on("eose", () => {
const result = processEoseResults( if (!resolved) {
searchType, resolved = true;
searchState, clearTimeout(timeoutId);
searchFilter, const result = processEoseResults(
ndk, searchType,
callbacks, searchState,
); searchFilter,
searchCache.set(searchType, searchState.normalizedSearchTerm, result); ndk,
cleanup?.(); callbacks,
resolve(result); );
searchCache.set(searchType, searchState.normalizedSearchTerm, result);
cleanup?.();
resolve(result);
}
}); });
}); });
} }
@ -821,7 +920,7 @@ function processEoseResults(
if (searchType === "n") { if (searchType === "n") {
return processProfileEoseResults(searchState, searchFilter, ndk, callbacks); return processProfileEoseResults(searchState, searchFilter, ndk, callbacks);
} else if (searchType === "d") { } else if (searchType === "d") {
return processContentEoseResults(searchState, searchType, ndk); return processContentEoseResults(searchState, searchType, ndk, callbacks);
} else if (searchType === "t") { } else if (searchType === "t") {
return processTTagEoseResults(searchState); return processTTagEoseResults(searchState);
} }
@ -926,6 +1025,7 @@ function processContentEoseResults(
searchState: any, searchState: any,
searchType: SearchSubscriptionType, searchType: SearchSubscriptionType,
ndk: NDK, ndk: NDK,
callbacks?: SearchCallbacks,
): SearchResult { ): SearchResult {
if (searchState.firstOrderEvents.length === 0) { if (searchState.firstOrderEvents.length === 0) {
return createEmptySearchResult( return createEmptySearchResult(
@ -954,6 +1054,8 @@ function processContentEoseResults(
searchState.eventIds, searchState.eventIds,
searchState.eventAddresses, searchState.eventAddresses,
ndk, ndk,
undefined, // targetPubkey not needed for d-tag searches
callbacks,
); );
} }
@ -1041,23 +1143,16 @@ async function performSecondOrderSearchInBackground(
targetPubkey, targetPubkey,
); );
// AI-NOTE: 2025-01-24 - Use only active relays for second-order profile search to prevent hanging // AI-NOTE: 2025-01-24 - Use all available relays for second-order search to maximize results
const activeRelays = [
...get(activeInboxRelays),
...get(activeOutboxRelays),
];
const availableRelays = activeRelays
.map((url) => ndk.pool.relays.get(url))
.filter((relay): relay is any => relay !== undefined);
const relaySet = new NDKRelaySet( const relaySet = new NDKRelaySet(
new Set(availableRelays), new Set(Array.from(ndk.pool.relays.values())),
ndk, ndk,
); );
console.log( console.log(
"subscription_search: Using", "subscription_search: Using",
activeRelays.length, ndk.pool.relays.size,
"active relays for second-order search", "relays for second-order search",
); );
// Search for events that mention this pubkey via p-tags // Search for events that mention this pubkey via p-tags

24
src/routes/events/+page.svelte

@ -33,6 +33,28 @@
import { UserOutline } from "flowbite-svelte-icons"; import { UserOutline } from "flowbite-svelte-icons";
import type { UserProfile } from "$lib/models/user_profile"; import type { UserProfile } from "$lib/models/user_profile";
import type { SearchType } from "$lib/models/search_type"; import type { SearchType } from "$lib/models/search_type";
import { clearAllCaches } from "$lib/utils/cache_manager";
// AI-NOTE: 2025-01-24 - Add cache clearing function for testing second-order search
// This can be called from browser console: window.clearCache()
if (typeof window !== 'undefined') {
(window as any).clearCache = () => {
console.log('Clearing all caches for testing...');
clearAllCaches();
console.log('Caches cleared. Try searching again to test second-order search.');
};
// AI-NOTE: 2025-01-24 - Add function to clear specific search cache
// Usage: window.clearSearchCache('n', 'silberengel')
(window as any).clearSearchCache = (searchType: string, searchTerm: string) => {
console.log(`Clearing search cache for ${searchType}:${searchTerm}...`);
// Import searchCache dynamically
import('$lib/utils/searchCache').then(({ searchCache }) => {
searchCache.clear();
console.log('Search cache cleared. Try searching again to test second-order search.');
});
};
}
let loading = $state(false); let loading = $state(false);
let error = $state<string | null>(null); let error = $state<string | null>(null);
@ -137,7 +159,7 @@
const npub = toNpub(pubkey); const npub = toNpub(pubkey);
if (npub) { if (npub) {
// Force fetch to ensure profile is cached // Force fetch to ensure profile is cached
await getUserMetadata(npub, undefined, true); await getUserMetadata(npub, ndk, true);
console.log(`[Events Page] Cached profile for pubkey: ${pubkey}`); console.log(`[Events Page] Cached profile for pubkey: ${pubkey}`);
} }
} catch (error) { } catch (error) {

Loading…
Cancel
Save