Browse Source

corrected relay management

master
silberengel 7 months ago
parent
commit
f3a589b595
  1. 3
      import_map.json
  2. 592
      package-lock.json
  3. 1
      package.json
  4. 761
      src/lib/components/EventSearch.svelte
  5. 42
      src/lib/components/publications/PublicationFeed.svelte
  6. 25
      src/lib/ndk.ts
  7. 84
      src/lib/services/event_search_service.ts
  8. 62
      src/lib/services/search_state_manager.ts
  9. 28
      src/lib/utils/event_search.ts
  10. 16
      src/lib/utils/nostrUtils.ts
  11. 26
      src/lib/utils/search_result_formatter.ts
  12. 57
      src/lib/utils/subscription_search.ts
  13. 5
      vite.config.ts

3
import_map.json

@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
"node-emoji": "npm:node-emoji@^2.2.0",
"plantuml-encoder": "npm:plantuml-encoder@^1.4.0",
"qrcode": "npm:qrcode@^1.5.4",
"child_process": "node:child_process"
"child_process": "node:child_process",
"process": "node:process"
}
}

592
package-lock.json generated

File diff suppressed because it is too large Load Diff

1
package.json

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite dev",
"dev:debug": "DEBUG_RELAYS=true vite dev",
"dev:node": "node --version && vite dev",
"build": "vite build",
"preview": "vite preview",

761
src/lib/components/EventSearch.svelte

File diff suppressed because it is too large Load Diff

42
src/lib/components/publications/PublicationFeed.svelte

