fix(nostr): Better subscript management and preventing race conditions

This commit is contained in:
Chris Troutner
2025-11-05 09:38:06 -08:00
parent 55054b4e51
commit f303b95741
3 changed files with 75 additions and 8 deletions
+69 -5
View File
@@ -197,6 +197,10 @@ function NostrChat (props) {
const subId = generateSubId(`group-${selectedChannel}`)
const filter = { limit: 10, kinds: [42], '#e': [selectedChannel] }
// Track if EOSE has been called
let eoseCalled = false
let eoseTimeoutId = null
const subscription = restClient.current.createSubscription(subId, filter, {
onEvent: (ev) => {
console.log('Group post retrieved from REST API', ev.content)
@@ -207,8 +211,17 @@ function NostrChat (props) {
}
},
onEose: () => {
eoseCalled = true
if (eoseTimeoutId) {
clearTimeout(eoseTimeoutId)
eoseTimeoutId = null
}
if (!selectedChannelIsDm) {
setLoadedMessages(true)
// Use setTimeout to ensure state updates from onEvent callbacks are processed
// before setting loadedMessages to true
setTimeout(() => {
setLoadedMessages(true)
}, 100)
}
},
onClosed: (message) => {
@@ -221,11 +234,24 @@ function NostrChat (props) {
subscriptionsRef.current[subId] = subscription
// Set up EOSE timeout fallback - if EOSE doesn't arrive within 10 seconds, set loadedMessages anyway
const EOSE_TIMEOUT_MS = 10000 // 10 seconds
eoseTimeoutId = setTimeout(() => {
if (!eoseCalled && !selectedChannelIsDm) {
console.warn(`EOSE timeout reached for subscription ${subId} - setting loadedMessages to true`)
setLoadedMessages(true)
}
}, EOSE_TIMEOUT_MS)
// Capture values for cleanup
const subscriptionsRefValue = subscriptionsRef.current
const restClientValue = restClient.current
return () => {
// Clear EOSE timeout if it exists
if (eoseTimeoutId) {
clearTimeout(eoseTimeoutId)
}
// Close subscription on component unmount or selected channel changes
console.log('Close existing subscription for group channel')
if (subscriptionsRefValue[subId]) {
@@ -233,7 +259,11 @@ function NostrChat (props) {
delete subscriptionsRefValue[subId]
}
restClientValue.closeSubscription(subId).catch(err => {
console.warn('Error closing subscription:', err)
// Subscription already closed is not an error - this is expected behavior
const errorMessage = err?.message || err?.toString() || ''
if (!errorMessage.includes('not found') && !errorMessage.includes('already closed')) {
console.warn('Error closing subscription:', err)
}
})
}
}, [onMsgRead, selectedChannel, selectedChannelIsDm, nostrQueries, channelsLoaded, deletedChats])
@@ -255,6 +285,10 @@ function NostrChat (props) {
{ limit: 10, kinds: [4], '#p': [dmPubKey], authors: [nostrKeyPair.pubHex] } // sent messages
]
// Track if EOSE has been called
let eoseCalled = false
let eoseTimeoutId = null
const subscription = restClient.current.createSubscription(subId, filters, {
onEvent: (ev) => {
console.log('DM post retrieved from REST API', ev.content)
@@ -268,8 +302,17 @@ function NostrChat (props) {
}
},
onEose: () => {
eoseCalled = true
if (eoseTimeoutId) {
clearTimeout(eoseTimeoutId)
eoseTimeoutId = null
}
if (selectedChannelIsDm) {
setLoadedMessages(true)
// Use setTimeout to ensure state updates from onEvent callbacks are processed
// before setting loadedMessages to true
setTimeout(() => {
setLoadedMessages(true)
}, 100)
}
},
onClosed: (message) => {
@@ -282,11 +325,24 @@ function NostrChat (props) {
subscriptionsRef.current[subId] = subscription
// Set up EOSE timeout fallback - if EOSE doesn't arrive within 10 seconds, set loadedMessages anyway
const EOSE_TIMEOUT_MS = 10000 // 10 seconds
eoseTimeoutId = setTimeout(() => {
if (!eoseCalled && selectedChannelIsDm) {
console.warn(`EOSE timeout reached for subscription ${subId} - setting loadedMessages to true`)
setLoadedMessages(true)
}
}, EOSE_TIMEOUT_MS)
// Capture values for cleanup
const subscriptionsRefValue = subscriptionsRef.current
const restClientValue = restClient.current
return () => {
// Clear EOSE timeout if it exists
if (eoseTimeoutId) {
clearTimeout(eoseTimeoutId)
}
// Close subscription on component unmount or selected channel changes
console.log('Close existing subscription for private channel')
if (subscriptionsRefValue[subId]) {
@@ -294,7 +350,11 @@ function NostrChat (props) {
delete subscriptionsRefValue[subId]
}
restClientValue.closeSubscription(subId).catch(err => {
console.warn('Error closing subscription:', err)
// Subscription already closed is not an error - this is expected behavior
const errorMessage = err?.message || err?.toString() || ''
if (!errorMessage.includes('not found') && !errorMessage.includes('already closed')) {
console.warn('Error closing subscription:', err)
}
})
}
}, [onMsgRead, selectedChannel, selectedChannelIsDm, nostrQueries, bchWalletState, decryptMsg])
@@ -374,7 +434,11 @@ function NostrChat (props) {
delete subscriptionsRefValue[subId]
}
restClientValue.closeSubscription(subId).catch(err => {
console.warn('Error closing subscription:', err)
// Subscription already closed is not an error - this is expected behavior
const errorMessage = err?.message || err?.toString() || ''
if (!errorMessage.includes('not found') && !errorMessage.includes('already closed')) {
console.warn('Error closing subscription:', err)
}
})
}
}, [handleIncomingDms, appData, nostrQueries, dmListLoaded, channelsLoaded])
@@ -13,7 +13,7 @@ import { Spinner } from 'react-bootstrap'
function MessageList (props) {
const { messages, loadedMessages } = props
console.log('loadedMessages', loadedMessages)
const [groupedMessages, setGroupedMessages] = useState([])
const [groupedMessages, setGroupedMessages] = useState({})
const msgContainerRef = useRef()
// Group messages by date
const groupMessagesByDate = useCallback((messages) => {
+5 -2
View File
@@ -14,12 +14,15 @@ const config = {
ghRepo: 'https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2',
radicleUrl: 'https://app.radicle.network/seeds/maple.radicle.garden/rad:git:hnrkd5cjwwb5tzx37hq9uqm5ubon7ee468xcy/remotes/hyyycncbn9qzqmobnhjq9rry6t4mbjiadzjoyhaknzxjcz3cxkpfpc',
// dexServer: 'https://dex-api.fullstack.cash',
dexServer: 'http://localhost:5700',
dexServer: 'https://dex-api.fullstack.cash',
// dexServer: 'http://localhost:5700',
nostrTopic: 'bch-dex-test-topic-02',
// REST API endpoint for Nostr relay interactions (primary interface)
nostrRestApiUrl: 'https://nostr-relay-api.psfoundation.info',
// nostrRestApiUrl: 'http://localhost:5942',
// Legacy relay URLs kept for reference (may be used in tags, but actual connections use REST API)
nostrRelay: 'wss://nostr-relay.psfoundation.info',
nostrRelays: [