mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2.git
synced 2026-09-21 16:52:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8607ce4f15 | ||
|
|
e18addff30 | ||
|
|
faec078403 | ||
|
|
416941089b | ||
|
|
f4c47d4ce3 | ||
|
|
121f1001e1 | ||
|
|
43013bd464 |
@@ -6,6 +6,7 @@
|
|||||||
// Global npm libraries
|
// Global npm libraries
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ServerSelectView from './select-server-view'
|
import ServerSelectView from './select-server-view'
|
||||||
|
import RelaySelectionView from './relay-selection-view'
|
||||||
|
|
||||||
function ConfigurationView (props) {
|
function ConfigurationView (props) {
|
||||||
const { appData } = props
|
const { appData } = props
|
||||||
@@ -13,6 +14,7 @@ function ConfigurationView (props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ServerSelectView appData={appData} />
|
<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
|
||||||
@@ -15,14 +15,14 @@ import GetBalance from './balance'
|
|||||||
import Wallet from './bch-wallet'
|
import Wallet from './bch-wallet'
|
||||||
import Placeholder2 from './placeholder2'
|
import Placeholder2 from './placeholder2'
|
||||||
import Placeholder3 from './placeholder3'
|
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 SelectServerButton from './servers/select-server-button'
|
||||||
import NftsForSale from './nfts-for-sale'
|
import NftsForSale from './nfts-for-sale'
|
||||||
import BchSend from './bch-send'
|
import BchSend from './bch-send'
|
||||||
import SlpTokens from './slp-tokens'
|
import SlpTokens from './slp-tokens'
|
||||||
import SweepWif from './sweep/index.js'
|
import SweepWif from './sweep/index.js'
|
||||||
import SignMessage from './sign/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 NostrPost from './nostr/nostr-post/index.js'
|
||||||
import Profile from './nostr/profile/index.js'
|
import Profile from './nostr/profile/index.js'
|
||||||
import Feeds from './nostr/feeds/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='/servers' element={<ServerSelectView appData={appData} />} />
|
||||||
<Route path='/sweep' element={<SweepWif appData={appData} />} />
|
<Route path='/sweep' element={<SweepWif appData={appData} />} />
|
||||||
<Route path='/sign' element={<SignMessage 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='/nostr-post' element={<NostrPost appData={appData} />} />
|
||||||
<Route path='/profile/:npub' element={<Profile appData={appData} />} />
|
<Route path='/profile/:npub' element={<Profile appData={appData} />} />
|
||||||
<Route path='/feeds' element={<Feeds appData={appData} />} />
|
<Route path='/feeds' element={<Feeds appData={appData} />} />
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import axios from 'axios'
|
|||||||
// Local libraries
|
// Local libraries
|
||||||
import config from '../../../../config'
|
import config from '../../../../config'
|
||||||
import ContentCard from './content-card'
|
import ContentCard from './content-card'
|
||||||
import { RelayPool } from 'nostr'
|
|
||||||
|
|
||||||
// Global variables and constants
|
// Global variables and constants
|
||||||
const SERVER = config.dexServer
|
const SERVER = config.dexServer
|
||||||
@@ -34,79 +33,17 @@ function ContentCreators (props) {
|
|||||||
// Get the list of profiles followed by the user.
|
// Get the list of profiles followed by the user.
|
||||||
// It aggregates all the followers from all the relays.
|
// It aggregates all the followers from all the relays.
|
||||||
const getFollowList = useCallback(async () => {
|
const getFollowList = useCallback(async () => {
|
||||||
const list = await new Promise((resolve, reject) => {
|
const { nostrKeyPair } = appData.bchWalletState
|
||||||
let list = []
|
|
||||||
let closedRelays = 0
|
|
||||||
const { nostrKeyPair } = appData.bchWalletState
|
|
||||||
|
|
||||||
const pool = RelayPool(config.nostrRelays)
|
const pubKeyHex = nostrKeyPair.pubHex
|
||||||
// const pool = RelayPool([config.nostrRelay])
|
const list = await appData.nostrQueries.getFollowList(pubKeyHex)
|
||||||
pool.on('open', relay => {
|
|
||||||
relay.subscribe('subid', { limit: 1, kinds: [3], authors: [nostrKeyPair.pubHex] })
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
|
||||||
relay.close()
|
|
||||||
closedRelays++
|
|
||||||
// Resolve list if all relays are closed
|
|
||||||
if (closedRelays === config.nostrRelays.length) {
|
|
||||||
resolve(list)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
// console.log('Received event:', ev)
|
|
||||||
// Merge list received from all relays
|
|
||||||
list = [...list, ...ev.tags]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// console.log('Follow List', list)
|
|
||||||
setFollowList(list)
|
setFollowList(list)
|
||||||
}, [appData])
|
}, [appData])
|
||||||
|
|
||||||
// Load profile from nostr relays
|
|
||||||
// It uses multiple relays. It will exit after the first successful retrieval
|
|
||||||
// from any relay. If one relay fails, it will move on to the next one.
|
|
||||||
const loadProfile = useCallback(async (pubKey) => {
|
|
||||||
// Looking for the profile in each relay sequentially
|
|
||||||
for (let i = 0; i < config.nostrRelays.length; i++) {
|
|
||||||
const profile = await new Promise((resolve) => {
|
|
||||||
const relay = config.nostrRelays[i]
|
|
||||||
// const pool = RelayPool(config.nostrRelays)
|
|
||||||
const pool = RelayPool([relay])
|
|
||||||
pool.on('open', relay => {
|
|
||||||
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubKey] })
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
|
||||||
console.log('Closing Relay')
|
|
||||||
relay.close()
|
|
||||||
resolve(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
try {
|
|
||||||
const profile = JSON.parse(ev.content)
|
|
||||||
// console.log('profile', profile)
|
|
||||||
console.log(`Profile found for ${pubKey} at ${relay.url}`)
|
|
||||||
resolve(profile)
|
|
||||||
} catch (error) {
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
relay.close()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// Stop looking for profile if found
|
|
||||||
if (profile) {
|
|
||||||
return profile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadCreators = async () => {
|
const loadCreators = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log('loadCreators()')
|
||||||
const creatorsRes = await axios.get(`${SERVER}/sm/list/all/0`)
|
const creatorsRes = await axios.get(`${SERVER}/sm/list/all/0`)
|
||||||
const creators = creatorsRes.data
|
const creators = creatorsRes.data
|
||||||
// console.log('creators', creators)
|
// console.log('creators', creators)
|
||||||
@@ -116,8 +53,9 @@ function ContentCreators (props) {
|
|||||||
for (let i = 0; i < creators.length; i++) {
|
for (let i = 0; i < creators.length; i++) {
|
||||||
try {
|
try {
|
||||||
const creator = creators[i]
|
const creator = creators[i]
|
||||||
const profile = await loadProfile(creator.pubkey)
|
const profileRes = await appData.nostrQueries.getProfile(creator.pubkey)
|
||||||
|
let profile = profileRes
|
||||||
|
if (!profileRes) profile = {}
|
||||||
// the folowing lines should re-render the ContentCard
|
// the folowing lines should re-render the ContentCard
|
||||||
setCreators(prevCreators => {
|
setCreators(prevCreators => {
|
||||||
const updatedCreators = [...prevCreators]
|
const updatedCreators = [...prevCreators]
|
||||||
@@ -137,7 +75,7 @@ function ContentCreators (props) {
|
|||||||
loadCreators()
|
loadCreators()
|
||||||
getFollowList()
|
getFollowList()
|
||||||
}
|
}
|
||||||
}, [loaded, followList, getFollowList, loadProfile])
|
}, [loaded, getFollowList, appData])
|
||||||
|
|
||||||
const filteredCreators = useCallback(() => {
|
const filteredCreators = useCallback(() => {
|
||||||
if (!creators || creators.length === 0) {
|
if (!creators || creators.length === 0) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import * as nip19 from 'nostr-tools/nip19'
|
|||||||
import { finalizeEvent } from 'nostr-tools/pure'
|
import { finalizeEvent } from 'nostr-tools/pure'
|
||||||
import { Relay } from 'nostr-tools/relay'
|
import { Relay } from 'nostr-tools/relay'
|
||||||
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
||||||
import { RelayPool } from 'nostr'
|
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import CopyOnClick from '../../bch-wallet/copy-on-click.js'
|
import CopyOnClick from '../../bch-wallet/copy-on-click.js'
|
||||||
@@ -20,7 +19,7 @@ import NostrFormat from '../nostr-format'
|
|||||||
|
|
||||||
function FeedCard (props) {
|
function FeedCard (props) {
|
||||||
const { post, appData, profiles } = props
|
const { post, appData, profiles } = props
|
||||||
const { nostrKeyPair } = appData.bchWalletState
|
const { nostrKeyPair, writeRelays } = appData.bchWalletState
|
||||||
|
|
||||||
const [profile, setProfile] = useState(profiles[post.pubkey])
|
const [profile, setProfile] = useState(profiles[post.pubkey])
|
||||||
|
|
||||||
@@ -33,53 +32,19 @@ function FeedCard (props) {
|
|||||||
// function to fetch a post likes reaction
|
// function to fetch a post likes reaction
|
||||||
const handleLikes = useCallback(async (post, userPubKey) => {
|
const handleLikes = useCallback(async (post, userPubKey) => {
|
||||||
try {
|
try {
|
||||||
const psf = 'wss://nostr-relay.psfoundation.info'
|
// Get all post likes events
|
||||||
const res = await new Promise((resolve) => {
|
const likesArr = await appData.nostrQueries.getPostLikes(post.id)
|
||||||
let likesCnt = 0
|
const likesCount = likesArr.length
|
||||||
let userLikedCnt = 0
|
|
||||||
const pool = RelayPool([psf])
|
|
||||||
pool.on('open', relay => {
|
|
||||||
relay.subscribe('subid', { kinds: [7], '#e': [post.id] })
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
// Verify if the users pubkey is in the likes array
|
||||||
relay.close()
|
const userLikedPost = likesArr.find(val => { return val.pubkey === userPubKey })
|
||||||
resolve({
|
setLikesCount(likesCount)
|
||||||
count: likesCnt,
|
setIsLiked(userLikedPost)
|
||||||
userLiked: userLikedCnt > 0
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
try {
|
|
||||||
// Count likes
|
|
||||||
if (ev.content === '+') {
|
|
||||||
likesCnt++
|
|
||||||
// Count user likes
|
|
||||||
if (ev.pubkey === userPubKey) {
|
|
||||||
userLikedCnt++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Count dislikes
|
|
||||||
if (ev.content === '-') {
|
|
||||||
likesCnt--
|
|
||||||
// Count user dislikes.
|
|
||||||
if (ev.pubkey === userPubKey) {
|
|
||||||
userLikedCnt--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// skip error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
setLikesCount(res.count)
|
|
||||||
setIsLiked(res.userLiked)
|
|
||||||
setLikesFetched(true)
|
setLikesFetched(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error)
|
console.warn(error)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [appData])
|
||||||
|
|
||||||
const handleProfilePictureError = () => {
|
const handleProfilePictureError = () => {
|
||||||
setProfilePictureError(true)
|
setProfilePictureError(true)
|
||||||
@@ -122,9 +87,6 @@ function FeedCard (props) {
|
|||||||
// Convert private key to binary
|
// Convert private key to binary
|
||||||
const privateKeyBin = hexToBytes(nostrKeyPair.privHex)
|
const privateKeyBin = hexToBytes(nostrKeyPair.privHex)
|
||||||
|
|
||||||
// Relay list
|
|
||||||
const psf = 'wss://nostr-relay.psfoundation.info'
|
|
||||||
|
|
||||||
// Define if like or dislike
|
// Define if like or dislike
|
||||||
let content = '+'
|
let content = '+'
|
||||||
if (isLiked) { content = '-' }
|
if (isLiked) { content = '-' }
|
||||||
@@ -134,27 +96,32 @@ function FeedCard (props) {
|
|||||||
kind: 7,
|
kind: 7,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
tags: [
|
tags: [
|
||||||
['e', post.id, psf],
|
['e', post.id],
|
||||||
['p', psf, nostrKeyPair.pubHex]
|
['p', nostrKeyPair.pubHex]
|
||||||
],
|
],
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
||||||
|
|
||||||
// Sign the post
|
writeRelays.map(async (relayUrl) => {
|
||||||
const signedEvent = finalizeEvent(eventTemplate, privateKeyBin)
|
try {
|
||||||
console.log('signedEvent: ', signedEvent)
|
// Sign the post
|
||||||
|
const signedEvent = finalizeEvent(eventTemplate, privateKeyBin)
|
||||||
|
console.log('signedEvent: ', signedEvent)
|
||||||
|
// Connect to a relay.
|
||||||
|
const relay = await Relay.connect(relayUrl)
|
||||||
|
console.log(`connected to ${relay.url}`)
|
||||||
|
|
||||||
// Connect to a relay.
|
// Publish the message to the relay.
|
||||||
const relay = await Relay.connect(psf)
|
const result = await relay.publish(signedEvent)
|
||||||
console.log(`connected to ${relay.url}`)
|
console.log('result: ', result)
|
||||||
|
|
||||||
// Publish the message to the relay.
|
// Close the connection to the relay.
|
||||||
const result = await relay.publish(signedEvent)
|
relay.close()
|
||||||
console.log('result: ', result)
|
} catch (err) {
|
||||||
|
console.warn(`Skipping publishing to ${relayUrl} due to error: ${err}`)
|
||||||
// Close the connection to the relay.
|
}
|
||||||
relay.close()
|
})
|
||||||
|
|
||||||
await handleLikes(post, nostrKeyPair.pubHex)
|
await handleLikes(post, nostrKeyPair.pubHex)
|
||||||
setLikesFetched(true)
|
setLikesFetched(true)
|
||||||
|
|||||||
@@ -4,41 +4,21 @@
|
|||||||
// Global npm libraries
|
// Global npm libraries
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Container, Spinner } from 'react-bootstrap'
|
import { Container, Spinner } from 'react-bootstrap'
|
||||||
import { RelayPool } from 'nostr'
|
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import config from '../../../../config'
|
|
||||||
import FeedCard from './feed-card'
|
import FeedCard from './feed-card'
|
||||||
|
|
||||||
function Following (props) {
|
function Following (props) {
|
||||||
const { appData, posts, profiles } = props
|
const { appData, posts, profiles } = props
|
||||||
|
const { nostrQueries } = appData
|
||||||
const [followingPosts, setFollowingPosts] = useState([])
|
const [followingPosts, setFollowingPosts] = useState([])
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getFollowingPosts = async () => {
|
const getFollowingPosts = async () => {
|
||||||
const followingList = await new Promise((resolve, reject) => {
|
console.log('getFollowingPosts')
|
||||||
let list = []
|
const { nostrKeyPair } = appData.bchWalletState
|
||||||
const { nostrKeyPair } = appData.bchWalletState
|
const followingList = await nostrQueries.getFollowList(nostrKeyPair.pubHex)
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter posts by following list
|
// Filter posts by following list
|
||||||
let filteredPosts = []
|
let filteredPosts = []
|
||||||
@@ -55,7 +35,7 @@ function Following (props) {
|
|||||||
setLoaded(true)
|
setLoaded(true)
|
||||||
}
|
}
|
||||||
getFollowingPosts()
|
getFollowingPosts()
|
||||||
}, [appData, posts])
|
}, [appData, posts, nostrQueries])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className='mt-4 mb-5' style={{ marginBottom: '50px' }}>
|
<Container className='mt-4 mb-5' style={{ marginBottom: '50px' }}>
|
||||||
|
|||||||
@@ -3,14 +3,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Global npm libraries
|
// Global npm libraries
|
||||||
import React, { useState, useEffect, useCallback } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { Container, Nav, Tab, Spinner } from 'react-bootstrap'
|
import { Container, Nav, Tab, Spinner } from 'react-bootstrap'
|
||||||
import { RelayPool } from 'nostr'
|
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import Feed from './feed'
|
import Feed from './feed'
|
||||||
import Following from './following'
|
import Following from './following'
|
||||||
import config from '../../../../config'
|
|
||||||
|
|
||||||
function Feeds (props) {
|
function Feeds (props) {
|
||||||
const { appData } = props
|
const { appData } = props
|
||||||
@@ -20,92 +18,12 @@ function Feeds (props) {
|
|||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
const [profiles, setProfiles] = useState({})
|
const [profiles, setProfiles] = useState({})
|
||||||
|
|
||||||
// Load profile from nostr relays
|
|
||||||
// It uses multiple relays. It will exit after the first successful retrieval
|
|
||||||
// from any relay. If one relay fails, it will move on to the next one.
|
|
||||||
const fetchProfile = useCallback(async (pubKey) => {
|
|
||||||
// Looking for the profile in each relay sequentially
|
|
||||||
for (let i = 0; i < config.nostrRelays.length; i++) {
|
|
||||||
const profile = await new Promise((resolve) => {
|
|
||||||
const relay = config.nostrRelays[i]
|
|
||||||
// const pool = RelayPool(config.nostrRelays)
|
|
||||||
const pool = RelayPool([relay])
|
|
||||||
pool.on('open', relay => {
|
|
||||||
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubKey] })
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
|
||||||
console.log('Closing Relay')
|
|
||||||
relay.close()
|
|
||||||
resolve(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
try {
|
|
||||||
const profile = JSON.parse(ev.content)
|
|
||||||
// console.log('profile', profile)
|
|
||||||
console.log(`Profile found for ${pubKey} at ${relay.url}`)
|
|
||||||
resolve(profile)
|
|
||||||
} catch (error) {
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
relay.close()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// Stop looking for profile if found
|
|
||||||
if (profile) {
|
|
||||||
return profile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Get array of feeds from relays
|
|
||||||
const fetchFeeds = useCallback(async () => {
|
|
||||||
let feeds = await new Promise((resolve, reject) => {
|
|
||||||
let list = []
|
|
||||||
let closedRelays = 0
|
|
||||||
|
|
||||||
const pool = RelayPool(config.nostrRelays)
|
|
||||||
// const pool = RelayPool([config.nostrRelay])
|
|
||||||
pool.on('open', relay => {
|
|
||||||
relay.subscribe('REQ', { limit: 10, kinds: [1], '#t': ['slpdex-socialmedia'] })
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
|
||||||
relay.close()
|
|
||||||
closedRelays++
|
|
||||||
// Resolve list if all relays are closed
|
|
||||||
if (closedRelays === config.nostrRelays.length) {
|
|
||||||
resolve(list)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
// console.log('post retrieved from ', relay.url, ev.sig)
|
|
||||||
list = [...list, ev]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Remove duplicated feeds
|
|
||||||
feeds = feeds.filter((val, i, list) => {
|
|
||||||
const existingIndex = list.findIndex(value => value.id === val.id)
|
|
||||||
return existingIndex === i
|
|
||||||
})
|
|
||||||
|
|
||||||
// Sort from newest to oldest
|
|
||||||
feeds.sort((a, b) => b.created_at - a.created_at)
|
|
||||||
|
|
||||||
// console.log('feeds', feeds)
|
|
||||||
|
|
||||||
setPosts(feeds)
|
|
||||||
return feeds
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Load data on component mount.
|
// Load data on component mount.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
// Get feeds
|
// Get feeds
|
||||||
const feeds = await fetchFeeds()
|
const feeds = await appData.nostrQueries.getGlobalFeeds()
|
||||||
|
setPosts(feeds)
|
||||||
setLoaded(true)
|
setLoaded(true)
|
||||||
|
|
||||||
const loadedProfiles = [] // fetched profiles ( this will be used for prevent load the same profile multiple times.)
|
const loadedProfiles = [] // fetched profiles ( this will be used for prevent load the same profile multiple times.)
|
||||||
@@ -116,7 +34,7 @@ function Feeds (props) {
|
|||||||
const exist = loadedProfiles.find((val) => { return val === pubKey })
|
const exist = loadedProfiles.find((val) => { return val === pubKey })
|
||||||
if (exist) { continue }
|
if (exist) { continue }
|
||||||
// Fech profile.
|
// Fech profile.
|
||||||
const profile = await fetchProfile(pubKey)
|
const profile = await appData.nostrQueries.getProfile(pubKey)
|
||||||
loadedProfiles.push(pubKey) // mark as loaded
|
loadedProfiles.push(pubKey) // mark as loaded
|
||||||
|
|
||||||
// Update profile state
|
// Update profile state
|
||||||
@@ -130,7 +48,7 @@ function Feeds (props) {
|
|||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
}, [loaded, fetchFeeds, fetchProfile])
|
}, [loaded, appData])
|
||||||
|
|
||||||
const onChangeTab = (tab) => {
|
const onChangeTab = (tab) => {
|
||||||
setActiveTab(tab)
|
setActiveTab(tab)
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ import Accordion from 'react-bootstrap/Accordion'
|
|||||||
import { finalizeEvent } from 'nostr-tools/pure'
|
import { finalizeEvent } from 'nostr-tools/pure'
|
||||||
import { Relay } from 'nostr-tools/relay'
|
import { Relay } from 'nostr-tools/relay'
|
||||||
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
||||||
import { RelayPool } from 'nostr'
|
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import config from '../../../../config'
|
|
||||||
|
|
||||||
function ProfilePost (props) {
|
function ProfilePost (props) {
|
||||||
const { bchWalletState } = props.appData
|
const { appData } = props
|
||||||
|
const { bchWalletState, writeRelays } = appData
|
||||||
const [accordionKey, setAccordionKey] = useState(null)
|
const [accordionKey, setAccordionKey] = useState(null)
|
||||||
const [onFetch, setOnFetch] = useState(false)
|
const [onFetch, setOnFetch] = useState(false)
|
||||||
const [formLoaded, setFormLoaded] = useState(false)
|
const [formLoaded, setFormLoaded] = useState(false)
|
||||||
@@ -34,40 +33,19 @@ function ProfilePost (props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Get Last post from
|
// Get Last post from
|
||||||
const getLastPost = () => {
|
const getLastProfilePost = async () => {
|
||||||
const { nostrKeyPair } = bchWalletState
|
const { nostrKeyPair } = appData.bchWalletState
|
||||||
|
const lastProfile = await appData.nostrQueries.getProfile(nostrKeyPair.pubHex)
|
||||||
const psf = 'wss://nostr-relay.psfoundation.info'
|
if (lastProfile) {
|
||||||
|
setFormData(lastProfile)
|
||||||
const pool = RelayPool([psf])
|
}
|
||||||
pool.on('open', relay => {
|
setFormLoaded(true)
|
||||||
relay.subscribe('subid', { limit: 1, kinds: [0], authors: [nostrKeyPair.pubHex] })
|
|
||||||
setFormLoaded(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
|
||||||
console.log('Closing Relay')
|
|
||||||
setFormLoaded(true)
|
|
||||||
relay.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
console.log('Received event:', ev)
|
|
||||||
// setPosts(currentPosts => [...currentPosts, ev])
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(ev.content)
|
|
||||||
console.log('data', data)
|
|
||||||
setFormData(data)
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formLoaded) {
|
if (!formLoaded) {
|
||||||
getLastPost()
|
getLastProfilePost()
|
||||||
}
|
}
|
||||||
}, [bchWalletState, formLoaded])
|
}, [appData, formLoaded])
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
const { name, value } = e.target
|
const { name, value } = e.target
|
||||||
@@ -111,7 +89,7 @@ function ProfilePost (props) {
|
|||||||
console.log('signedEvent: ', signedEvent)
|
console.log('signedEvent: ', signedEvent)
|
||||||
|
|
||||||
// Publish the post to each relay.
|
// Publish the post to each relay.
|
||||||
config.nostrRelays.map(async (relayUrl) => {
|
writeRelays.map(async (relayUrl) => {
|
||||||
try {
|
try {
|
||||||
// Connect to a relay.
|
// Connect to a relay.
|
||||||
const relay = await Relay.connect(relayUrl)
|
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
|
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import config from '../../../../config'
|
|
||||||
|
|
||||||
function PublicPost (props) {
|
function PublicPost (props) {
|
||||||
const [accordionKey, setAccordionKey] = useState('0')
|
const [accordionKey, setAccordionKey] = useState('0')
|
||||||
const [onFetch, setOnFetch] = useState(false)
|
const [onFetch, setOnFetch] = useState(false)
|
||||||
const { bchWalletState } = props.appData
|
const { bchWalletState, writeRelays } = props.appData
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
content: ''
|
content: ''
|
||||||
})
|
})
|
||||||
@@ -64,7 +63,7 @@ function PublicPost (props) {
|
|||||||
console.log('signedEvent: ', signedEvent)
|
console.log('signedEvent: ', signedEvent)
|
||||||
|
|
||||||
// Publish the post to each relay.
|
// Publish the post to each relay.
|
||||||
config.nostrRelays.map(async (relayUrl) => {
|
writeRelays.map(async (relayUrl) => {
|
||||||
try {
|
try {
|
||||||
// Connect to a relay.
|
// Connect to a relay.
|
||||||
const relay = await Relay.connect(relayUrl)
|
const relay = await Relay.connect(relayUrl)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function Profile (props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container>
|
<Container>
|
||||||
<ProfileRead {...props} setProfile={setProfile} npub={npub} />
|
<ProfileRead {...props} onProfileRead={setProfile} npub={npub} />
|
||||||
<PublicRead {...props} profile={profile} npub={npub} />
|
<PublicRead {...props} profile={profile} npub={npub} />
|
||||||
<SlpTokensDisplay {...props} npub={npub} />
|
<SlpTokensDisplay {...props} npub={npub} />
|
||||||
<NFTForSale {...props} npub={npub} />
|
<NFTForSale {...props} npub={npub} />
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function NFTForSale (props) {
|
|||||||
const [offersAreLoaded, setOffersAreLoaded] = useState(false)
|
const [offersAreLoaded, setOffersAreLoaded] = useState(false)
|
||||||
const [iconsAreLoaded, setIconsAreLoaded] = useState(false)
|
const [iconsAreLoaded, setIconsAreLoaded] = useState(false)
|
||||||
const [dataAreLoaded, setDataAreLoaded] = useState(false)
|
const [dataAreLoaded, setDataAreLoaded] = useState(false)
|
||||||
|
const [startAsync, setStartAsync] = useState(false)
|
||||||
|
|
||||||
const getAddressByNpub = useCallback(async () => {
|
const getAddressByNpub = useCallback(async () => {
|
||||||
const url = `${config.dexServer}/sm/npub/${npub}`
|
const url = `${config.dexServer}/sm/npub/${npub}`
|
||||||
@@ -24,6 +25,7 @@ function NFTForSale (props) {
|
|||||||
|
|
||||||
// Handler for refresh button
|
// Handler for refresh button
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
|
console.log('handle refresh')
|
||||||
loadNftOffers()
|
loadNftOffers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,9 +220,13 @@ function NFTForSale (props) {
|
|||||||
|
|
||||||
// Effect to load NFTs on component mount
|
// Effect to load NFTs on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('loading nfts for sale')
|
// Prevent to load data twice
|
||||||
loadNftOffers()
|
if (!startAsync) {
|
||||||
}, [loadNftOffers])
|
console.log('loading nfts for sale')
|
||||||
|
loadNftOffers()
|
||||||
|
setStartAsync(true)
|
||||||
|
}
|
||||||
|
}, [loadNftOffers, startAsync])
|
||||||
|
|
||||||
// Get Cid from url
|
// Get Cid from url
|
||||||
const parseCid = (url) => {
|
const parseCid = (url) => {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Component to read nostr information kind 0 (normal posts)
|
* Component to read nostr information kind 0 (normal posts)
|
||||||
*
|
*
|
||||||
* TODO:
|
|
||||||
* - Currently feeds are retrieved from only one relay. It should retrieve Kind
|
|
||||||
* 0 posts from all relays. It should then remove any duplicate entries.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Global npm libraries
|
// Global npm libraries
|
||||||
@@ -15,45 +12,25 @@ import { faUser, faGlobe } from '@fortawesome/free-solid-svg-icons'
|
|||||||
import * as nip19 from 'nostr-tools/nip19'
|
import * as nip19 from 'nostr-tools/nip19'
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import config from '../../../../config'
|
|
||||||
import NostrFormat from '../nostr-format'
|
import NostrFormat from '../nostr-format'
|
||||||
import { RelayPool } from 'nostr'
|
|
||||||
|
|
||||||
function ProfileRead (props) {
|
function ProfileRead (props) {
|
||||||
const { npub } = props
|
const { npub, appData } = props
|
||||||
const { setProfile } = props
|
const { onProfileRead } = props
|
||||||
const [post, setPost] = useState({})
|
const [profile, setProfile] = useState({})
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
const [imageError, setImageError] = useState({ picture: false, banner: false })
|
const [imageError, setImageError] = useState({ picture: false, banner: false })
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const start = () => {
|
const start = async () => {
|
||||||
const pubHexData = nip19.decode(npub)
|
const pubHexData = nip19.decode(npub)
|
||||||
const pubHex = pubHexData.data
|
const pubHex = pubHexData.data
|
||||||
|
|
||||||
// const pool = RelayPool(config.nostrRelays)
|
const profile = await appData.nostrQueries.getProfile(pubHex)
|
||||||
const pool = new RelayPool([config.nostrRelay])
|
if (profile) {
|
||||||
pool.on('open', relay => {
|
onProfileRead(profile)
|
||||||
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubHex] })
|
setProfile(profile)
|
||||||
setLoaded(true)
|
}
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
|
||||||
console.log('Closing Relay')
|
|
||||||
relay.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
console.log('Received event:', ev)
|
|
||||||
try {
|
|
||||||
const profile = JSON.parse(ev.content)
|
|
||||||
console.log('Profile:', profile)
|
|
||||||
setPost(profile)
|
|
||||||
setProfile(profile)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing profile:', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setLoaded(true)
|
setLoaded(true)
|
||||||
}
|
}
|
||||||
@@ -61,7 +38,7 @@ function ProfileRead (props) {
|
|||||||
if (!loaded && npub) {
|
if (!loaded && npub) {
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
}, [loaded, setProfile, npub])
|
}, [loaded, onProfileRead, npub, appData])
|
||||||
|
|
||||||
const handleImageError = (type) => {
|
const handleImageError = (type) => {
|
||||||
setImageError(prev => ({ ...prev, [type]: true }))
|
setImageError(prev => ({ ...prev, [type]: true }))
|
||||||
@@ -74,10 +51,10 @@ function ProfileRead (props) {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{/* Banner Section */}
|
{/* Banner Section */}
|
||||||
{post.banner && !imageError.banner && (
|
{profile.banner && !imageError.banner && (
|
||||||
<div className='position-relative'>
|
<div className='position-relative'>
|
||||||
<img
|
<img
|
||||||
src={post.banner}
|
src={profile.banner}
|
||||||
alt='Profile banner'
|
alt='Profile banner'
|
||||||
className='w-100 rounded-4 shadow-sm'
|
className='w-100 rounded-4 shadow-sm'
|
||||||
style={{ height: '200px', objectFit: 'cover' }}
|
style={{ height: '200px', objectFit: 'cover' }}
|
||||||
@@ -90,10 +67,10 @@ function ProfileRead (props) {
|
|||||||
<div className='d-flex flex-column flex-md-row align-items-center gap-4 mb-5 p-3 p-md-5 bg-light rounded-4 shadow-sm'>
|
<div className='d-flex flex-column flex-md-row align-items-center gap-4 mb-5 p-3 p-md-5 bg-light rounded-4 shadow-sm'>
|
||||||
{/* Profile Picture */}
|
{/* Profile Picture */}
|
||||||
<div style={{ width: '120px', height: '120px' }} className='mb-3 mb-md-0'>
|
<div style={{ width: '120px', height: '120px' }} className='mb-3 mb-md-0'>
|
||||||
{post.picture && !imageError.picture
|
{profile.picture && !imageError.picture
|
||||||
? (
|
? (
|
||||||
<img
|
<img
|
||||||
src={post.picture}
|
src={profile.picture}
|
||||||
alt='Profile'
|
alt='Profile'
|
||||||
className='rounded-circle shadow w-100 h-100'
|
className='rounded-circle shadow w-100 h-100'
|
||||||
style={{ objectFit: 'cover' }}
|
style={{ objectFit: 'cover' }}
|
||||||
@@ -117,7 +94,7 @@ function ProfileRead (props) {
|
|||||||
|
|
||||||
{/* Profile Information */}
|
{/* Profile Information */}
|
||||||
<div className='flex-grow-1 text-center text-md-start mb-3 mb-md-0 d-flex flex-column align-items-center align-items-md-start'>
|
<div className='flex-grow-1 text-center text-md-start mb-3 mb-md-0 d-flex flex-column align-items-center align-items-md-start'>
|
||||||
<h3 className='mb-2 fw-bold'>{post.name || 'username'}</h3>
|
<h3 className='mb-2 fw-bold'>{profile.name || 'username'}</h3>
|
||||||
|
|
||||||
<div className='text-muted small mb-3 d-flex align-items-center flex-column flex-md-row'>
|
<div className='text-muted small mb-3 d-flex align-items-center flex-column flex-md-row'>
|
||||||
<span className='text-truncate me-2 mb-2 mb-md-0'>
|
<span className='text-truncate me-2 mb-2 mb-md-0'>
|
||||||
@@ -132,24 +109,24 @@ function ProfileRead (props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* About Section */}
|
{/* About Section */}
|
||||||
{post.about && (
|
{profile.about && (
|
||||||
<div className='fs-6 text-secondary'>
|
<div className='fs-6 text-secondary'>
|
||||||
<NostrFormat content={post.about} />
|
<NostrFormat content={profile.about} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Website Link */}
|
{/* Website Link */}
|
||||||
{post.website && (
|
{profile.website && (
|
||||||
<div className='mb-3 d-flex align-items-center gap-2'>
|
<div className='mb-3 d-flex align-items-center gap-2'>
|
||||||
<FontAwesomeIcon icon={faGlobe} className='text-muted' size='sm' />
|
<FontAwesomeIcon icon={faGlobe} className='text-muted' size='sm' />
|
||||||
<a
|
<a
|
||||||
href={post.website.startsWith('http') ? post.website : `https://${post.website}`}
|
href={profile.website.startsWith('http') ? profile.website : `https://${profile.website}`}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
className='text-decoration-none text-muted small'
|
className='text-decoration-none text-muted small'
|
||||||
style={{ wordBreak: 'break-all' }}
|
style={{ wordBreak: 'break-all' }}
|
||||||
>
|
>
|
||||||
{post.website}
|
{profile.website}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,49 +6,30 @@ import React, { useEffect, useState } from 'react'
|
|||||||
import { Container, Card, Spinner } from 'react-bootstrap'
|
import { Container, Card, Spinner } from 'react-bootstrap'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { RelayPool } from 'nostr'
|
|
||||||
import * as nip19 from 'nostr-tools/nip19'
|
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import config from '../../../../config'
|
|
||||||
import NostrFormat from '../nostr-format'
|
import NostrFormat from '../nostr-format'
|
||||||
|
|
||||||
function PublicRead (props) {
|
function PublicRead (props) {
|
||||||
const { npub } = props
|
const { npub, appData } = props
|
||||||
const { bchWalletState } = props.appData
|
const { bchWalletState } = appData
|
||||||
const [posts, setPosts] = useState([])
|
const [posts, setPosts] = useState([])
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
const [profilePictureError, setProfilePictureError] = useState(false)
|
const [profilePictureError, setProfilePictureError] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Get Last post from a author
|
// Get Last post from a author
|
||||||
const start = () => {
|
const start = async () => {
|
||||||
const pubHexData = nip19.decode(npub)
|
const pubKeyHex = appData.nostrQueries.npubToHex(npub)
|
||||||
const pubHex = pubHexData.data
|
const feeds = await appData.nostrQueries.getUserFeeds(pubKeyHex)
|
||||||
console.log('pubhex', pubHex)
|
setPosts(feeds)
|
||||||
|
setLoaded(true)
|
||||||
const pool = RelayPool(config.nostrRelays)
|
|
||||||
pool.on('open', relay => {
|
|
||||||
relay.subscribe('subid', { limit: 5, kinds: [1], authors: [pubHex] })
|
|
||||||
setLoaded(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('eose', relay => {
|
|
||||||
console.log('Closing Relay')
|
|
||||||
setLoaded(true)
|
|
||||||
relay.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.on('event', (relay, subId, ev) => {
|
|
||||||
console.log('Received event:', ev)
|
|
||||||
setPosts(currentPosts => [...currentPosts, ev])
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loaded && npub) {
|
if (!loaded && npub) {
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
}, [bchWalletState, loaded, npub])
|
}, [bchWalletState, loaded, npub, appData])
|
||||||
|
|
||||||
const handleProfilePictureError = () => {
|
const handleProfilePictureError = () => {
|
||||||
setProfilePictureError(true)
|
setProfilePictureError(true)
|
||||||
|
|||||||
+61
-11
@@ -1,25 +1,31 @@
|
|||||||
import { useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
// import { useQueryParam, StringParam } from 'use-query-params'
|
// import { useQueryParam, StringParam } from 'use-query-params'
|
||||||
import useLocalStorageState from 'use-local-storage-state'
|
import useLocalStorageState from 'use-local-storage-state'
|
||||||
import AppUtil from '../util'
|
import AppUtil from '../util'
|
||||||
|
import NostrQueries from '../services/nostr-queries'
|
||||||
|
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
function useAppState () {
|
function useAppState () {
|
||||||
const location = useLocation()
|
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
|
// Load Local storage Data
|
||||||
const [lsState, setLSState, { removeItem }] = useLocalStorageState('bchWalletState-template', {
|
const [lsState, setLSState, { removeItem }] = useLocalStorageState('bchWalletState-template', {
|
||||||
ssr: true,
|
ssr: true,
|
||||||
defaultValue: {
|
defaultValue: localStorageDefault
|
||||||
serverUrl: 'https://free-bch.fullstack.cash' // Default server
|
|
||||||
},
|
|
||||||
nftData: {},
|
|
||||||
lastFeedTab: 'feed'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('lsState: ', lsState)
|
console.log('lsState: ', lsState)
|
||||||
|
|
||||||
// Initialize data states
|
// Initialize data states
|
||||||
const [serverUrl, setServerUrl] = useState(lsState.serverUrl) // Default server url
|
const [serverUrl, setServerUrl] = useState(lsState.serverUrl) // Default server url
|
||||||
const [menuState, setMenuState] = useState(0)
|
const [menuState, setMenuState] = useState(0)
|
||||||
@@ -27,7 +33,7 @@ function useAppState () {
|
|||||||
const [servers, setServers] = useState([])
|
const [servers, setServers] = useState([])
|
||||||
const [dexLib, setDexLib] = useState(false)
|
const [dexLib, setDexLib] = useState(false)
|
||||||
const [nostr, setNostr] = 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
|
// Startup state management
|
||||||
const [asyncInitStarted, setAsyncInitStarted] = useState(false)
|
const [asyncInitStarted, setAsyncInitStarted] = useState(false)
|
||||||
@@ -44,6 +50,14 @@ function useAppState () {
|
|||||||
// NFTs for sale stored data to improve performance
|
// NFTs for sale stored data to improve performance
|
||||||
const [nftForSaleCacheData, setNftForSaleCacheData] = useState(lsState.nftData || {})
|
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
|
// The wallet state makes this a true progressive web app (PWA). As
|
||||||
// balances, UTXOs, and tokens are retrieved, this state is updated.
|
// balances, UTXOs, and tokens are retrieved, this state is updated.
|
||||||
// properties are enumerated here for the purpose of documentation.
|
// properties are enumerated here for the purpose of documentation.
|
||||||
@@ -96,7 +110,7 @@ function useAppState () {
|
|||||||
return newBchWalletState
|
return newBchWalletState
|
||||||
})
|
})
|
||||||
|
|
||||||
// console.log(`New wallet state: ${JSON.stringify(bchWalletState, null, 2)}`)
|
// console.log(`New wallet state: ${JSON.stringify(bchWalletState, null, 2)}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in App.js updateBchWalletState()')
|
console.error('Error in App.js updateBchWalletState()')
|
||||||
throw err
|
throw err
|
||||||
@@ -113,6 +127,35 @@ function useAppState () {
|
|||||||
setNftForSaleCacheData(allCacheData) // Update the state
|
setNftForSaleCacheData(allCacheData) // Update the state
|
||||||
updateLocalStorage({ nftData: allCacheData }) // Update the local storage
|
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 {
|
return {
|
||||||
serverUrl,
|
serverUrl,
|
||||||
@@ -156,7 +199,14 @@ function useAppState () {
|
|||||||
lastFeedTab,
|
lastFeedTab,
|
||||||
setLastFeedTab,
|
setLastFeedTab,
|
||||||
isSingleView,
|
isSingleView,
|
||||||
setIsSingleView
|
setIsSingleView,
|
||||||
|
nostrQueries: nostrQueriesRef.current,
|
||||||
|
relaysData,
|
||||||
|
updateRelaysData,
|
||||||
|
restoreRelaysData,
|
||||||
|
readRelays,
|
||||||
|
writeRelays
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
/**
|
||||||
|
* Nostr class for query into relay pools
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { RelayPool } from 'nostr'
|
||||||
|
import * as nip19 from 'nostr-tools/nip19'
|
||||||
|
|
||||||
|
export default class NostrQueries {
|
||||||
|
constructor ({ relays }) {
|
||||||
|
this.relays = relays || []
|
||||||
|
}
|
||||||
|
|
||||||
|
setRelays (relays) {
|
||||||
|
this.relays = relays
|
||||||
|
}
|
||||||
|
|
||||||
|
npubToHex (npub) {
|
||||||
|
const pubHexData = nip19.decode(npub)
|
||||||
|
const pubHex = pubHexData.data
|
||||||
|
return pubHex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load profile from nostr relays
|
||||||
|
// It uses multiple relays. It will exit after the first successful retrieval
|
||||||
|
// 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]
|
||||||
|
// const pool = RelayPool(config.nostrRelays)
|
||||||
|
const pool = RelayPool([relay])
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubHex] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', relay => {
|
||||||
|
relay.close()
|
||||||
|
resolve(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('event', (relay, subId, ev) => {
|
||||||
|
try {
|
||||||
|
const profile = JSON.parse(ev.content)
|
||||||
|
// console.log('profile', profile)
|
||||||
|
console.log(`Profile found for ${pubHex} at ${relay.url}`)
|
||||||
|
resolve(profile)
|
||||||
|
} catch (error) {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
relay.close()
|
||||||
|
})
|
||||||
|
pool.on('error', (relay) => {
|
||||||
|
relay.close()
|
||||||
|
resolve(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Stop looking for profile if found
|
||||||
|
if (profile) {
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Feeds by user pubkey
|
||||||
|
async getUserFeeds (pubHex) {
|
||||||
|
try {
|
||||||
|
if (this.relays.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let feeds = await new Promise((resolve) => {
|
||||||
|
let list = []
|
||||||
|
const closedRelays = []
|
||||||
|
|
||||||
|
const pool = RelayPool(this.relays)
|
||||||
|
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('subid', { limit: 5, kinds: [1], authors: [pubHex] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
const existingIndex = list.findIndex(value => value.id === val.id)
|
||||||
|
return existingIndex === i
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort from newest to oldest
|
||||||
|
feeds.sort((a, b) => b.created_at - a.created_at)
|
||||||
|
|
||||||
|
return feeds
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get global feeds
|
||||||
|
async getGlobalFeeds () {
|
||||||
|
try {
|
||||||
|
if (this.relays.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let feeds = await new Promise((resolve, reject) => {
|
||||||
|
let list = []
|
||||||
|
const closedRelays = []
|
||||||
|
|
||||||
|
const pool = RelayPool(this.relays)
|
||||||
|
// const pool = RelayPool([config.nostrRelay])
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('REQ', { limit: 10, kinds: [1], '#t': ['slpdex-socialmedia'] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('event', (relay, subId, ev) => {
|
||||||
|
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
|
||||||
|
feeds = feeds.filter((val, i, list) => {
|
||||||
|
const existingIndex = list.findIndex(value => value.id === val.id)
|
||||||
|
return existingIndex === i
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort from newest to oldest
|
||||||
|
feeds.sort((a, b) => b.created_at - a.created_at)
|
||||||
|
|
||||||
|
// console.log('feeds', feeds)
|
||||||
|
|
||||||
|
return feeds
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get follow list by pubkey
|
||||||
|
async getFollowList (pubHex) {
|
||||||
|
if (this.relays.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let list = []
|
||||||
|
const closedRelays = []
|
||||||
|
|
||||||
|
const pool = RelayPool(this.relays)
|
||||||
|
// const pool = RelayPool([config.nostrRelay])
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('subid', { limit: 1, kinds: [3], authors: [pubHex] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('event', (relay, subId, ev) => {
|
||||||
|
// console.log('Received event:', ev)
|
||||||
|
// 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 = []
|
||||||
|
const closedRelays = []
|
||||||
|
|
||||||
|
const pool = RelayPool(this.relays)
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('subid', { kinds: [7], '#e': [postId] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('event', (relay, subId, ev) => {
|
||||||
|
try {
|
||||||
|
// Count likes
|
||||||
|
if (ev.content === '+' || ev.content === '-') {
|
||||||
|
likes.push(ev)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 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
|
||||||
|
likesRes = likesRes.filter((val, i, list) => {
|
||||||
|
const existingIndex = list.findIndex(value => value.id === val.id)
|
||||||
|
return existingIndex === i
|
||||||
|
})
|
||||||
|
// Get likes
|
||||||
|
const likesArr = likesRes.filter((val, i, list) => {
|
||||||
|
return val.content === '+'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get dislikes
|
||||||
|
const dislikesArr = likesRes.filter((val, i, list) => {
|
||||||
|
return val.content === '-'
|
||||||
|
})
|
||||||
|
// For every user dislike remove a user like from the array
|
||||||
|
for (let i = 0; i < dislikesArr.length; i++) {
|
||||||
|
const disLike = dislikesArr[i]
|
||||||
|
const likeExist = likesArr.findIndex(val => val.pubkey === disLike.pubkey)
|
||||||
|
if (likeExist >= 0) likesArr.splice(likeExist, 1)
|
||||||
|
}
|
||||||
|
// Return array of likes.
|
||||||
|
return likesArr
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user