@ -77,6 +77,8 @@ @@ -77,6 +77,8 @@
});
// Initialize relays and fetch events
// AI-NOTE: This function is called when the component mounts and when relay configuration changes
// It ensures that events are fetched from the current set of active relays
async function initializeAndFetch() {
if (!ndk) {
console.debug('[PublicationFeed] No NDK instance available');
@ -122,11 +124,12 @@ @@ -122,11 +124,12 @@
}
}
// Watch for relay store changes
// Watch for relay store changes and user authentication state
$effect(() => {
const inboxRelays = $activeInboxRelays;
const outboxRelays = $activeOutboxRelays;
const newRelays = [...inboxRelays, ...outboxRelays];
const userState = $userStore;
if (newRelays.length > 0 && !hasInitialized) {
console.debug('[PublicationFeed] Relays available, initializing');
@ -145,6 +148,18 @@ @@ -145,6 +148,18 @@
initializeAndFetch();
}, 3000);
}
} else if (hasInitialized && newRelays.length > 0) {
// AI-NOTE: Re-fetch events when user authentication state changes or relays are updated
// This ensures that when a user logs in and their relays are loaded, we fetch events from those relays
const currentRelaysString = allRelays.sort().join(',');
const newRelaysString = newRelays.sort().join(',');
if (currentRelaysString !== newRelaysString) {
console.debug('[PublicationFeed] Relay configuration changed, re-fetching events');
// Clear cache to force fresh fetch from new relays
indexEventCache.clear();
setTimeout(() => initializeAndFetch(), 0);
}
}
});
@ -513,6 +528,31 @@ @@ -513,6 +528,31 @@
debouncedSearch(props.searchQuery);
});
// AI-NOTE: Watch for user authentication state changes to re-fetch events when user logs in/out
$effect(() => {
const userState = $userStore;
if (hasInitialized && userState.signedIn) {
console.debug('[PublicationFeed] User signed in, checking if we need to re-fetch events');
// Check if we have user-specific relays that we haven't fetched from yet
const inboxRelays = $activeInboxRelays;
const outboxRelays = $activeOutboxRelays;
const newRelays = [...inboxRelays, ...outboxRelays];
if (newRelays.length > 0) {
const currentRelaysString = allRelays.sort().join(',');
const newRelaysString = newRelays.sort().join(',');
if (currentRelaysString !== newRelaysString) {
console.debug('[PublicationFeed] User logged in with new relays, re-fetching events');
// Clear cache to force fresh fetch from user's relays
indexEventCache.clear();
setTimeout(() => initializeAndFetch(), 0);
}
}
}
});
// AI-NOTE: Watch for changes in the user filter checkbox
$effect(() => {
// Trigger filtering when the user filter checkbox changes

25
src/lib/ndk.ts

@ -368,7 +368,10 @@ function ensureSecureWebSocket(url: string): string { @@ -368,7 +368,10 @@ function ensureSecureWebSocket(url: string): string {
*/
function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
try {
console.debug(`[NDK.ts] Creating relay with URL: ${url}`);
// Reduce verbosity in development - only log relay creation if debug mode is enabled
if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) {
console.debug(`[NDK.ts] Creating relay with URL: ${url}`);
}
// Ensure the URL is using appropriate protocol
const secureUrl = ensureSecureWebSocket(url);
@ -383,7 +386,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { @@ -383,7 +386,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
// Set up connection timeout
const connectionTimeout = setTimeout(() => {
try {
console.warn(`[NDK.ts] Connection timeout for ${secureUrl}`);
// Only log connection timeouts if debug mode is enabled
if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) {
console.debug(`[NDK.ts] Connection timeout for ${secureUrl}`);
}
relay.disconnect();
} catch {
// Silently ignore disconnect errors
@ -395,7 +401,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { @@ -395,7 +401,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
const authPolicy = new CustomRelayAuthPolicy(ndk);
relay.on("connect", () => {
try {
console.debug(`[NDK.ts] Relay connected: ${secureUrl}`);
// Only log successful connections if debug mode is enabled
if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) {
console.debug(`[NDK.ts] Relay connected: ${secureUrl}`);
}
clearTimeout(connectionTimeout);
authPolicy.authenticate(relay);
} catch {
@ -405,7 +414,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { @@ -405,7 +414,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
} else {
relay.on("connect", () => {
try {
console.debug(`[NDK.ts] Relay connected: ${secureUrl}`);
// Only log successful connections if debug mode is enabled
if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) {
console.debug(`[NDK.ts] Relay connected: ${secureUrl}`);
}
clearTimeout(connectionTimeout);
} catch {
// Silently handle connect handler errors
@ -513,7 +525,10 @@ export async function updateActiveRelayStores(ndk: NDK, forceUpdate: boolean = f @@ -513,7 +525,10 @@ export async function updateActiveRelayStores(ndk: NDK, forceUpdate: boolean = f
// 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');
// Reduce verbosity in development - only log relay addition if debug mode is enabled
if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) {
console.debug('[NDK.ts] updateActiveRelayStores: Adding', allRelayUrls.length, 'relays to NDK pool');
}
for (const url of allRelayUrls) {
try {

84
src/lib/services/event_search_service.ts

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
/**
* Service class for handling event search operations
* AI-NOTE: 2025-01-24 - Extracted from EventSearch component for better separation of concerns
*/
export class EventSearchService {
/**
* Determines the search type from a query string
*/
getSearchType(query: string): { type: string; term: string } | null {
const lowerQuery = query.toLowerCase();
if (lowerQuery.startsWith("d:")) {
const dTag = query.slice(2).trim().toLowerCase();
return dTag ? { type: "d", term: dTag } : null;
}
if (lowerQuery.startsWith("t:")) {
const searchTerm = query.slice(2).trim();
return searchTerm ? { type: "t", term: searchTerm } : null;
}
if (lowerQuery.startsWith("n:")) {
const searchTerm = query.slice(2).trim();
return searchTerm ? { type: "n", term: searchTerm } : null;
}
if (query.includes("@")) {
return { type: "nip05", term: query };
}
return null;
}
/**
* Checks if a search value matches the current event
*/
isCurrentEventMatch(searchValue: string, event: any, relays: string[]): boolean {
const currentEventId = event.id;
let currentNaddr = null;
let currentNevent = null;
let currentNpub = null;
let currentNprofile = null;
try {
const { neventEncode, naddrEncode, nprofileEncode } = require("$lib/utils");
const { getMatchingTags, toNpub } = require("$lib/utils/nostrUtils");
currentNevent = neventEncode(event, relays);
} catch {}
try {
const { naddrEncode } = require("$lib/utils");
const { getMatchingTags } = require("$lib/utils/nostrUtils");
currentNaddr = getMatchingTags(event, "d")[0]?.[1]
? naddrEncode(event, relays)
: null;
} catch {}
try {
const { toNpub } = require("$lib/utils/nostrUtils");
currentNpub = event.kind === 0 ? toNpub(event.pubkey) : null;
} catch {}
if (
searchValue &&
searchValue.startsWith("nprofile1") &&
event.kind === 0
) {
try {
const { nprofileEncode } = require("$lib/utils");
currentNprofile = nprofileEncode(event.pubkey, relays);
} catch {}
}
return (
searchValue === currentEventId ||
(currentNaddr && searchValue === currentNaddr) ||
(currentNevent && searchValue === currentNevent) ||
(currentNpub && searchValue === currentNpub) ||
(currentNprofile && searchValue === currentNprofile)
);
}
}

62
src/lib/services/search_state_manager.ts

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/**
* Service class for managing search state operations
* AI-NOTE: 2025-01-24 - Extracted from EventSearch component for better separation of concerns
*/
export class SearchStateManager {
/**
* Updates the search state with new values
*/
updateSearchState(
state: {
searching: boolean;
searchCompleted: boolean;
searchResultCount: number | null;
searchResultType: string | null;
},
onLoadingChange?: (loading: boolean) => void
): void {
if (onLoadingChange) {
onLoadingChange(state.searching);
}
}
/**
* Resets all search state to initial values
*/
resetSearchState(
callbacks: {
onSearchResults: (events: any[], secondOrder: any[], tTagEvents: any[], eventIds: Set<string>, addresses: Set<string>) => void;
cleanupSearch: () => void;
clearTimeout: () => void;
}
): void {
callbacks.cleanupSearch();
callbacks.onSearchResults([], [], [], new Set(), new Set());
callbacks.clearTimeout();
}
/**
* Handles search errors with consistent error handling
*/
handleSearchError(
error: unknown,
defaultMessage: string,
callbacks: {
setLocalError: (error: string | null) => void;
cleanupSearch: () => void;
updateSearchState: (state: any) => void;
resetProcessingFlags: () => void;
}
): void {
const errorMessage = error instanceof Error ? error.message : defaultMessage;
callbacks.setLocalError(errorMessage);
callbacks.cleanupSearch();
callbacks.updateSearchState({
searching: false,
searchCompleted: false,
searchResultCount: null,
searchResultType: null
});
callbacks.resetProcessingFlags();
}
}

28
src/lib/utils/event_search.ts

@ -6,6 +6,7 @@ import type { Filter } from "./search_types.ts"; @@ -6,6 +6,7 @@ import type { Filter } from "./search_types.ts";
import { get } from "svelte/store";
import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts";
import { TIMEOUTS, VALIDATION } from "./search_constants.ts";
import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts";
/**
* Search for a single event by ID or filter
@ -17,18 +18,35 @@ export async function searchEvent(query: string): Promise<NDKEvent | null> { @@ -17,18 +18,35 @@ export async function searchEvent(query: string): Promise<NDKEvent | null> {
return null;
}
// Wait for relays to be available
// AI-NOTE: 2025-01-24 - Wait for any relays to be available, not just pool relays
// This ensures searches can proceed even if some relay types are not available
let attempts = 0;
const maxAttempts = 10;
while (ndk.pool.relays.size === 0 && attempts < maxAttempts) {
const maxAttempts = 5; // Reduced since we'll use fallback relays
while (attempts < maxAttempts) {
// Check if we have any relays in the pool
if (ndk.pool.relays.size > 0) {
console.log(`[Search] Found ${ndk.pool.relays.size} relays in NDK pool`);
break;
}
// Also check if we have any active relays
const inboxRelays = get(activeInboxRelays);
const outboxRelays = get(activeOutboxRelays);
if (inboxRelays.length > 0 || outboxRelays.length > 0) {
console.log(`[Search] Found active relays - inbox: ${inboxRelays.length}, outbox: ${outboxRelays.length}`);
break;
}
console.log(`[Search] Waiting for relays to be available (attempt ${attempts + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, 500));
attempts++;
}
// AI-NOTE: 2025-01-24 - Don't fail if no relays are available, let fetchEventWithFallback handle fallbacks
// The fetchEventWithFallback function will use all available relays including fallback relays
if (ndk.pool.relays.size === 0) {
console.warn("[Search] No relays available after waiting");
return null;
console.warn("[Search] No relays in pool, but proceeding with search - fallback relays will be used");
}
// Clean the query and normalize to lowercase

16
src/lib/utils/nostrUtils.ts

@ -5,7 +5,7 @@ import { npubCache } from "./npubCache.ts"; @@ -5,7 +5,7 @@ import { npubCache } from "./npubCache.ts";
import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk";
import type { NDKKind, NostrEvent } from "@nostr-dev-kit/ndk";
import type { Filter } from "./search_types.ts";
import { communityRelays, secondaryRelays, searchRelays } from "../consts.ts";
import { communityRelays, secondaryRelays, searchRelays, anonymousRelays } from "../consts.ts";
import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts";
import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk";
import { sha256 } from "@noble/hashes/sha2.js";
@ -443,19 +443,27 @@ export async function fetchEventWithFallback( @@ -443,19 +443,27 @@ export async function fetchEventWithFallback(
filterOrId: string | Filter,
timeoutMs: number = 3000,
): Promise<NDKEvent | null> {
// Use both inbox and outbox relays for better event discovery
// AI-NOTE: 2025-01-24 - Use ALL available relays for comprehensive event discovery
// This ensures we don't miss events that might be on any available relay
// Get all relays from NDK pool first (most comprehensive)
const poolRelays = Array.from(ndk.pool.relays.values()).map((r: any) => r.url);
const inboxRelays = get(activeInboxRelays);
const outboxRelays = get(activeOutboxRelays);
let allRelays = [...inboxRelays, ...outboxRelays];
// Combine all available relays, prioritizing pool relays
let allRelays = [...new Set([...poolRelays, ...inboxRelays, ...outboxRelays])];
console.log("fetchEventWithFallback: Using pool relays:", poolRelays);
console.log("fetchEventWithFallback: Using inbox relays:", inboxRelays);
console.log("fetchEventWithFallback: Using outbox relays:", outboxRelays);
console.log("fetchEventWithFallback: Total unique relays:", allRelays.length);
// Check if we have any relays available
if (allRelays.length === 0) {
console.warn("fetchEventWithFallback: No relays available for event fetch, using fallback relays");
// Use fallback relays when no relays are available
allRelays = [...secondaryRelays, ...searchRelays];
allRelays = [...secondaryRelays, ...searchRelays, ...anonymousRelays];
console.log("fetchEventWithFallback: Using fallback relays:", allRelays);
}

26
src/lib/utils/search_result_formatter.ts

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/**
* Utility class for formatting search result messages
* AI-NOTE: 2025-01-24 - Extracted from EventSearch component for better separation of concerns
*/
export class SearchResultFormatter {
/**
* Formats a result message based on search count and type
*/
formatResultMessage(searchResultCount: number | null, searchResultType: string | null): string {
if (searchResultCount === 0) {
return "Search completed. No results found.";
}
const typeLabel =
searchResultType === "n"
? "profile"
: searchResultType === "nip05"
? "NIP-05 address"
: "event";
const countLabel = searchResultType === "n" ? "profiles" : "events";
return searchResultCount === 1
? `Search completed. Found 1 ${typeLabel}.`
: `Search completed. Found ${searchResultCount} ${countLabel}.`;
}
}

57
src/lib/utils/subscription_search.ts

@ -403,7 +403,8 @@ async function createProfileSearchFilter( @@ -403,7 +403,8 @@ async function createProfileSearchFilter(
}
/**
* Create primary relay set based on search type
* Create primary relay set for search operations
* AI-NOTE: 2025-01-24 - Updated to use all available relays to prevent search failures
*/
function createPrimaryRelaySet(
searchType: SearchSubscriptionType,
@ -413,9 +414,11 @@ function createPrimaryRelaySet( @@ -413,9 +414,11 @@ function createPrimaryRelaySet(
const poolRelays = Array.from(ndk.pool.relays.values());
console.debug('subscription_search: NDK pool relays:', poolRelays.map((r: any) => r.url));
// AI-NOTE: 2025-01-24 - Use ALL available relays for comprehensive search coverage
// This ensures searches don't fail due to missing relays and provides maximum event discovery
if (searchType === "n") {
// AI-NOTE: 2025-01-08 - For profile searches, prioritize search relays for speed
// Use search relays first, then fall back to all relays if needed
// For profile searches, prioritize search relays for speed but include all relays
const searchRelaySet = poolRelays.filter(
(relay: any) =>
searchRelays.some(
@ -426,30 +429,27 @@ function createPrimaryRelaySet( @@ -426,30 +429,27 @@ function createPrimaryRelaySet(
if (searchRelaySet.length > 0) {
console.debug('subscription_search: Profile search - using search relays for speed:', searchRelaySet.map((r: any) => r.url));
return new NDKRelaySet(new Set(searchRelaySet) as any, ndk);
// Still include all relays for comprehensive coverage
console.debug('subscription_search: Profile search - also including all relays for comprehensive coverage');
return new NDKRelaySet(new Set(poolRelays) as any, ndk);
} else {
// Fallback to all relays if search relays not available
console.debug('subscription_search: Profile search - fallback to all relays:', poolRelays.map((r: any) => r.url));
// Use all relays if search relays not available
console.debug('subscription_search: Profile search - using all relays:', poolRelays.map((r: any) => r.url));
return new NDKRelaySet(new Set(poolRelays) as any, ndk);
}
} else {
// For other searches, use active relays first
const searchRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)];
// For all other searches, use ALL available relays for maximum coverage
const activeRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)];
console.debug('subscription_search: Active relay stores:', {
inboxRelays: get(activeInboxRelays),
outboxRelays: get(activeOutboxRelays),
searchRelays
activeRelays
});
const activeRelaySet = poolRelays.filter(
(relay: any) =>
searchRelays.some(
(searchRelay: string) =>
normalizeUrl(relay.url) === normalizeUrl(searchRelay),
),
);
console.debug('subscription_search: Active relay set:', activeRelaySet.map((r: any) => r.url));
return new NDKRelaySet(new Set(activeRelaySet) as any, ndk);
// AI-NOTE: 2025-01-24 - Use all pool relays instead of filtering to active relays only
// This ensures we don't miss events that might be on other relays
console.debug('subscription_search: Using ALL pool relays for comprehensive search coverage:', poolRelays.map((r: any) => r.url));
return new NDKRelaySet(new Set(poolRelays) as any, ndk);
}
}
@ -647,24 +647,15 @@ function searchOtherRelaysInBackground( @@ -647,24 +647,15 @@ function searchOtherRelaysInBackground(
): Promise<SearchResult> {
const ndk = get(ndkInstance);
// AI-NOTE: 2025-01-24 - Use ALL available relays for comprehensive search coverage
// This ensures we don't miss events that might be on any available relay
const otherRelays = new NDKRelaySet(
new Set(
Array.from(ndk.pool.relays.values()).filter((relay: any) => {
if (searchType === "n") {
// AI-NOTE: 2025-01-08 - For profile searches, use ALL available relays
// Don't exclude any relays since we want maximum coverage
return true;
} else {
// For other searches, exclude community relays from fallback search
return !communityRelays.some(
(communityRelay: string) =>
normalizeUrl(relay.url) === normalizeUrl(communityRelay),
);
}
}),
),
new Set(Array.from(ndk.pool.relays.values())),
ndk,
);
console.debug('subscription_search: Background search using ALL relays:',
Array.from(ndk.pool.relays.values()).map((r: any) => r.url));
// Subscribe to events from other relays
const sub = ndk.subscribe(

5
vite.config.ts

@ -42,6 +42,8 @@ export default defineConfig({ @@ -42,6 +42,8 @@ export default defineConfig({
define: {
// Expose the app version as a global variable
"import.meta.env.APP_VERSION": JSON.stringify(getAppVersionString()),
// Enable debug logging for relays when needed
"process.env.DEBUG_RELAYS": JSON.stringify(process.env.DEBUG_RELAYS || "false"),
},
optimizeDeps: {
esbuildOptions: {
@ -54,5 +56,8 @@ export default defineConfig({ @@ -54,5 +56,8 @@ export default defineConfig({
fs: {
allow: ['..'],
},
hmr: {
overlay: false, // Disable HMR overlay to prevent ESM URL scheme errors
},
},
});

Loading…
Cancel
Save