You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

133 lines
5.2 KiB

import { SEARCHABLE_RELAY_URLS } from '@/constants'
import { AGGR_NOSTR_LAND_WSS } from '@/lib/nostr-land-aggr'
import { normalizeAnyRelayUrl } from '@/lib/url'
/**
* True when the URL is `wss://aggr.nostr.land` (aggregator read/search endpoint).
*/
export function relayUrlIsAggrNostrLand(url: string): boolean {
const raw = url.trim()
if (!raw) return false
const normalized = (normalizeAnyRelayUrl(raw) || raw).toLowerCase()
const aggrCanon = (normalizeAnyRelayUrl(AGGR_NOSTR_LAND_WSS) || AGGR_NOSTR_LAND_WSS).toLowerCase()
if (normalized === aggrCanon) return true
try {
const u = new URL(normalized.replace(/^ws:\/\//i, 'http://').replace(/^wss:\/\//i, 'https://'))
return u.hostname.toLowerCase() === 'aggr.nostr.land'
} catch {
return /^wss:\/\/aggr\.nostr\.land\/?$/i.test(normalized)
}
}
/** Drop `wss://aggr.nostr.land` from REQ stacks where it must not appear (e.g. home feeds). */
export function stripNostrLandAggrFromRelayUrls(urls: readonly string[]): string[] {
return urls.filter((url) => !relayUrlIsAggrNostrLand(url))
}
/**
* True when any URL is the canonical nostr.land **inbox** relay (`wss://nostr.land`), i.e. host `nostr.land`
* exactly — not `aggr.nostr.land`, `hist.nostr.land`, or other subdomains.
*/
export function relayUrlsIncludeCanonicalNostrLandRelay(urls: readonly string[]): boolean {
return urls.some((url) => {
const normalized = normalizeAnyRelayUrl(url) || String(url).trim()
if (!normalized) return false
try {
const parsed = new URL(
normalized.replace(/^ws:\/\//i, 'http://').replace(/^wss:\/\//i, 'https://')
)
return parsed.hostname.toLowerCase() === 'nostr.land'
} catch {
return false
}
})
}
/** @deprecated Use {@link relayUrlsIncludeCanonicalNostrLandRelay}. */
export function relayUrlsMentionNostrLandDomain(urls: readonly string[]): boolean {
return relayUrlsIncludeCanonicalNostrLandRelay(urls)
}
let viewerStackMentionsNostrLand = false
/**
* Synced from the logged-in viewer’s favorites and NIP-65 / cache / HTTP relay lists.
* When true, {@link AGGR_NOSTR_LAND_WSS} is treated as a search relay and inbox-tier read relay.
*/
export function syncViewerRelayStackNostrLandAggrEligible(urls: readonly string[]): boolean {
viewerStackMentionsNostrLand = relayUrlsIncludeCanonicalNostrLandRelay(urls)
return viewerStackMentionsNostrLand
}
export function getViewerRelayStackNostrLandAggrEligible(): boolean {
return viewerStackMentionsNostrLand
}
/** Aggr URL to merge into NIP-50 / searchable relay sets when the viewer lists `wss://nostr.land`. */
export function getViewerNostrLandAggrSearchRelayUrls(): string[] {
if (!viewerStackMentionsNostrLand) return []
const n = normalizeAnyRelayUrl(AGGR_NOSTR_LAND_WSS) || AGGR_NOSTR_LAND_WSS
return n ? [n] : []
}
/**
* Search / wide-id fetch relays: aggr first when eligible, then {@link SEARCHABLE_RELAY_URLS}.
* Use for reply-to blurbs, thread parent/root fallback, embed wide pass, etc. — not home timeline OP REQs.
*/
export function getAggrAwareSearchRelayUrls(): string[] {
const seen = new Set<string>()
const out: string[] = []
const add = (raw: string) => {
const n = normalizeAnyRelayUrl(raw) || raw.trim()
if (!n) return
const k = n.toLowerCase()
if (seen.has(k)) return
seen.add(k)
out.push(n)
}
for (const u of getViewerNostrLandAggrSearchRelayUrls()) add(u)
for (const u of SEARCHABLE_RELAY_URLS) add(u)
return out
}
/** Drop aggr unless the viewer has `wss://nostr.land` on favorites or relay lists. */
export function filterAggrNostrLandUnlessViewerEligible(urls: readonly string[]): string[] {
if (viewerStackMentionsNostrLand) return [...urls]
return urls.filter((u) => !relayUrlIsAggrNostrLand(u))
}
/** URLs to pass to {@link syncViewerRelayStackNostrLandAggrEligible} (favorites + NIP-65 + cache + HTTP lists). */
export function urlsForViewerNostrLandAggrEligibilitySync(options: {
favoriteRelayUrls?: readonly string[]
relayListRead?: readonly string[]
relayListWrite?: readonly string[]
cacheRelayRead?: readonly string[]
httpRelayRead?: readonly string[]
}): string[] {
return [
...(options.favoriteRelayUrls ?? []),
...(options.relayListRead ?? []),
...(options.relayListWrite ?? []),
...(options.cacheRelayRead ?? []),
...(options.httpRelayRead ?? [])
]
}
/**
* Prepend aggr for event-by-id lookups (threads, embeds, parent previews, comprehensive fetch).
* Home OP timelines must not use this — use {@link buildAllFavoritesFeedRelayUrls} / `nostrLandAggr: 'never'`.
*/
export function prependAggrForEventLookupRelayUrls(relayUrls: readonly string[]): string[] {
return prependAggrNostrLandIfViewerEligible(relayUrls)
}
/** Deduped prepend of aggr when the viewer opted into nostr.land relays (see sync…). */
export function prependAggrNostrLandIfViewerEligible(relayUrls: readonly string[]): string[] {
if (!viewerStackMentionsNostrLand) return [...relayUrls]
const aggrNorm = (normalizeAnyRelayUrl(AGGR_NOSTR_LAND_WSS) || AGGR_NOSTR_LAND_WSS).toLowerCase()
const norm = (u: string) => (normalizeAnyRelayUrl(u) || u.trim()).toLowerCase()
if (relayUrls.some((u) => norm(u) === aggrNorm)) {
return [...relayUrls]
}
return [AGGR_NOSTR_LAND_WSS, ...relayUrls]
}