97 lines
2.4 KiB
TypeScript
97 lines
2.4 KiB
TypeScript
import {request} from "@welshman/net"
|
|
import {clamp} from "@welshman/lib"
|
|
import type {TrustedEvent} from "@welshman/util"
|
|
|
|
const DEFAULT_ASSERTION_RELAYS =
|
|
"wss://nip85.brainstorm.world,wss://nip85.nosfabrica.com,wss://nip85.uid.ovh"
|
|
|
|
const toAssertionRelayUrl = (url: string) => {
|
|
const trimmed = url.trim()
|
|
|
|
if (!trimmed) return ""
|
|
if (trimmed.startsWith("http://")) return trimmed.replace(/^http:/, "ws:")
|
|
if (trimmed.startsWith("https://")) return trimmed.replace(/^https:/, "wss:")
|
|
if (!/^[a-z]+:\/\//i.test(trimmed)) return `wss://${trimmed}`
|
|
|
|
return trimmed
|
|
}
|
|
|
|
const ASSERTION_RELAYS = String(import.meta.env.VITE_ASSERTION_RELAYS || DEFAULT_ASSERTION_RELAYS)
|
|
.split(",")
|
|
.map(toAssertionRelayUrl)
|
|
.filter(Boolean)
|
|
|
|
const rankCache = new Map<string, number | null>()
|
|
const pendingRankRequests = new Map<string, Promise<number | null>>()
|
|
|
|
const getMedian = (values: number[]) => {
|
|
const sorted = [...values].sort((a, b) => a - b)
|
|
const middle = Math.floor(sorted.length / 2)
|
|
|
|
if (sorted.length % 2 === 1) {
|
|
return sorted[middle]
|
|
}
|
|
|
|
return (sorted[middle - 1] + sorted[middle]) / 2
|
|
}
|
|
|
|
const getRankFromEvent = (event: TrustedEvent) => {
|
|
const rankTag = event.tags?.find((tag: string[]) => tag[0] === "rank")
|
|
const rankValue = rankTag ? Number(rankTag[1]) : null
|
|
|
|
if (rankValue === null || !Number.isFinite(rankValue)) {
|
|
return null
|
|
}
|
|
|
|
return clamp([0, 100], rankValue)
|
|
}
|
|
|
|
const fetchPubkeyRank = async (pubkey: string): Promise<number | null> => {
|
|
if (ASSERTION_RELAYS.length === 0) {
|
|
return 50
|
|
}
|
|
|
|
try {
|
|
const events = await request({
|
|
relays: ASSERTION_RELAYS,
|
|
autoClose: true,
|
|
filters: [{kinds: [30382], "#d": [pubkey], limit: 8}],
|
|
})
|
|
|
|
const ranks = events.map(getRankFromEvent).filter((rank): rank is number => rank !== null)
|
|
|
|
if (ranks.length === 0) {
|
|
return 50
|
|
}
|
|
|
|
const median = getMedian(ranks)
|
|
|
|
return median
|
|
} catch {
|
|
return 50
|
|
}
|
|
}
|
|
|
|
export const getPubkeyRank = (pubkey: string): Promise<number | null> => {
|
|
if (rankCache.has(pubkey)) {
|
|
return Promise.resolve(rankCache.get(pubkey) ?? 50)
|
|
}
|
|
|
|
const pending = pendingRankRequests.get(pubkey)
|
|
|
|
if (pending) {
|
|
return pending
|
|
}
|
|
|
|
const requestPromise = fetchPubkeyRank(pubkey).then(rank => {
|
|
rankCache.set(pubkey, rank ?? 50)
|
|
pendingRankRequests.delete(pubkey)
|
|
|
|
return rank ?? 50
|
|
})
|
|
|
|
pendingRankRequests.set(pubkey, requestPromise)
|
|
|
|
return requestPromise
|
|
}
|