UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.

157 lines (132 loc) 5.31 kB
import type { NDKEvent, NDKTag } from "../events"; import { getRelaysForSync } from "../outbox/write"; import { NDKRelaySet } from "../relay/sets"; import { calculateRelaySetsFromFilters } from "../relay/sets/calculate"; import type { NDKSubscriptionOptions } from "../subscription"; import type { NDK } from "."; /** * Options on how to handle when a relay hint doesn't respond * with the requested event. * * When a tag includes a relay hint, and the relay hint doesn't come back * with the event, the fallback options are used to try to fetch the event * from somewhere else. */ export type NDKFetchFallbackOptions = { /** * Relay set to use as a fallback when the hint relay doesn't respond. * If not provided, the normal NDK calculation is used (whether explicit relays or outbox calculation) * Default is `undefined`. */ relaySet?: NDKRelaySet; /** * Type of fallback to use when the hint relay doesn't respond. * - "timeout" will wait for a timeout before falling back * - "eose" will wait for the EOSE before falling back * - "none" will not fall back * Default is "timeout". */ type: "timeout" | "eose" | "none"; /** * Timeout in milliseconds for the fallback relay. * Default is 1500ms. */ timeout?: number; }; function isValidHint(hint: string | undefined) { if (!hint || hint === "") return false; // Check if the hint is a valid URL try { new URL(hint); return true; } catch (_e) { return false; } } function isRelayHintConnected(ndk: NDK, hint: string | undefined) { if (!isValidHint(hint)) return false; return ndk.pool.isRelayConnected(hint!); } /** * @ignore */ export async function fetchEventFromTag( this: NDK, tag: NDKTag, originalEvent: NDKEvent, subOpts?: NDKSubscriptionOptions, fallback: NDKFetchFallbackOptions = { type: "timeout", }, ) { const d = this.debug.extend("fetch-event-from-tag"); const [_, id, hint] = tag; // If we are supposed to stick to the cache, just go with that // if (subOpts?.cacheUsage === NDKSubscriptionCacheUsage.ONLY_CACHE) { // return this.fetchEvent(id, subOpts); // } // XXXXX subOpts = {}; d("fetching event from tag", tag, subOpts, fallback); // If we are connected to the relay hint, try exclusively from that relay // if (isRelayHintConnected(this, hint)) { // d("fetching event from connected relay hint (%s)", normalizeRelayUrl(hint)); // let event = await this.fetchEvent(id, subOpts, this.pool.getRelay(hint)); // if (event) return event; // } // Check if we have a relay list for the author of the original event // and prefer to use those relays const authorRelays = getRelaysForSync(this, originalEvent.pubkey); if (authorRelays && authorRelays.size > 0) { d("fetching event from author relays %o", Array.from(authorRelays)); const relaySet = NDKRelaySet.fromRelayUrls(Array.from(authorRelays), this); const event = await this.fetchEvent(id, subOpts, relaySet); if (event) return event; } else { d("no author relays found for %s", originalEvent.pubkey, originalEvent); } // Attempt without relay hint on whatever NDK calculates const relaySet = calculateRelaySetsFromFilters(this, [{ ids: [id] }], this.pool); d("fetching event without relay hint", relaySet); const event = await this.fetchEvent(id, subOpts); if (event) return event; // If we didn't get the event, try to fetch in the relay hint if (hint && hint !== "") { const event = await this.fetchEvent(id, subOpts, this.pool.getRelay(hint, true, true, [{ ids: [id] }])); if (event) return event; } let result: NDKEvent | null | undefined; const relay = isValidHint(hint) ? this.pool.getRelay(hint, false, true, [{ ids: [id] }]) : undefined; const fetchMaybeWithRelayHint = new Promise<NDKEvent | null>((resolve) => { this.fetchEvent(id, subOpts, relay).then(resolve); }); // if we don't have a relay hint we don't need to setup a fallback if (!isValidHint(hint) || fallback.type === "none") { return fetchMaybeWithRelayHint; } /** * Fallback fetch promise. */ const fallbackFetchPromise = new Promise<NDKEvent | null>(async (resolve) => { const fallbackRelaySet = fallback.relaySet; const timeout = fallback.timeout ?? 1500; const timeoutPromise = new Promise<void>((resolve) => setTimeout(resolve, timeout)); // if this is a timeout fallback, we need to wait for the timeout to resolve if (fallback.type === "timeout") await timeoutPromise; if (result) { resolve(result); } else { d("fallback fetch triggered"); const fallbackEvent = await this.fetchEvent(id, subOpts, fallbackRelaySet); resolve(fallbackEvent); } }); switch (fallback.type) { case "timeout": return Promise.race([fetchMaybeWithRelayHint, fallbackFetchPromise]); case "eose": result = await fetchMaybeWithRelayHint; if (result) return result; return fallbackFetchPromise; } }