Merge pull request #55 from Permissionless-Software-Foundation/dh-relay-selection

feat(nostr): Added relay selection to Configuration page
This commit is contained in:
Chris Troutner
2025-08-23 11:48:02 -07:00
committed by GitHub
10 changed files with 501 additions and 69 deletions
@@ -6,6 +6,7 @@
// Global npm libraries
import React from 'react'
import ServerSelectView from './select-server-view'
import RelaySelectionView from './relay-selection-view'
function ConfigurationView (props) {
const { appData } = props
@@ -13,6 +14,7 @@ function ConfigurationView (props) {
return (
<>
<ServerSelectView appData={appData} />
<RelaySelectionView appData={appData} />
</>
)
}
@@ -0,0 +1,333 @@
/*
This component is a View that allows the user to manage Nostr relay settings.
*/
// Global npm libraries
import React, { useState } from 'react'
import { Row, Col, Form, Card, Button } from 'react-bootstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faShareAlt, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
function RelaySelectionView (props) {
const { appData } = props
const { relaysData, updateRelaysData, restoreRelaysData } = appData
console.log('relaysData', relaysData)
const [newRelayAddress, setNewRelayAddress] = useState('')
const addRelay = (newRelay) => {
const exist = relaysData.find(relay => relay.address === newRelay)
if (!exist) {
relaysData.push({ address: newRelay, read: true, write: true })
updateRelaysData(relaysData)
}
}
// Delete relay from relaysData array
const deleteRelay = (relayToDelete) => {
const index = relaysData.findIndex(relay => relay.address === relayToDelete.address)
if (index !== -1) {
relaysData.splice(index, 1)
updateRelaysData(relaysData)
}
}
const handleReadToggle = (relayToToggleRead) => {
const index = relaysData.findIndex(relay => relay.address === relayToToggleRead.address)
if (index !== -1) {
relaysData[index].read = !relaysData[index].read
updateRelaysData(relaysData)
}
}
const handleWriteToggle = (relayToToggleWrite) => {
const index = relaysData.findIndex(relay => relay.address === relayToToggleWrite.address)
if (index !== -1) {
relaysData[index].write = !relaysData[index].write
updateRelaysData(relaysData)
}
}
return (
<>
<Card className='m-3'>
<Card.Body>
<Row>
<Col style={{ textAlign: 'center' }}>
<h2>Nostr Relay Configuration</h2>
<p>
Manage your Nostr relay connections. Configure read and write permissions for each relay.
</p>
</Col>
</Row>
<hr />
<Row className='mb-3'>
<Col className='text-end'>
<Button
onClick={() => restoreRelaysData()}
variant='outline-secondary'
style={{ minWidth: '120px' }}
>
Restore defaults
</Button>
</Col>
</Row>
<Row>
<Col>
<div
className='relay-table-container' style={{
backgroundColor: 'transparent',
borderRadius: '12px',
padding: '20px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}
>
<div
className='relay-table-header' style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '20px',
paddingBottom: '15px',
borderBottom: '2px solid #e9ecef'
}}
>
<h5 style={{ margin: 0, color: '#495057', fontWeight: '600' }}>Relay Connections</h5>
<span style={{
fontSize: '0.85em',
color: '#6c757d',
backgroundColor: '#e9ecef',
padding: '4px 12px',
borderRadius: '20px'
}}
>
{relaysData.length} relays
</span>
</div>
<div className='relay-list'>
{relaysData.map((relay, index) => (
<div
key={`relay-${index}`} style={{
backgroundColor: 'white',
borderRadius: '8px',
padding: '16px',
marginBottom: '12px',
border: '1px solid #e9ecef',
transition: 'all 0.2s ease',
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
}}
>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'wrap',
gap: '12px'
}}
>
{/* Relay Address */}
<div style={{ flex: '1', minWidth: '200px' }}>
<div style={{
fontFamily: 'monospace',
fontSize: '0.9em',
color: '#495057',
fontWeight: '500',
wordBreak: 'break-all'
}}
>
{relay.address}
</div>
</div>
{/* Controls */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
flexWrap: 'wrap'
}}
>
{/* Read/Write Toggles */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
backgroundColor: '#f8f9fa',
padding: '8px 12px',
borderRadius: '6px',
border: '1px solid #dee2e6'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style={{
fontSize: '0.8em',
color: '#6c757d',
fontWeight: '500',
minWidth: '35px'
}}
>
Read
</span>
<Form.Check
type='switch'
onClick={() => handleReadToggle(relay)}
checked={relay.read}
className='mb-0'
style={{ margin: 0 }}
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style={{
fontSize: '0.8em',
color: '#6c757d',
fontWeight: '500',
minWidth: '35px'
}}
>
Write
</span>
<Form.Check
type='switch'
onClick={() => handleWriteToggle(relay)}
checked={relay.write}
className='mb-0'
style={{ margin: 0 }}
/>
</div>
</div>
{/* Action Buttons */}
<div style={{ display: 'flex', gap: '8px' }}>
<Button
variant='outline-primary'
size='sm'
className='d-flex align-items-center gap-1'
style={{
minWidth: '65px',
fontSize: '0.8em',
padding: '4px 8px',
borderWidth: '1px'
}}
>
<FontAwesomeIcon icon={faShareAlt} size='xs' />
Share
</Button>
<Button
onClick={() => deleteRelay(relay)}
variant='outline-danger'
size='sm'
className='d-flex align-items-center gap-1'
style={{
minWidth: '65px',
fontSize: '0.8em',
padding: '4px 8px',
borderWidth: '1px'
}}
>
<FontAwesomeIcon icon={faTrashAlt} size='xs' />
Delete
</Button>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</Col>
</Row>
<Row className='mt-4'>
<Col>
<div
className='add-relay-container' style={{
backgroundColor: 'white',
borderRadius: '12px',
padding: '20px',
border: '1px solid #e9ecef',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}
>
<div
className='add-relay-header' style={{
marginBottom: '16px',
paddingBottom: '12px',
borderBottom: '2px solid #e9ecef'
}}
>
<h6 style={{
margin: 0,
color: '#495057',
fontWeight: '600',
fontSize: '1rem'
}}
>
Add New Relay
</h6>
<p style={{
margin: '4px 0 0 0',
fontSize: '0.85em',
color: '#6c757d'
}}
>
Enter the URL of a Nostr relay to add it to your configuration
</p>
</div>
<div
className='add-relay-form' style={{
display: 'flex',
gap: '12px',
alignItems: 'end'
}}
>
<div style={{ flex: '1' }}>
<Form.Label
htmlFor='newRelayAddress'
style={{
fontSize: '0.9em',
fontWeight: '500',
color: '#495057',
marginBottom: '8px'
}}
>
Relay Address
</Form.Label>
<Form.Control
type='text'
id='newRelayAddress'
placeholder='wss://your-relay-address.com'
value={newRelayAddress}
onChange={(e) => setNewRelayAddress(e.target.value)}
style={{
fontFamily: 'monospace',
fontSize: '0.9em',
border: '1px solid #dee2e6',
borderRadius: '6px',
padding: '10px 12px'
}}
/>
</div>
<Button
onClick={() => addRelay(newRelayAddress)}
variant='primary'
style={{
minWidth: '80px',
height: '38px',
borderRadius: '6px',
fontSize: '0.9em',
fontWeight: '500',
padding: '8px 16px'
}}
>
Add
</Button>
</div>
</div>
</Col>
</Row>
</Card.Body>
</Card>
</>
)
}
export default RelaySelectionView
+3 -3
View File
@@ -15,14 +15,14 @@ import GetBalance from './balance'
import Wallet from './bch-wallet'
import Placeholder2 from './placeholder2'
import Placeholder3 from './placeholder3'
// import ServerSelectView from './servers/select-server-view'
import ServerSelectView from './configuration/select-server-view.js'
// import SelectServerButton from './servers/select-server-button'
import NftsForSale from './nfts-for-sale'
import BchSend from './bch-send'
import SlpTokens from './slp-tokens'
import SweepWif from './sweep/index.js'
import SignMessage from './sign/index.js'
import ServerSelectView from './configuration/select-server-view'
import ConfigurationView from './configuration/index.js'
import NostrPost from './nostr/nostr-post/index.js'
import Profile from './nostr/profile/index.js'
import Feeds from './nostr/feeds/index.js'
@@ -47,7 +47,7 @@ function AppBody (props) {
<Route path='/servers' element={<ServerSelectView appData={appData} />} />
<Route path='/sweep' element={<SweepWif appData={appData} />} />
<Route path='/sign' element={<SignMessage appData={appData} />} />
<Route path='/configuration' element={<ServerSelectView appData={appData} />} />
<Route path='/configuration' element={<ConfigurationView appData={appData} />} />
<Route path='/nostr-post' element={<NostrPost appData={appData} />} />
<Route path='/profile/:npub' element={<Profile appData={appData} />} />
<Route path='/feeds' element={<Feeds appData={appData} />} />
@@ -43,6 +43,7 @@ function ContentCreators (props) {
useEffect(() => {
const loadCreators = async () => {
try {
console.log('loadCreators()')
const creatorsRes = await axios.get(`${SERVER}/sm/list/all/0`)
const creators = creatorsRes.data
// console.log('creators', creators)
@@ -52,8 +53,9 @@ function ContentCreators (props) {
for (let i = 0; i < creators.length; i++) {
try {
const creator = creators[i]
const profile = await appData.nostrQueries.getProfile(creator.pubkey)
const profileRes = await appData.nostrQueries.getProfile(creator.pubkey)
let profile = profileRes
if (!profileRes) profile = {}
// the folowing lines should re-render the ContentCard
setCreators(prevCreators => {
const updatedCreators = [...prevCreators]
@@ -73,7 +75,7 @@ function ContentCreators (props) {
loadCreators()
getFollowList()
}
}, [loaded, followList, getFollowList, appData])
}, [loaded, getFollowList, appData])
const filteredCreators = useCallback(() => {
if (!creators || creators.length === 0) {
@@ -12,7 +12,6 @@ import * as nip19 from 'nostr-tools/nip19'
import { finalizeEvent } from 'nostr-tools/pure'
import { Relay } from 'nostr-tools/relay'
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
import config from '../../../../config/index.js'
// Local libraries
import CopyOnClick from '../../bch-wallet/copy-on-click.js'
@@ -20,7 +19,7 @@ import NostrFormat from '../nostr-format'
function FeedCard (props) {
const { post, appData, profiles } = props
const { nostrKeyPair } = appData.bchWalletState
const { nostrKeyPair, writeRelays } = appData.bchWalletState
const [profile, setProfile] = useState(profiles[post.pubkey])
@@ -104,7 +103,7 @@ function FeedCard (props) {
}
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
config.nostrRelays.map(async (relayUrl) => {
writeRelays.map(async (relayUrl) => {
try {
// Sign the post
const signedEvent = finalizeEvent(eventTemplate, privateKeyBin)
@@ -4,41 +4,21 @@
// Global npm libraries
import React, { useEffect, useState } from 'react'
import { Container, Spinner } from 'react-bootstrap'
import { RelayPool } from 'nostr'
// Local libraries
import config from '../../../../config'
import FeedCard from './feed-card'
function Following (props) {
const { appData, posts, profiles } = props
const { nostrQueries } = appData
const [followingPosts, setFollowingPosts] = useState([])
const [loaded, setLoaded] = useState(false)
useEffect(() => {
const getFollowingPosts = async () => {
const followingList = await new Promise((resolve, reject) => {
let list = []
const { nostrKeyPair } = appData.bchWalletState
const pool = RelayPool(config.nostrRelays)
pool.on('open', relay => {
relay.subscribe('subid', { limit: 1, kinds: [3], authors: [nostrKeyPair.pubHex] })
})
pool.on('eose', relay => {
relay.close()
/** This ensures to return an empty array if no records are found!
* Applies for new users that don't have a follow list
*/
resolve(list)
})
pool.on('event', (relay, subId, ev) => {
list = ev.tags
resolve(list)
})
})
console.log('getFollowingPosts')
const { nostrKeyPair } = appData.bchWalletState
const followingList = await nostrQueries.getFollowList(nostrKeyPair.pubHex)
// Filter posts by following list
let filteredPosts = []
@@ -55,7 +35,7 @@ function Following (props) {
setLoaded(true)
}
getFollowingPosts()
}, [appData, posts])
}, [appData, posts, nostrQueries])
return (
<Container className='mt-4 mb-5' style={{ marginBottom: '50px' }}>
@@ -11,11 +11,10 @@ import { Relay } from 'nostr-tools/relay'
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
// Local libraries
import config from '../../../../config'
function ProfilePost (props) {
const { appData } = props
const { bchWalletState } = appData
const { bchWalletState, writeRelays } = appData
const [accordionKey, setAccordionKey] = useState(null)
const [onFetch, setOnFetch] = useState(false)
const [formLoaded, setFormLoaded] = useState(false)
@@ -90,7 +89,7 @@ function ProfilePost (props) {
console.log('signedEvent: ', signedEvent)
// Publish the post to each relay.
config.nostrRelays.map(async (relayUrl) => {
writeRelays.map(async (relayUrl) => {
try {
// Connect to a relay.
const relay = await Relay.connect(relayUrl)
@@ -11,12 +11,11 @@ import { Relay } from 'nostr-tools/relay'
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
// Local libraries
import config from '../../../../config'
function PublicPost (props) {
const [accordionKey, setAccordionKey] = useState('0')
const [onFetch, setOnFetch] = useState(false)
const { bchWalletState } = props.appData
const { bchWalletState, writeRelays } = props.appData
const [formData, setFormData] = useState({
content: ''
})
@@ -64,7 +63,7 @@ function PublicPost (props) {
console.log('signedEvent: ', signedEvent)
// Publish the post to each relay.
config.nostrRelays.map(async (relayUrl) => {
writeRelays.map(async (relayUrl) => {
try {
// Connect to a relay.
const relay = await Relay.connect(relayUrl)
+59 -11
View File
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useRef, useState } from 'react'
// import { useQueryParam, StringParam } from 'use-query-params'
import useLocalStorageState from 'use-local-storage-state'
import AppUtil from '../util'
@@ -8,19 +8,24 @@ import { useLocation } from 'react-router-dom'
function useAppState () {
const location = useLocation()
// Default local storage object
const localStorageDefault = {
serverUrl: 'https://free-bch.fullstack.cash', // Default server
relays: [
{ address: 'wss://nostr-relay.psfoundation.info', read: true, write: true },
{ address: 'wss://relay.damus.io', read: true, write: true }
],
nftData: {},
lastFeedTab: 'feed'
}
// Load Local storage Data
const [lsState, setLSState, { removeItem }] = useLocalStorageState('bchWalletState-template', {
ssr: true,
defaultValue: {
serverUrl: 'https://free-bch.fullstack.cash' // Default server
},
nftData: {},
lastFeedTab: 'feed'
defaultValue: localStorageDefault
})
console.log('lsState: ', lsState)
// Initialize data states
const [serverUrl, setServerUrl] = useState(lsState.serverUrl) // Default server url
const [menuState, setMenuState] = useState(0)
@@ -28,7 +33,7 @@ function useAppState () {
const [servers, setServers] = useState([])
const [dexLib, setDexLib] = useState(false)
const [nostr, setNostr] = useState(false)
const [lastFeedTab, setLastFeedTab] = useState(lsState.lastFeedTab || 'feed')
const [lastFeedTab, setLastFeedTab] = useState(lsState.lastFeedTab || localStorageDefault.lastFeedTab)
// Startup state management
const [asyncInitStarted, setAsyncInitStarted] = useState(false)
@@ -45,6 +50,14 @@ function useAppState () {
// NFTs for sale stored data to improve performance
const [nftForSaleCacheData, setNftForSaleCacheData] = useState(lsState.nftData || {})
// Relays
const [relaysData, setRelaysData] = useState(lsState.relays || localStorageDefault.relays) // All relays data
const [readRelays, setReadRelays] = useState(relaysData.filter(relay => relay.read).map(relay => relay.address)) // Read relays
const [writeRelays, setWriteRelays] = useState(relaysData.filter(relay => relay.write).map(relay => relay.address)) // Write relays
// Nostr queries service
const nostrQueriesRef = useRef(new NostrQueries({ relays: readRelays }))
// The wallet state makes this a true progressive web app (PWA). As
// balances, UTXOs, and tokens are retrieved, this state is updated.
// properties are enumerated here for the purpose of documentation.
@@ -97,7 +110,7 @@ function useAppState () {
return newBchWalletState
})
// console.log(`New wallet state: ${JSON.stringify(bchWalletState, null, 2)}`)
// console.log(`New wallet state: ${JSON.stringify(bchWalletState, null, 2)}`)
} catch (err) {
console.error('Error in App.js updateBchWalletState()')
throw err
@@ -114,6 +127,35 @@ function useAppState () {
setNftForSaleCacheData(allCacheData) // Update the state
updateLocalStorage({ nftData: allCacheData }) // Update the local storage
}
// Update relays data
function updateRelaysData (relaysData) {
setRelaysData(relaysData)
updateLocalStorage({ relays: relaysData }) // Update the local storage
// get the relay addresses with read set to true
const readRelays = relaysData.filter(relay => relay.read).map(relay => relay.address)
setReadRelays(readRelays)
console.log('readRelays: ', readRelays)
// get the relay addresses with write set to true
const writeRelays = relaysData.filter(relay => relay.write).map(relay => relay.address)
setWriteRelays(writeRelays)
console.log('writeRelays: ', writeRelays)
nostrQueriesRef.current = new NostrQueries({ relays: readRelays })
}
// Restore relays data
function restoreRelaysData () {
const relaysData = [...localStorageDefault.relays] // Create a new array in order to detect changes
setRelaysData(relaysData)
updateLocalStorage({ relays: relaysData })
// get the relay addresses with read set to true
const readRelays = relaysData.filter(relay => relay.read).map(relay => relay.address)
setReadRelays(readRelays)
console.log('readRelays: ', readRelays)
// get the relay addresses with write set to true
const writeRelays = relaysData.filter(relay => relay.write).map(relay => relay.address)
setWriteRelays(writeRelays)
console.log('writeRelays: ', writeRelays)
nostrQueriesRef.current = new NostrQueries({ relays: readRelays })
}
return {
serverUrl,
@@ -158,7 +200,13 @@ function useAppState () {
setLastFeedTab,
isSingleView,
setIsSingleView,
nostrQueries: new NostrQueries()
nostrQueries: nostrQueriesRef.current,
relaysData,
updateRelaysData,
restoreRelaysData,
readRelays,
writeRelays
}
}
+88 -18
View File
@@ -2,13 +2,16 @@
* Nostr class for query into relay pools
*
*/
import config from '../config'
import { RelayPool } from 'nostr'
import * as nip19 from 'nostr-tools/nip19'
export default class NostrQueries {
constructor () {
this.relays = config.nostrRelays
constructor ({ relays }) {
this.relays = relays || []
}
setRelays (relays) {
this.relays = relays
}
npubToHex (npub) {
@@ -22,6 +25,9 @@ export default class NostrQueries {
// from any relay. If one relay fails, it will move on to the next one.
async getProfile (pubHex) {
try {
if (this.relays.length === 0) {
return false
}
for (let i = 0; i < this.relays.length; i++) {
const profile = await new Promise((resolve) => {
const relay = this.relays[i]
@@ -47,6 +53,10 @@ export default class NostrQueries {
}
relay.close()
})
pool.on('error', (relay) => {
relay.close()
resolve(false)
})
})
// Stop looking for profile if found
if (profile) {
@@ -61,9 +71,12 @@ export default class NostrQueries {
// Get Feeds by user pubkey
async getUserFeeds (pubHex) {
try {
if (this.relays.length === 0) {
return []
}
let feeds = await new Promise((resolve) => {
let list = []
let closedRelays = 0
const closedRelays = []
const pool = RelayPool(this.relays)
@@ -73,9 +86,11 @@ export default class NostrQueries {
pool.on('eose', relay => {
relay.close()
closedRelays++
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
}
// Resolve list if all relays are closed
if (closedRelays === config.nostrRelays.length) {
if (closedRelays.length === this.relays.length) {
resolve(list)
}
})
@@ -83,6 +98,15 @@ export default class NostrQueries {
pool.on('event', (relay, subId, ev) => {
list = [...list, ev]
})
pool.on('error', (relay) => {
relay.close()
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
} // Resolve list if all relays are closed
if (closedRelays.length === this.relays.length) {
resolve(list)
}
})
})
// Remove duplicated feeds
feeds = feeds.filter((val, i, list) => {
@@ -102,11 +126,15 @@ export default class NostrQueries {
// Get global feeds
async getGlobalFeeds () {
try {
if (this.relays.length === 0) {
return []
}
let feeds = await new Promise((resolve, reject) => {
let list = []
let closedRelays = 0
const closedRelays = []
const pool = RelayPool(config.nostrRelays)
const pool = RelayPool(this.relays)
// const pool = RelayPool([config.nostrRelay])
pool.on('open', relay => {
relay.subscribe('REQ', { limit: 10, kinds: [1], '#t': ['slpdex-socialmedia'] })
@@ -114,17 +142,29 @@ export default class NostrQueries {
pool.on('eose', relay => {
relay.close()
closedRelays++
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
}
// Resolve list if all relays are closed
if (closedRelays === config.nostrRelays.length) {
if (closedRelays.length === this.relays.length) {
resolve(list)
}
})
pool.on('event', (relay, subId, ev) => {
// console.log('post retrieved from ', relay.url, ev.sig)
console.log('post retrieved from ', relay.url, ev.sig)
list = [...list, ev]
})
pool.on('error', (relay) => {
relay.close()
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
}
// Resolve list if all relays are closed
if (closedRelays.length === this.relays.length) {
resolve(list)
}
})
})
// Remove duplicated feeds
@@ -146,11 +186,14 @@ export default class NostrQueries {
// Get follow list by pubkey
async getFollowList (pubHex) {
if (this.relays.length === 0) {
return []
}
return new Promise((resolve, reject) => {
let list = []
let closedRelays = 0
const closedRelays = []
const pool = RelayPool(config.nostrRelays)
const pool = RelayPool(this.relays)
// const pool = RelayPool([config.nostrRelay])
pool.on('open', relay => {
relay.subscribe('subid', { limit: 1, kinds: [3], authors: [pubHex] })
@@ -158,9 +201,11 @@ export default class NostrQueries {
pool.on('eose', relay => {
relay.close()
closedRelays++
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
}
// Resolve list if all relays are closed
if (closedRelays === this.relays.length) {
if (closedRelays.length === this.relays.length) {
resolve(list)
}
})
@@ -170,15 +215,28 @@ export default class NostrQueries {
// Merge list received from all relays
list = [...list, ...ev.tags]
})
pool.on('error', (relay) => {
relay.close()
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
}
// Resolve list if all relays are closed
if (closedRelays.length === this.relays.length) {
resolve(list)
}
})
})
}
// Get event likes
async getPostLikes (postId) {
try {
if (this.relays.length === 0) {
return []
}
let likesRes = await new Promise((resolve) => {
const likes = []
let closedRelays = 0
const closedRelays = []
const pool = RelayPool(this.relays)
pool.on('open', relay => {
@@ -187,9 +245,11 @@ export default class NostrQueries {
pool.on('eose', relay => {
relay.close()
closedRelays++
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
}
// Resolve list if all relays are closed
if (closedRelays === this.relays.length) {
if (closedRelays.length === this.relays.length) {
resolve(likes)
}
})
@@ -204,6 +264,16 @@ export default class NostrQueries {
// skip error
}
})
pool.on('error', (relay) => {
relay.close()
if (!closedRelays.includes(relay)) {
closedRelays.push(relay)
}
// Resolve list if all relays are closed
if (closedRelays.length === this.relays.length) {
resolve(likes)
}
})
})
// Remove duplicated events