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