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.
 
 
 
 

117 lines
3.4 KiB

import { normalizeHttpRelayUrl, normalizeRelayUrlByScheme, isHttpOrHttpsScheme } from '@/lib/url'
import type { TFeedSubRequest } from '@/types'
function relayDedupeKey(url: string): string {
return (normalizeRelayUrlByScheme(url) || url.trim()).toLowerCase()
}
/** Deduped relay URLs from all timeline subrequests (REQ order preserved). */
export function uniqueRelayUrlsFromSubRequests(requests: readonly TFeedSubRequest[]): string[] {
const seen = new Set<string>()
const out: string[] = []
for (const req of requests) {
for (const raw of req.urls) {
const n = normalizeRelayUrlByScheme(raw) || raw.trim()
if (!n) continue
const key = relayDedupeKey(n)
if (seen.has(key)) continue
seen.add(key)
out.push(n)
}
}
return out
}
/**
* Keep viewer kind-10243 HTTP index relays in a capped feed stack (they are easy to drop when
* favorites + NIP-65 WS fill {@link FAUX_SPELL_MAX_RELAYS}).
*/
export function pinHttpIndexRelaysInRelayCap(
capped: readonly string[],
sourceUrls: readonly string[],
maxRelays: number
): string[] {
const httpSources = sourceUrls
.map((u) => normalizeHttpRelayUrl(u) || (isHttpOrHttpsScheme(u.trim()) ? u.trim() : ''))
.filter(Boolean)
if (httpSources.length === 0) return [...capped]
const httpKeySet = new Set(httpSources.map((u) => u.toLowerCase()))
const out = [...capped]
const outKeys = new Set(out.map(relayDedupeKey))
for (const http of httpSources) {
const key = http.toLowerCase()
if (outKeys.has(key)) continue
while (out.length >= maxRelays) {
let dropped = false
for (let i = out.length - 1; i >= 0; i--) {
const candidate = out[i]!
const ck = relayDedupeKey(candidate)
if (httpKeySet.has(ck) || isHttpOrHttpsScheme(candidate.trim())) continue
out.splice(i, 1)
outKeys.delete(ck)
dropped = true
break
}
if (!dropped) break
}
if (out.length >= maxRelays) continue
out.push(http)
outKeys.add(key)
}
return out.slice(0, maxRelays)
}
/**
* Keep global mention / read aggregators in a capped stack (notifications `#p` REQs).
* Long NIP-65 lists otherwise fill {@link FAUX_SPELL_MAX_RELAYS} before index relays are reached.
*/
export function pinMentionRelaysInRelayCap(
capped: readonly string[],
mentionSources: readonly string[],
maxRelays: number,
minPinned: number
): string[] {
const pinKeys = new Set(
mentionSources
.slice(0, Math.max(0, minPinned))
.map((u) => relayDedupeKey(u))
.filter(Boolean)
)
if (pinKeys.size === 0) return [...capped]
const mentionKeySet = new Set(mentionSources.map((u) => relayDedupeKey(u)).filter(Boolean))
const out = [...capped]
const outKeys = new Set(out.map(relayDedupeKey))
for (const raw of mentionSources) {
const key = relayDedupeKey(raw)
if (!key || outKeys.has(key)) continue
while (out.length >= maxRelays) {
let dropped = false
for (let i = out.length - 1; i >= 0; i--) {
const candidate = out[i]!
const ck = relayDedupeKey(candidate)
if (pinKeys.has(ck) || mentionKeySet.has(ck)) continue
out.splice(i, 1)
outKeys.delete(ck)
dropped = true
break
}
if (!dropped) break
}
if (out.length >= maxRelays) continue
out.push(raw)
outKeys.add(key)
pinKeys.add(key)
if ([...pinKeys].every((k) => outKeys.has(k))) break
}
return out.slice(0, maxRelays)
}