From 0e153b1161bd28a46952646678c57f89156cde0d Mon Sep 17 00:00:00 2001 From: silberengel Date: Sun, 3 Aug 2025 09:54:32 +0200 Subject: [PATCH] sped up relays and made connections more robust for fetchNostrEvent function --- src/lib/utils/websocket_utils.ts | 137 +++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 44 deletions(-) diff --git a/src/lib/utils/websocket_utils.ts b/src/lib/utils/websocket_utils.ts index f835408..5c12c98 100644 --- a/src/lib/utils/websocket_utils.ts +++ b/src/lib/utils/websocket_utils.ts @@ -79,57 +79,106 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise 0 ? inboxRelays[0] : availableRelays[0]; - - const ws = await WebSocketPool.instance.acquire(selectedRelay); - const subId = crypto.randomUUID(); - - // AI-NOTE: Currying is used here to abstract the internal handler logic away from the WebSocket - // handling logic. The message and error handlers themselves can be refactored without affecting - // the WebSocket handling logic. - const curriedMessageHandler: (subId: string) => (resolve: ResolveCallback) => (reject: RejectCallback) => MessageEventHandler = - (subId) => - (resolve) => + // Try all available relays in parallel and return the first result + const relayPromises = availableRelays.map(async (relay) => { + try { + console.debug(`[WebSocket Utils]: Trying relay: ${relay}`); + + const ws = await WebSocketPool.instance.acquire(relay); + const subId = crypto.randomUUID(); + + // AI-NOTE: Currying is used here to abstract the internal handler logic away from the WebSocket + // handling logic. The message and error handlers themselves can be refactored without affecting + // the WebSocket handling logic. + const curriedMessageHandler: (subId: string) => (resolve: ResolveCallback) => (reject: RejectCallback) => MessageEventHandler = + (subId) => + (resolve) => + (reject) => + (ev: MessageEvent) => + handleMessage(ev, subId, resolve, reject); + const curriedErrorHandler: EventHandlerReject = (reject) => - (ev: MessageEvent) => - handleMessage(ev, subId, resolve, reject); - const curriedErrorHandler: EventHandlerReject = - (reject) => - (ev: Event) => - handleError(ev, reject); - - // AI-NOTE: These variables store references to partially-applied handlers so that the `finally` - // block receives the correct references to clean up the listeners. - let messageHandler: MessageEventHandler; - let errorHandler: EventHandler; - - const res = new Promise((resolve, reject) => { - messageHandler = curriedMessageHandler(subId)(resolve)(reject); - errorHandler = curriedErrorHandler(reject); - - ws.addEventListener("message", messageHandler); - ws.addEventListener("error", errorHandler); - }) - .withTimeout(2000) - .finally(() => { - ws.removeEventListener("message", messageHandler); - ws.removeEventListener("error", errorHandler); - WebSocketPool.instance.release(ws); + (ev: Event) => + handleError(ev, reject); + + // AI-NOTE: These variables store references to partially-applied handlers so that the `finally` + // block receives the correct references to clean up the listeners. + let messageHandler: MessageEventHandler; + let errorHandler: EventHandler; + + const res = new Promise((resolve, reject) => { + messageHandler = curriedMessageHandler(subId)(resolve)(reject); + errorHandler = curriedErrorHandler(reject); + + ws.addEventListener("message", messageHandler); + ws.addEventListener("error", errorHandler); + }) + .withTimeout(2000) + .finally(() => { + ws.removeEventListener("message", messageHandler); + ws.removeEventListener("error", errorHandler); + WebSocketPool.instance.release(ws); + }); + + ws.send(JSON.stringify(["REQ", subId, filter])); + + const result = await res; + if (result) { + console.debug(`[WebSocket Utils]: Found event on relay: ${relay}`); + return result; + } + + console.debug(`[WebSocket Utils]: No event found on relay: ${relay}`); + return null; + } catch (err) { + console.warn(`[WebSocket Utils]: Failed to fetch from relay ${relay}:`, err); + return null; + } }); - ws.send(JSON.stringify(["REQ", subId, filter])); - return res; + // Wait for the first successful result or all to fail with timeout + const timeoutPromise = new Promise((resolve) => { + setTimeout(() => { + console.warn("[WebSocket Utils]: Fetch timeout reached"); + resolve(null); + }, 5000); // 5 second timeout for the entire fetch operation + }); + + const fetchPromise = Promise.allSettled(relayPromises).then((results) => { + // Find the first successful result + for (const result of results) { + if (result.status === 'fulfilled' && result.value) { + return result.value; + } + } + return null; + }); + + // Race between the fetch and the timeout + const result = await Promise.race([fetchPromise, timeoutPromise]); + + if (result) { + return result; + } + + console.warn("[WebSocket Utils]: Failed to fetch event from all relays (timeout or no results)"); + return null; } /**