2024-08-15 22:13:39 +05:00
|
|
|
import axios from 'axios'
|
|
|
|
import { Event, Filter } from 'nostr-tools'
|
2024-08-07 15:52:14 +03:00
|
|
|
import { RelayList } from 'nostr-tools/kinds'
|
2024-08-15 22:13:39 +05:00
|
|
|
import { relayController } from '../controllers/RelayController.ts'
|
2024-08-07 15:52:14 +03:00
|
|
|
import { localCache } from '../services'
|
2024-08-15 22:13:39 +05:00
|
|
|
import { setMostPopularRelaysAction } from '../store/actions'
|
|
|
|
import store from '../store/store'
|
|
|
|
import { RelayMap, RelayReadStats, RelaySet, RelayStats } from '../types'
|
2024-08-07 15:52:14 +03:00
|
|
|
import { ONE_WEEK_IN_MS, SIGIT_RELAY } from './const.ts'
|
|
|
|
|
2024-08-07 18:24:12 +03:00
|
|
|
const READ_MARKER = 'read'
|
|
|
|
const WRITE_MARKER = 'write'
|
2024-08-07 15:52:14 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempts to find a relay list from the provided lookUpRelays.
|
|
|
|
* If the relay list is found, it will be added to the user relay list metadata.
|
|
|
|
* @param lookUpRelays
|
|
|
|
* @param hexKey
|
|
|
|
* @return found relay list or null
|
|
|
|
*/
|
2024-08-07 18:24:12 +03:00
|
|
|
const findRelayListAndUpdateCache = async (
|
|
|
|
lookUpRelays: string[],
|
|
|
|
hexKey: string
|
|
|
|
): Promise<Event | null> => {
|
2024-08-07 15:52:14 +03:00
|
|
|
try {
|
|
|
|
const eventFilter: Filter = {
|
|
|
|
kinds: [RelayList],
|
|
|
|
authors: [hexKey]
|
|
|
|
}
|
2024-08-15 22:13:39 +05:00
|
|
|
|
|
|
|
const event = await relayController.fetchEvent(eventFilter, lookUpRelays)
|
2024-08-07 15:52:14 +03:00
|
|
|
if (event) {
|
|
|
|
await localCache.addUserRelayListMetadata(event)
|
|
|
|
}
|
|
|
|
return event
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempts to find a relay list in cache. If it is present, it will check that the cached event is not
|
|
|
|
* older than one week.
|
|
|
|
* @param hexKey
|
|
|
|
* @return RelayList event if it's not older than a week; otherwise null
|
|
|
|
*/
|
|
|
|
const findRelayListInCache = async (hexKey: string): Promise<Event | null> => {
|
|
|
|
try {
|
|
|
|
// Attempt to retrieve the metadata event from the local cache
|
2024-08-07 18:24:12 +03:00
|
|
|
const cachedRelayListMetadataEvent =
|
|
|
|
await localCache.getUserRelayListMetadata(hexKey)
|
2024-08-07 15:52:14 +03:00
|
|
|
|
|
|
|
// Check if the cached event is not older than one week
|
2024-08-07 18:24:12 +03:00
|
|
|
if (
|
|
|
|
cachedRelayListMetadataEvent &&
|
|
|
|
isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt)
|
|
|
|
) {
|
2024-08-07 15:52:14 +03:00
|
|
|
return cachedRelayListMetadataEvent.event
|
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Transforms a list of relay tags from a Nostr Event to a RelaySet.
|
|
|
|
* @param tags
|
|
|
|
*/
|
|
|
|
const getUserRelaySet = (tags: string[][]): RelaySet => {
|
|
|
|
return tags
|
|
|
|
.filter(isRelayTag)
|
|
|
|
.reduce<RelaySet>(toRelaySet, getDefaultRelaySet())
|
|
|
|
}
|
|
|
|
|
|
|
|
const getDefaultRelaySet = (): RelaySet => ({
|
|
|
|
read: [SIGIT_RELAY],
|
|
|
|
write: [SIGIT_RELAY]
|
|
|
|
})
|
|
|
|
|
|
|
|
const getDefaultRelayMap = (): RelayMap => ({
|
|
|
|
[SIGIT_RELAY]: { write: true, read: true }
|
|
|
|
})
|
|
|
|
|
|
|
|
const isOlderThanOneWeek = (cachedAt: number) => {
|
|
|
|
return Date.now() - cachedAt < ONE_WEEK_IN_MS
|
|
|
|
}
|
|
|
|
|
|
|
|
const isRelayTag = (tag: string[]): boolean => tag[0] === 'r'
|
|
|
|
|
|
|
|
const toRelaySet = (obj: RelaySet, tag: string[]): RelaySet => {
|
|
|
|
if (tag.length >= 3) {
|
|
|
|
const marker = tag[2]
|
|
|
|
|
|
|
|
if (marker === READ_MARKER) {
|
|
|
|
obj.read.push(tag[1])
|
2024-08-07 18:24:12 +03:00
|
|
|
} else if (marker === WRITE_MARKER) {
|
2024-08-07 15:52:14 +03:00
|
|
|
obj.write.push(tag[1])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tag.length === 2) {
|
|
|
|
obj.read.push(tag[1])
|
|
|
|
obj.write.push(tag[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2024-08-15 22:13:39 +05:00
|
|
|
/**
|
|
|
|
* Provides most popular relays.
|
|
|
|
* @param numberOfTopRelays - number representing how many most popular relays to provide
|
|
|
|
* @returns - promise that resolves into an array of most popular relays
|
|
|
|
*/
|
|
|
|
const getMostPopularRelays = async (
|
|
|
|
numberOfTopRelays: number = 30
|
|
|
|
): Promise<string[]> => {
|
|
|
|
const mostPopularRelaysState = store.getState().relays?.mostPopular
|
|
|
|
|
|
|
|
// return most popular relays from app state if present
|
|
|
|
if (mostPopularRelaysState) return mostPopularRelaysState
|
|
|
|
|
|
|
|
// relays in env
|
|
|
|
const { VITE_MOST_POPULAR_RELAYS } = import.meta.env
|
|
|
|
const hardcodedPopularRelays = (VITE_MOST_POPULAR_RELAYS || '').split(' ')
|
|
|
|
const url = `https://stats.nostr.band/stats_api?method=stats`
|
|
|
|
|
|
|
|
const response = await axios.get<RelayStats>(url).catch(() => undefined)
|
|
|
|
|
|
|
|
if (!response) {
|
|
|
|
return hardcodedPopularRelays //return hardcoded relay list
|
|
|
|
}
|
|
|
|
|
|
|
|
const data = response.data
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
return hardcodedPopularRelays //return hardcoded relay list
|
|
|
|
}
|
|
|
|
|
|
|
|
const apiTopRelays = data.relay_stats.user_picks.read_relays
|
|
|
|
.slice(0, numberOfTopRelays)
|
|
|
|
.map((relay: RelayReadStats) => relay.d)
|
|
|
|
|
|
|
|
if (!apiTopRelays.length) {
|
|
|
|
return Promise.reject(`Couldn't fetch popular relays.`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (store.getState().auth?.loggedIn) {
|
|
|
|
store.dispatch(setMostPopularRelaysAction(apiTopRelays))
|
|
|
|
}
|
|
|
|
|
|
|
|
return apiTopRelays
|
|
|
|
}
|
|
|
|
|
2024-08-07 15:52:14 +03:00
|
|
|
export {
|
|
|
|
findRelayListAndUpdateCache,
|
|
|
|
findRelayListInCache,
|
2024-08-07 17:54:45 +03:00
|
|
|
getDefaultRelayMap,
|
2024-08-15 22:13:39 +05:00
|
|
|
getDefaultRelaySet,
|
|
|
|
getMostPopularRelays,
|
|
|
|
getUserRelaySet,
|
2024-08-07 18:24:12 +03:00
|
|
|
isOlderThanOneWeek
|
|
|
|
}
|