mirror of
https://github.com/dergigi/boris.git
synced 2026-01-16 13:24:20 +01:00
181 lines
4.5 KiB
TypeScript
181 lines
4.5 KiB
TypeScript
import { RelayPool } from 'applesauce-relay'
|
|
import { NostrEvent } from 'nostr-tools'
|
|
import { queryEvents } from './dataFetch'
|
|
|
|
export interface UserRelayInfo {
|
|
url: string
|
|
mode?: 'read' | 'write' | 'both'
|
|
}
|
|
|
|
/**
|
|
* Loads user's relay list from kind 10002 (NIP-65)
|
|
*/
|
|
export async function loadUserRelayList(
|
|
relayPool: RelayPool,
|
|
pubkey: string,
|
|
options?: {
|
|
onUpdate?: (relays: UserRelayInfo[]) => void
|
|
}
|
|
): Promise<UserRelayInfo[]> {
|
|
try {
|
|
|
|
|
|
// Try querying with streaming callback for faster results
|
|
const events: NostrEvent[] = []
|
|
const eventsMap = new Map<string, NostrEvent>()
|
|
|
|
const result = await queryEvents(relayPool, {
|
|
kinds: [10002],
|
|
authors: [pubkey],
|
|
limit: 10
|
|
}, {
|
|
onEvent: (evt) => {
|
|
// Deduplicate by id and keep most recent
|
|
const existing = eventsMap.get(evt.id)
|
|
if (!existing || evt.created_at > existing.created_at) {
|
|
eventsMap.set(evt.id, evt)
|
|
// Update events array with deduplicated events
|
|
events.length = 0
|
|
events.push(...Array.from(eventsMap.values()))
|
|
|
|
// Stream immediate updates to caller using the newest event
|
|
if (options?.onUpdate) {
|
|
const tags = evt.tags || []
|
|
const relays: UserRelayInfo[] = []
|
|
for (const tag of tags) {
|
|
if (tag[0] === 'r' && tag[1]) {
|
|
const url = tag[1]
|
|
const mode = (tag[2] as 'read' | 'write' | undefined) || 'both'
|
|
relays.push({ url, mode })
|
|
}
|
|
}
|
|
if (relays.length > 0) {
|
|
options.onUpdate(relays)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
// Use the streaming results if we got any, otherwise fall back to the full result
|
|
const finalEvents = events.length > 0 ? events : result
|
|
|
|
// Also try a broader query to see if we get any events at all
|
|
await queryEvents(relayPool, {
|
|
kinds: [10002],
|
|
limit: 5
|
|
})
|
|
|
|
|
|
|
|
if (finalEvents.length === 0) return []
|
|
|
|
// Get most recent event
|
|
const sortedEvents = finalEvents.sort((a, b) => b.created_at - a.created_at)
|
|
const relayListEvent = sortedEvents[0]
|
|
|
|
const relays: UserRelayInfo[] = []
|
|
for (const tag of relayListEvent.tags) {
|
|
if (tag[0] === 'r' && tag[1]) {
|
|
const url = tag[1]
|
|
const mode = tag[2] as 'read' | 'write' | undefined
|
|
relays.push({
|
|
url,
|
|
mode: mode || 'both'
|
|
})
|
|
}
|
|
}
|
|
|
|
return relays
|
|
} catch (error) {
|
|
console.error('Failed to load user relay list:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads blocked relays from kind 10006 (NIP-51 mute list)
|
|
*/
|
|
export async function loadBlockedRelays(
|
|
relayPool: RelayPool,
|
|
pubkey: string
|
|
): Promise<string[]> {
|
|
try {
|
|
const events = await queryEvents(relayPool, {
|
|
kinds: [10006],
|
|
authors: [pubkey]
|
|
})
|
|
|
|
if (events.length === 0) return []
|
|
|
|
// Get most recent event
|
|
const sortedEvents = events.sort((a, b) => b.created_at - a.created_at)
|
|
const muteListEvent = sortedEvents[0]
|
|
|
|
const blocked: string[] = []
|
|
for (const tag of muteListEvent.tags) {
|
|
if (tag[0] === 'r' && tag[1]) {
|
|
blocked.push(tag[1])
|
|
}
|
|
}
|
|
|
|
return blocked
|
|
} catch (error) {
|
|
console.error('Failed to load blocked relays:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Computes final relay set by merging inputs and removing blocked relays
|
|
*/
|
|
export function computeRelaySet(params: {
|
|
hardcoded: string[]
|
|
bunker?: string[]
|
|
userList?: UserRelayInfo[]
|
|
blocked?: string[]
|
|
alwaysIncludeLocal: string[]
|
|
}): string[] {
|
|
const {
|
|
hardcoded,
|
|
bunker = [],
|
|
userList = [],
|
|
blocked = [],
|
|
alwaysIncludeLocal
|
|
} = params
|
|
|
|
const relaySet = new Set<string>()
|
|
const blockedSet = new Set(blocked)
|
|
|
|
// Helper to check if relay should be included
|
|
const shouldInclude = (url: string): boolean => {
|
|
// Always include local relays
|
|
if (alwaysIncludeLocal.includes(url)) return true
|
|
// Otherwise check if blocked
|
|
return !blockedSet.has(url)
|
|
}
|
|
|
|
// Add hardcoded relays
|
|
for (const url of hardcoded) {
|
|
if (shouldInclude(url)) relaySet.add(url)
|
|
}
|
|
|
|
// Add bunker relays
|
|
for (const url of bunker) {
|
|
if (shouldInclude(url)) relaySet.add(url)
|
|
}
|
|
|
|
// Add user relays (treating 'both' and 'read' as applicable for queries)
|
|
for (const relay of userList) {
|
|
if (shouldInclude(relay.url)) relaySet.add(relay.url)
|
|
}
|
|
|
|
// Always ensure local relays are present
|
|
for (const url of alwaysIncludeLocal) {
|
|
relaySet.add(url)
|
|
}
|
|
|
|
return Array.from(relaySet)
|
|
}
|
|
|