relay-info.service.ts raw

   1  import { simplifyUrl } from '@/lib/url'
   2  import indexDb from '@/services/indexed-db.service'
   3  import { TAwesomeRelayCollection, TRelayInfo } from '@/types'
   4  import DataLoader from 'dataloader'
   5  
   6  class RelayInfoService {
   7    static instance: RelayInfoService
   8  
   9    public static getInstance(): RelayInfoService {
  10      if (!RelayInfoService.instance) {
  11        RelayInfoService.instance = new RelayInfoService()
  12      }
  13      return RelayInfoService.instance
  14    }
  15  
  16    private awesomeRelayCollections: Promise<TAwesomeRelayCollection[]> | null = null
  17    private fetchDataloader = new DataLoader<string, TRelayInfo | undefined>(
  18      async (urls) => {
  19        const results = await Promise.allSettled(urls.map((url) => this._getRelayInfo(url)))
  20        return results.map((res) => (res.status === 'fulfilled' ? res.value : undefined))
  21      },
  22      { maxBatchSize: 1 }
  23    )
  24  
  25    async getRelayInfos(urls: string[]) {
  26      if (urls.length === 0) {
  27        return []
  28      }
  29      const relayInfos = await this.fetchDataloader.loadMany(urls)
  30      return relayInfos.map((relayInfo) => (relayInfo instanceof Error ? undefined : relayInfo))
  31    }
  32  
  33    async getRelayInfo(url: string) {
  34      return this.fetchDataloader.load(url)
  35    }
  36  
  37    async getAwesomeRelayCollections() {
  38      if (this.awesomeRelayCollections) return this.awesomeRelayCollections
  39  
  40      this.awesomeRelayCollections = (async () => {
  41        try {
  42          const res = await fetch(
  43            'https://raw.githubusercontent.com/CodyTseng/awesome-nostr-relays/master/dist/collections.json'
  44          )
  45          if (!res.ok) {
  46            throw new Error('Failed to fetch awesome relay collections')
  47          }
  48          const data = (await res.json()) as { collections: TAwesomeRelayCollection[] }
  49          return data.collections
  50        } catch (error) {
  51          console.error('Error fetching awesome relay collections:', error)
  52          return []
  53        }
  54      })()
  55  
  56      return this.awesomeRelayCollections
  57    }
  58  
  59    private async _getRelayInfo(url: string) {
  60      const fetchRelayInfo = async (background: boolean) => {
  61        const nip11 = await this.fetchRelayNip11(url)
  62        const relayInfo = {
  63          ...(nip11 ?? {}),
  64          url,
  65          shortUrl: simplifyUrl(url)
  66        }
  67  
  68        if (!Array.isArray(relayInfo.supported_nips)) {
  69          relayInfo.supported_nips = []
  70        }
  71  
  72        await indexDb.putRelayInfo(relayInfo)
  73  
  74        if (background) {
  75          this.fetchDataloader.clear(url)
  76          this.fetchDataloader.prime(url, relayInfo)
  77        }
  78  
  79        return relayInfo
  80      }
  81  
  82      const storedRelayInfo = await indexDb.getRelayInfo(url)
  83      if (storedRelayInfo) {
  84        fetchRelayInfo(true) // Update in background
  85        return storedRelayInfo
  86      }
  87  
  88      return fetchRelayInfo(false)
  89    }
  90  
  91    private async fetchRelayNip11(url: string) {
  92      try {
  93        const res = await fetch(url.replace('ws://', 'http://').replace('wss://', 'https://'), {
  94          headers: { Accept: 'application/nostr+json' }
  95        })
  96        return res.json() as Omit<TRelayInfo, 'url' | 'shortUrl'>
  97      } catch {
  98        return undefined
  99      }
 100    }
 101  }
 102  
 103  const instance = RelayInfoService.getInstance()
 104  export default instance
 105