mirror of
https://github.com/dergigi/boris.git
synced 2026-02-17 21:15:02 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1a7f0e4a4 | ||
|
|
cde8e30ab2 | ||
|
|
aa7e532950 | ||
|
|
c9208cfff2 | ||
|
|
2fb4132342 | ||
|
|
81180c8ba8 | ||
|
|
1c48adf44e |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.10.8] - 2025-10-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Individual event rendering via `/e/:eventId` path
|
||||||
|
- Display `kind:1` notes and other events with article-like presentation
|
||||||
|
- Publication date displayed in top-right corner like articles
|
||||||
|
- Author attribution with "Note by @author" titles
|
||||||
|
- Direct event loading with intelligent caching from eventStore
|
||||||
|
- Centralized event fetching via new `eventManager` singleton
|
||||||
|
- Request deduplication for concurrent fetches
|
||||||
|
- Automatic retry logic when relay pool becomes available
|
||||||
|
- Non-blocking background fetching with 12-second timeout
|
||||||
|
- Seamless integration with eventStore for instant cached event display
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Bookmark hydration efficiency
|
||||||
|
- Only request content for bookmarks missing data (not all bookmarks)
|
||||||
|
- Use eventStore fallback for instant display of cached profiles
|
||||||
|
- Prevents over-fetching and improves initial load performance
|
||||||
|
- Search button behavior for notes
|
||||||
|
- Opens `kind:1` notes directly via `/e/{eventId}` instead of search portal
|
||||||
|
- Articles continue to use search portal with proper naddr encoding
|
||||||
|
- Removes unwanted `nostr-event:` prefix from URLs
|
||||||
|
- Author profile resolution
|
||||||
|
- Fetch author profiles from eventStore cache first before relay requests
|
||||||
|
- Instant title updates if profile already loaded
|
||||||
|
- Graceful fallback to short pubkey display if profile unavailable
|
||||||
|
|
||||||
## [0.10.7] - 2025-10-21
|
## [0.10.7] - 2025-10-21
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "boris",
|
"name": "boris",
|
||||||
"version": "0.10.7",
|
"version": "0.10.9",
|
||||||
"description": "A minimal nostr client for bookmark management",
|
"description": "A minimal nostr client for bookmark management",
|
||||||
"homepage": "https://read.withboris.com/",
|
"homepage": "https://read.withboris.com/",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -578,8 +578,6 @@ function App() {
|
|||||||
|
|
||||||
// Handle user relay list and blocked relays when account changes
|
// Handle user relay list and blocked relays when account changes
|
||||||
const userRelaysSub = accounts.active$.subscribe((account) => {
|
const userRelaysSub = accounts.active$.subscribe((account) => {
|
||||||
console.log('[relay-init] userRelaysSub fired, account:', account ? 'logged in' : 'logged out')
|
|
||||||
console.log('[relay-init] Pool has', Array.from(pool.relays.keys()).length, 'relays before applying changes')
|
|
||||||
if (account) {
|
if (account) {
|
||||||
// User logged in - start with hardcoded relays immediately, then stream user relay list updates
|
// User logged in - start with hardcoded relays immediately, then stream user relay list updates
|
||||||
const pubkey = account.pubkey
|
const pubkey = account.pubkey
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default function RouteDebug() {
|
|||||||
// Unexpected during deep-link refresh tests
|
// Unexpected during deep-link refresh tests
|
||||||
console.warn('[RouteDebug] unexpected root redirect', info)
|
console.warn('[RouteDebug] unexpected root redirect', info)
|
||||||
} else {
|
} else {
|
||||||
console.debug('[RouteDebug]', info)
|
// silent
|
||||||
}
|
}
|
||||||
}, [location, matchArticle])
|
}, [location, matchArticle])
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const TTSControls: React.FC<Props> = ({ text, defaultLang, className, settings }
|
|||||||
const lang = detect(text)
|
const lang = detect(text)
|
||||||
if (typeof lang === 'string' && lang.length >= 2) langOverride = lang.slice(0, 2)
|
if (typeof lang === 'string' && lang.length >= 2) langOverride = lang.slice(0, 2)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.debug('[tts][detect] failed', err)
|
// ignore detection errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!langOverride && resolvedSystemLang) {
|
if (!langOverride && resolvedSystemLang) {
|
||||||
@@ -78,7 +78,6 @@ const TTSControls: React.FC<Props> = ({ text, defaultLang, className, settings }
|
|||||||
const currentIndex = SPEED_OPTIONS.indexOf(rate)
|
const currentIndex = SPEED_OPTIONS.indexOf(rate)
|
||||||
const nextIndex = (currentIndex + 1) % SPEED_OPTIONS.length
|
const nextIndex = (currentIndex + 1) % SPEED_OPTIONS.length
|
||||||
const next = SPEED_OPTIONS[nextIndex]
|
const next = SPEED_OPTIONS[nextIndex]
|
||||||
console.debug('[tts][ui] cycle speed', { from: rate, to: next, speaking, paused })
|
|
||||||
setRate(next)
|
setRate(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
// Update rate when defaultRate option changes
|
// Update rate when defaultRate option changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (options.defaultRate !== undefined) {
|
if (options.defaultRate !== undefined) {
|
||||||
console.debug('[tts] defaultRate changed ->', options.defaultRate)
|
|
||||||
setRate(options.defaultRate)
|
setRate(options.defaultRate)
|
||||||
}
|
}
|
||||||
}, [options.defaultRate])
|
}, [options.defaultRate])
|
||||||
@@ -73,7 +72,6 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
if (!voice && v.length) {
|
if (!voice && v.length) {
|
||||||
const byLang = v.find(x => x.lang?.toLowerCase().startsWith(defaultLang.toLowerCase()))
|
const byLang = v.find(x => x.lang?.toLowerCase().startsWith(defaultLang.toLowerCase()))
|
||||||
setVoice(byLang || v[0] || null)
|
setVoice(byLang || v[0] || null)
|
||||||
console.debug('[tts] voices loaded', { total: v.length, picked: (byLang || v[0] || null)?.lang })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
load()
|
load()
|
||||||
@@ -107,44 +105,37 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
|
|
||||||
u.onstart = () => {
|
u.onstart = () => {
|
||||||
if (utteranceRef.current !== self) return
|
if (utteranceRef.current !== self) return
|
||||||
console.debug('[tts] onstart')
|
|
||||||
setSpeaking(true)
|
setSpeaking(true)
|
||||||
setPaused(false)
|
setPaused(false)
|
||||||
}
|
}
|
||||||
u.onpause = () => {
|
u.onpause = () => {
|
||||||
if (utteranceRef.current !== self) return
|
if (utteranceRef.current !== self) return
|
||||||
console.debug('[tts] onpause')
|
|
||||||
setPaused(true)
|
setPaused(true)
|
||||||
}
|
}
|
||||||
u.onresume = () => {
|
u.onresume = () => {
|
||||||
if (utteranceRef.current !== self) return
|
if (utteranceRef.current !== self) return
|
||||||
console.debug('[tts] onresume')
|
|
||||||
setPaused(false)
|
setPaused(false)
|
||||||
}
|
}
|
||||||
u.onend = () => {
|
u.onend = () => {
|
||||||
if (utteranceRef.current !== self) return
|
if (utteranceRef.current !== self) return
|
||||||
console.debug('[tts] onend')
|
|
||||||
// Continue with next chunk if available
|
// Continue with next chunk if available
|
||||||
const hasMore = chunkIndexRef.current < (chunksRef.current.length - 1)
|
const hasMore = chunkIndexRef.current < (chunksRef.current.length - 1)
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
chunkIndexRef.current += 1
|
chunkIndexRef.current++
|
||||||
globalOffsetRef.current += self.text.length
|
charIndexRef.current += self.text.length
|
||||||
const next = chunksRef.current[chunkIndexRef.current] || ''
|
const nextChunk = chunksRef.current[chunkIndexRef.current]
|
||||||
const nextUtterance = createUtterance(next, langRef.current)
|
const nextUtterance = createUtterance(nextChunk, langRef.current)
|
||||||
utteranceRef.current = nextUtterance
|
utteranceRef.current = nextUtterance
|
||||||
synth!.speak(nextUtterance)
|
synth!.speak(nextUtterance)
|
||||||
return
|
} else {
|
||||||
|
setSpeaking(false)
|
||||||
|
setPaused(false)
|
||||||
}
|
}
|
||||||
setSpeaking(false)
|
|
||||||
setPaused(false)
|
|
||||||
utteranceRef.current = null
|
|
||||||
}
|
}
|
||||||
u.onerror = () => {
|
u.onerror = () => {
|
||||||
if (utteranceRef.current !== self) return
|
if (utteranceRef.current !== self) return
|
||||||
console.debug('[tts] onerror')
|
|
||||||
setSpeaking(false)
|
setSpeaking(false)
|
||||||
setPaused(false)
|
setPaused(false)
|
||||||
utteranceRef.current = null
|
|
||||||
}
|
}
|
||||||
u.onboundary = (ev: SpeechSynthesisEvent) => {
|
u.onboundary = (ev: SpeechSynthesisEvent) => {
|
||||||
if (utteranceRef.current !== self) return
|
if (utteranceRef.current !== self) return
|
||||||
@@ -197,7 +188,6 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
|
|
||||||
const stop = useCallback(() => {
|
const stop = useCallback(() => {
|
||||||
if (!supported) return
|
if (!supported) return
|
||||||
console.debug('[tts] stop')
|
|
||||||
synth!.cancel()
|
synth!.cancel()
|
||||||
setSpeaking(false)
|
setSpeaking(false)
|
||||||
setPaused(false)
|
setPaused(false)
|
||||||
@@ -211,18 +201,16 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
|
|
||||||
const speak = useCallback((text: string, langOverride?: string) => {
|
const speak = useCallback((text: string, langOverride?: string) => {
|
||||||
if (!supported || !text?.trim()) return
|
if (!supported || !text?.trim()) return
|
||||||
console.debug('[tts] speak', { len: text.length, rate })
|
|
||||||
synth!.cancel()
|
synth!.cancel()
|
||||||
spokenTextRef.current = text
|
spokenTextRef.current = text
|
||||||
charIndexRef.current = 0
|
charIndexRef.current = 0
|
||||||
langRef.current = langOverride
|
langRef.current = langOverride
|
||||||
startSpeakingChunks(text)
|
startSpeakingChunks(text)
|
||||||
}, [supported, synth, startSpeakingChunks, rate])
|
}, [supported, synth, startSpeakingChunks])
|
||||||
|
|
||||||
const pause = useCallback(() => {
|
const pause = useCallback(() => {
|
||||||
if (!supported) return
|
if (!supported) return
|
||||||
if (synth!.speaking && !synth!.paused) {
|
if (synth!.speaking && !synth!.paused) {
|
||||||
console.debug('[tts] pause')
|
|
||||||
synth!.pause()
|
synth!.pause()
|
||||||
setPaused(true)
|
setPaused(true)
|
||||||
}
|
}
|
||||||
@@ -231,7 +219,6 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
const resume = useCallback(() => {
|
const resume = useCallback(() => {
|
||||||
if (!supported) return
|
if (!supported) return
|
||||||
if (synth!.speaking && synth!.paused) {
|
if (synth!.speaking && synth!.paused) {
|
||||||
console.debug('[tts] resume')
|
|
||||||
synth!.resume()
|
synth!.resume()
|
||||||
setPaused(false)
|
setPaused(false)
|
||||||
}
|
}
|
||||||
@@ -242,14 +229,11 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
if (!supported) return
|
if (!supported) return
|
||||||
if (!utteranceRef.current) return
|
if (!utteranceRef.current) return
|
||||||
|
|
||||||
console.debug('[tts] rate change', { rate, speaking: synth!.speaking, paused: synth!.paused, charIndex: charIndexRef.current })
|
|
||||||
|
|
||||||
if (synth!.speaking && !synth!.paused) {
|
if (synth!.speaking && !synth!.paused) {
|
||||||
const fullText = spokenTextRef.current
|
const fullText = spokenTextRef.current
|
||||||
const startIndex = Math.max(0, Math.min(charIndexRef.current, fullText.length))
|
const startIndex = Math.max(0, Math.min(charIndexRef.current, fullText.length))
|
||||||
const remainingText = fullText.slice(startIndex)
|
const remainingText = fullText.slice(startIndex)
|
||||||
|
|
||||||
console.debug('[tts] restart at new rate', { startIndex, remainingLen: remainingText.length })
|
|
||||||
synth!.cancel()
|
synth!.cancel()
|
||||||
// restart chunked from current global index
|
// restart chunked from current global index
|
||||||
spokenTextRef.current = remainingText
|
spokenTextRef.current = remainingText
|
||||||
@@ -273,7 +257,6 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
const fullText = spokenTextRef.current
|
const fullText = spokenTextRef.current
|
||||||
const startIndex = Math.max(0, Math.min(charIndexRef.current, fullText.length - 1))
|
const startIndex = Math.max(0, Math.min(charIndexRef.current, fullText.length - 1))
|
||||||
const remainingText = fullText.slice(startIndex)
|
const remainingText = fullText.slice(startIndex)
|
||||||
console.debug('[tts] updateRate -> restart', { newRate, startIndex, remainingLen: remainingText.length })
|
|
||||||
synth!.cancel()
|
synth!.cancel()
|
||||||
const u = createUtterance(remainingText)
|
const u = createUtterance(remainingText)
|
||||||
// ensure the new rate is applied immediately on the new utterance
|
// ensure the new rate is applied immediately on the new utterance
|
||||||
@@ -281,7 +264,6 @@ export function useTextToSpeech(options: UseTTSOptions = {}): UseTTS {
|
|||||||
utteranceRef.current = u
|
utteranceRef.current = u
|
||||||
synth!.speak(u)
|
synth!.speak(u)
|
||||||
} else if (utteranceRef.current) {
|
} else if (utteranceRef.current) {
|
||||||
console.debug('[tts] updateRate -> set on utterance', { newRate })
|
|
||||||
utteranceRef.current.rate = newRate
|
utteranceRef.current.rate = newRate
|
||||||
}
|
}
|
||||||
}, [supported, synth, createUtterance])
|
}, [supported, synth, createUtterance])
|
||||||
|
|||||||
@@ -279,8 +279,6 @@ class BookmarkController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`📋 Requesting hydration for: ${noteIds.length} note IDs, ${coordinates.length} coordinates`)
|
|
||||||
|
|
||||||
// Helper to build and emit bookmarks
|
// Helper to build and emit bookmarks
|
||||||
const emitBookmarks = (idToEvent: Map<string, NostrEvent>) => {
|
const emitBookmarks = (idToEvent: Map<string, NostrEvent>) => {
|
||||||
// Now hydrate the ORIGINAL items (which may have duplicates), using the deduplicated results
|
// Now hydrate the ORIGINAL items (which may have duplicates), using the deduplicated results
|
||||||
@@ -293,10 +291,8 @@ class BookmarkController {
|
|||||||
const enriched = allBookmarks.map(b => ({
|
const enriched = allBookmarks.map(b => ({
|
||||||
...b,
|
...b,
|
||||||
tags: b.tags || [],
|
tags: b.tags || [],
|
||||||
// Prefer hydrated content; fallback to any cached event content in external store
|
content: b.content || this.externalEventStore?.getEvent(b.id)?.content || '', // Fallback to eventStore content
|
||||||
content: b.content && b.content.length > 0
|
created_at: b.created_at || this.externalEventStore?.getEvent(b.id)?.created_at || b.created_at
|
||||||
? b.content
|
|
||||||
: (this.externalEventStore?.getEvent(b.id)?.content || '')
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const sortedBookmarks = enriched
|
const sortedBookmarks = enriched
|
||||||
|
|||||||
@@ -21,12 +21,16 @@ export interface AddressPointer {
|
|||||||
pubkey: string
|
pubkey: string
|
||||||
identifier: string
|
identifier: string
|
||||||
relays?: string[]
|
relays?: string[]
|
||||||
|
added_at?: number
|
||||||
|
created_at?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventPointer {
|
export interface EventPointer {
|
||||||
id: string
|
id: string
|
||||||
relays?: string[]
|
relays?: string[]
|
||||||
author?: string
|
author?: string
|
||||||
|
added_at?: number
|
||||||
|
created_at?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplesauceBookmarks {
|
export interface ApplesauceBookmarks {
|
||||||
@@ -77,14 +81,14 @@ export const processApplesauceBookmarks = (
|
|||||||
allItems.push({
|
allItems.push({
|
||||||
id: note.id,
|
id: note.id,
|
||||||
content: '',
|
content: '',
|
||||||
created_at: parentCreatedAt || 0,
|
created_at: note.created_at || parentCreatedAt || 0,
|
||||||
pubkey: note.author || activeAccount.pubkey,
|
pubkey: note.author || activeAccount.pubkey,
|
||||||
kind: 1, // Short note kind
|
kind: 1, // Short note kind
|
||||||
tags: [],
|
tags: [],
|
||||||
parsedContent: undefined,
|
parsedContent: undefined,
|
||||||
type: 'event' as const,
|
type: 'event' as const,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
added_at: parentCreatedAt || 0
|
added_at: note.added_at || parentCreatedAt || 0
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -97,14 +101,14 @@ export const processApplesauceBookmarks = (
|
|||||||
allItems.push({
|
allItems.push({
|
||||||
id: coordinate,
|
id: coordinate,
|
||||||
content: '',
|
content: '',
|
||||||
created_at: parentCreatedAt || 0,
|
created_at: article.created_at || parentCreatedAt || 0,
|
||||||
pubkey: article.pubkey,
|
pubkey: article.pubkey,
|
||||||
kind: article.kind, // Usually 30023 for long-form articles
|
kind: article.kind, // Usually 30023 for long-form articles
|
||||||
tags: [],
|
tags: [],
|
||||||
parsedContent: undefined,
|
parsedContent: undefined,
|
||||||
type: 'event' as const,
|
type: 'event' as const,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
added_at: parentCreatedAt || 0
|
added_at: article.added_at || parentCreatedAt || 0
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class EventManager {
|
|||||||
|
|
||||||
// Safety timeout for event fetches (ms)
|
// Safety timeout for event fetches (ms)
|
||||||
private fetchTimeoutMs = 12000
|
private fetchTimeoutMs = 12000
|
||||||
|
// Retry policy
|
||||||
|
private maxAttempts = 4
|
||||||
|
private baseBackoffMs = 700
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the event manager with event store and relay pool
|
* Initialize the event manager with event store and relay pool
|
||||||
@@ -70,7 +73,7 @@ class EventManager {
|
|||||||
|
|
||||||
// Start a new fetch request
|
// Start a new fetch request
|
||||||
this.pendingRequests.set(eventId, [{ resolve, reject }])
|
this.pendingRequests.set(eventId, [{ resolve, reject }])
|
||||||
this.fetchFromRelay(eventId)
|
this.fetchFromRelayWithRetry(eventId, 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,17 +89,14 @@ class EventManager {
|
|||||||
requests.forEach(req => req.reject(error))
|
requests.forEach(req => req.reject(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fetchFromRelayWithRetry(eventId: string, attempt: number): void {
|
||||||
* Actually fetch the event from relay
|
|
||||||
*/
|
|
||||||
private fetchFromRelay(eventId: string): void {
|
|
||||||
// If no loader yet, schedule retry
|
// If no loader yet, schedule retry
|
||||||
if (!this.relayPool || !this.eventLoader) {
|
if (!this.relayPool || !this.eventLoader) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.eventLoader && this.pendingRequests.has(eventId)) {
|
if (this.pendingRequests.has(eventId)) {
|
||||||
this.fetchFromRelay(eventId)
|
this.fetchFromRelayWithRetry(eventId, attempt)
|
||||||
}
|
}
|
||||||
}, 500)
|
}, this.baseBackoffMs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +111,23 @@ class EventManager {
|
|||||||
error: (err: unknown) => {
|
error: (err: unknown) => {
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
const error = err instanceof Error ? err : new Error(String(err))
|
const error = err instanceof Error ? err : new Error(String(err))
|
||||||
this.rejectPending(eventId, error)
|
// Retry on error until attempts exhausted
|
||||||
|
if (attempt < this.maxAttempts && this.pendingRequests.has(eventId)) {
|
||||||
|
setTimeout(() => this.fetchFromRelayWithRetry(eventId, attempt + 1), this.baseBackoffMs * attempt)
|
||||||
|
} else {
|
||||||
|
this.rejectPending(eventId, error)
|
||||||
|
}
|
||||||
subscription.unsubscribe()
|
subscription.unsubscribe()
|
||||||
},
|
},
|
||||||
complete: () => {
|
complete: () => {
|
||||||
// Completed without next - consider not found
|
// Completed without next - consider not found, but retry a few times
|
||||||
if (!delivered) {
|
if (!delivered) {
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
this.rejectPending(eventId, new Error('Event not found'))
|
if (attempt < this.maxAttempts && this.pendingRequests.has(eventId)) {
|
||||||
|
setTimeout(() => this.fetchFromRelayWithRetry(eventId, attempt + 1), this.baseBackoffMs * attempt)
|
||||||
|
} else {
|
||||||
|
this.rejectPending(eventId, new Error('Event not found'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
subscription.unsubscribe()
|
subscription.unsubscribe()
|
||||||
}
|
}
|
||||||
@@ -127,8 +136,13 @@ class EventManager {
|
|||||||
// Safety timeout
|
// Safety timeout
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
if (!delivered) {
|
if (!delivered) {
|
||||||
this.rejectPending(eventId, new Error('Timed out fetching event'))
|
if (attempt < this.maxAttempts && this.pendingRequests.has(eventId)) {
|
||||||
subscription.unsubscribe()
|
subscription.unsubscribe()
|
||||||
|
this.fetchFromRelayWithRetry(eventId, attempt + 1)
|
||||||
|
} else {
|
||||||
|
subscription.unsubscribe()
|
||||||
|
this.rejectPending(eventId, new Error('Timed out fetching event'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, this.fetchTimeoutMs)
|
}, this.fetchTimeoutMs)
|
||||||
}
|
}
|
||||||
@@ -139,7 +153,7 @@ class EventManager {
|
|||||||
private retryAllPending(): void {
|
private retryAllPending(): void {
|
||||||
const pendingIds = Array.from(this.pendingRequests.keys())
|
const pendingIds = Array.from(this.pendingRequests.keys())
|
||||||
pendingIds.forEach(eventId => {
|
pendingIds.forEach(eventId => {
|
||||||
this.fetchFromRelay(eventId)
|
this.fetchFromRelayWithRetry(eventId, 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user