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 @@
"node-emoji": "npm:node-emoji@^2.2.0", "node-emoji": "npm:node-emoji@^2.2.0",
"plantuml-encoder": "npm:plantuml-encoder@^1.4.0", "plantuml-encoder": "npm:plantuml-encoder@^1.4.0",
"qrcode": "npm:qrcode@^1.5.4", "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 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"dev:debug": "DEBUG_RELAYS=true vite dev",
"dev:node": "node --version && vite dev", "dev:node": "node --version && vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "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 @@
}); });
// Initialize relays and fetch events // 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() { async function initializeAndFetch() {
if (!ndk) { if (!ndk) {
console.debug('[PublicationFeed] No NDK instance available'); console.debug('[PublicationFeed] No NDK instance available');
@ -122,11 +124,12 @@
} }
} }
// Watch for relay store changes // Watch for relay store changes and user authentication state
$effect(() => { $effect(() => {
const inboxRelays = $activeInboxRelays; const inboxRelays = $activeInboxRelays;
const outboxRelays = $activeOutboxRelays; const outboxRelays = $activeOutboxRelays;
const newRelays = [...inboxRelays, ...outboxRelays]; const newRelays = [...inboxRelays, ...outboxRelays];
const userState = $userStore;
if (newRelays.length > 0 && !hasInitialized) { if (newRelays.length > 0 && !hasInitialized) {
console.debug('[PublicationFeed] Relays available, initializing'); console.debug('[PublicationFeed] Relays available, initializing');
@ -145,6 +148,18 @@
initializeAndFetch(); initializeAndFetch();
}, 3000); }, 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 @@
debouncedSearch(props.searchQuery); 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 // AI-NOTE: Watch for changes in the user filter checkbox
$effect(() => { $effect(() => {
// Trigger filtering when the user filter checkbox changes // Trigger filtering when the user filter checkbox changes

25
src/lib/ndk.ts

@ -368,7 +368,10 @@ function ensureSecureWebSocket(url: string): string {
*/ */
function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
try { 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 // Ensure the URL is using appropriate protocol
const secureUrl = ensureSecureWebSocket(url); const secureUrl = ensureSecureWebSocket(url);
@ -383,7 +386,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
// Set up connection timeout // Set up connection timeout
const connectionTimeout = setTimeout(() => { const connectionTimeout = setTimeout(() => {
try { 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(); relay.disconnect();
} catch { } catch {
// Silently ignore disconnect errors // Silently ignore disconnect errors
@ -395,7 +401,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
const authPolicy = new CustomRelayAuthPolicy(ndk); const authPolicy = new CustomRelayAuthPolicy(ndk);
relay.on("connect", () => { relay.on("connect", () => {
try { 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); clearTimeout(connectionTimeout);
authPolicy.authenticate(relay); authPolicy.authenticate(relay);
} catch { } catch {
@ -405,7 +414,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
} else { } else {
relay.on("connect", () => { relay.on("connect", () => {
try { 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); clearTimeout(connectionTimeout);
} catch { } catch {
// Silently handle connect handler errors // Silently handle connect handler errors
@ -513,7 +525,10 @@ export async function updateActiveRelayStores(ndk: NDK, forceUpdate: boolean = f
// Add relays to NDK pool (deduplicated) // Add relays to NDK pool (deduplicated)
const allRelayUrls = deduplicateRelayUrls([...relaySet.inboxRelays, ...relaySet.outboxRelays]); 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) { for (const url of allRelayUrls) {
try { try {

84
src/lib/services/event_search_service.ts

@ -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 @@
/**
* 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";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts"; import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts";
import { TIMEOUTS, VALIDATION } from "./search_constants.ts"; import { TIMEOUTS, VALIDATION } from "./search_constants.ts";
import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts";
/** /**
* Search for a single event by ID or filter * Search for a single event by ID or filter
@ -17,18 +18,35 @@ export async function searchEvent(query: string): Promise<NDKEvent | null> {
return 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; let attempts = 0;
const maxAttempts = 10; const maxAttempts = 5; // Reduced since we'll use fallback relays
while (ndk.pool.relays.size === 0 && attempts < maxAttempts) {
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})`); console.log(`[Search] Waiting for relays to be available (attempt ${attempts + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
attempts++; 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) { if (ndk.pool.relays.size === 0) {
console.warn("[Search] No relays available after waiting"); console.warn("[Search] No relays in pool, but proceeding with search - fallback relays will be used");
return null;
} }
// Clean the query and normalize to lowercase // Clean the query and normalize to lowercase

16
src/lib/utils/nostrUtils.ts

@ -5,7 +5,7 @@ import { npubCache } from "./npubCache.ts";
import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk"; import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk";
import type { NDKKind, NostrEvent } from "@nostr-dev-kit/ndk"; import type { NDKKind, NostrEvent } from "@nostr-dev-kit/ndk";
import type { Filter } from "./search_types.ts"; 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 { activeInboxRelays, activeOutboxRelays } from "../ndk.ts";
import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk"; import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk";
import { sha256 } from "@noble/hashes/sha2.js"; import { sha256 } from "@noble/hashes/sha2.js";
@ -443,19 +443,27 @@ export async function fetchEventWithFallback(
filterOrId: string | Filter, filterOrId: string | Filter,
timeoutMs: number = 3000, timeoutMs: number = 3000,
): Promise<NDKEvent | null> { ): 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 inboxRelays = get(activeInboxRelays);
const outboxRelays = get(activeOutboxRelays); 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 inbox relays:", inboxRelays);
console.log("fetchEventWithFallback: Using outbox relays:", outboxRelays); console.log("fetchEventWithFallback: Using outbox relays:", outboxRelays);
console.log("fetchEventWithFallback: Total unique relays:", allRelays.length);
// Check if we have any relays available // Check if we have any relays available
if (allRelays.length === 0) { if (allRelays.length === 0) {
console.warn("fetchEventWithFallback: No relays available for event fetch, using fallback relays"); console.warn("fetchEventWithFallback: No relays available for event fetch, using fallback relays");
// Use fallback relays when no relays are available // Use fallback relays when no relays are available
allRelays = [...secondaryRelays, ...searchRelays]; allRelays = [...secondaryRelays, ...searchRelays, ...anonymousRelays];
console.log("fetchEventWithFallback: Using fallback relays:", allRelays); console.log("fetchEventWithFallback: Using fallback relays:", allRelays);
} }

26
src/lib/utils/search_result_formatter.ts

@ -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(
} }
/** /**
* 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( function createPrimaryRelaySet(
searchType: SearchSubscriptionType, searchType: SearchSubscriptionType,
@ -413,9 +414,11 @@ function createPrimaryRelaySet(
const poolRelays = Array.from(ndk.pool.relays.values()); const poolRelays = Array.from(ndk.pool.relays.values());
console.debug('subscription_search: NDK pool relays:', poolRelays.map((r: any) => r.url)); 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") { if (searchType === "n") {
// AI-NOTE: 2025-01-08 - For profile searches, prioritize search relays for speed // For profile searches, prioritize search relays for speed but include all relays
// Use search relays first, then fall back to all relays if needed
const searchRelaySet = poolRelays.filter( const searchRelaySet = poolRelays.filter(
(relay: any) => (relay: any) =>
searchRelays.some( searchRelays.some(
@ -426,30 +429,27 @@ function createPrimaryRelaySet(
if (searchRelaySet.length > 0) { if (searchRelaySet.length > 0) {
console.debug('subscription_search: Profile search - using search relays for speed:', searchRelaySet.map((r: any) => r.url)); 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 { } else {
// Fallback to all relays if search relays not available // Use all relays if search relays not available
console.debug('subscription_search: Profile search - fallback to all relays:', poolRelays.map((r: any) => r.url)); console.debug('subscription_search: Profile search - using all relays:', poolRelays.map((r: any) => r.url));
return new NDKRelaySet(new Set(poolRelays) as any, ndk); return new NDKRelaySet(new Set(poolRelays) as any, ndk);
} }
} else { } else {
// For other searches, use active relays first // For all other searches, use ALL available relays for maximum coverage
const searchRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)]; const activeRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)];
console.debug('subscription_search: Active relay stores:', { console.debug('subscription_search: Active relay stores:', {
inboxRelays: get(activeInboxRelays), inboxRelays: get(activeInboxRelays),
outboxRelays: get(activeOutboxRelays), outboxRelays: get(activeOutboxRelays),
searchRelays activeRelays
}); });
const activeRelaySet = poolRelays.filter( // AI-NOTE: 2025-01-24 - Use all pool relays instead of filtering to active relays only
(relay: any) => // This ensures we don't miss events that might be on other relays
searchRelays.some( console.debug('subscription_search: Using ALL pool relays for comprehensive search coverage:', poolRelays.map((r: any) => r.url));
(searchRelay: string) => return new NDKRelaySet(new Set(poolRelays) as any, ndk);
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);
} }
} }
@ -647,24 +647,15 @@ function searchOtherRelaysInBackground(
): Promise<SearchResult> { ): Promise<SearchResult> {
const ndk = get(ndkInstance); 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( const otherRelays = new NDKRelaySet(
new Set( new Set(Array.from(ndk.pool.relays.values())),
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),
);
}
}),
),
ndk, 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 // Subscribe to events from other relays
const sub = ndk.subscribe( const sub = ndk.subscribe(

5
vite.config.ts

@ -42,6 +42,8 @@ export default defineConfig({
define: { define: {
// Expose the app version as a global variable // Expose the app version as a global variable
"import.meta.env.APP_VERSION": JSON.stringify(getAppVersionString()), "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: { optimizeDeps: {
esbuildOptions: { esbuildOptions: {
@ -54,5 +56,8 @@ export default defineConfig({
fs: { fs: {
allow: ['..'], allow: ['..'],
}, },
hmr: {
overlay: false, // Disable HMR overlay to prevent ESM URL scheme errors
},
}, },
}); });

Loading…
Cancel
Save