feat: smart relay pool
This commit is contained in:
parent
d1b3a8c4c7
commit
695f2fe017
4 changed files with 59 additions and 11 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -60,7 +60,7 @@
|
||||||
"lru-cache": "^11.0.2",
|
"lru-cache": "^11.0.2",
|
||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"nostr-tools": "^2.17.0",
|
"nostr-tools": "^2.19.1",
|
||||||
"nstart-modal": "^2.0.0",
|
"nstart-modal": "^2.0.0",
|
||||||
"path-to-regexp": "^8.2.0",
|
"path-to-regexp": "^8.2.0",
|
||||||
"qr-code-styling": "^1.9.2",
|
"qr-code-styling": "^1.9.2",
|
||||||
|
|
@ -9930,9 +9930,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nostr-tools": {
|
"node_modules/nostr-tools": {
|
||||||
"version": "2.17.0",
|
"version": "2.19.4",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.19.4.tgz",
|
||||||
"integrity": "sha512-lrvHM7cSaGhz7F0YuBvgHMoU2s8/KuThihDoOYk8w5gpVHTy0DeUCAgCN8uLGeuSl5MAWekJr9Dkfo5HClqO9w==",
|
"integrity": "sha512-qVLfoTpZegNYRJo5j+Oi6RPu0AwLP6jcvzcB3ySMnIT5DrAGNXfs5HNBspB/2HiGfH3GY+v6yXkTtcKSBQZwSg==",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/ciphers": "^0.5.1",
|
"@noble/ciphers": "^0.5.1",
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
"lru-cache": "^11.0.2",
|
"lru-cache": "^11.0.2",
|
||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"nostr-tools": "^2.17.0",
|
"nostr-tools": "^2.19.1",
|
||||||
"nstart-modal": "^2.0.0",
|
"nstart-modal": "^2.0.0",
|
||||||
"path-to-regexp": "^8.2.0",
|
"path-to-regexp": "^8.2.0",
|
||||||
"qr-code-styling": "^1.9.2",
|
"qr-code-styling": "^1.9.2",
|
||||||
|
|
|
||||||
48
src/lib/smart-pool.ts
Normal file
48
src/lib/smart-pool.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { SimplePool } from 'nostr-tools'
|
||||||
|
import { AbstractRelay } from 'nostr-tools/abstract-relay'
|
||||||
|
|
||||||
|
const DEFAULT_CONNECTION_TIMEOUT = 10 * 1000 // 10 seconds
|
||||||
|
const CLEANUP_THRESHOLD = 15 // number of relays to trigger cleanup
|
||||||
|
const CLEANUP_INTERVAL = 5 * 1000 // 5 seconds
|
||||||
|
const IDLE_TIMEOUT = 10 * 1000 // 10 seconds
|
||||||
|
|
||||||
|
export class SmartPool extends SimplePool {
|
||||||
|
private relayIdleTracker = new Map<string, number>()
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({ enablePing: true, enableReconnect: true })
|
||||||
|
|
||||||
|
// Periodically clean up idle relays
|
||||||
|
setInterval(() => this.cleanIdleRelays(), CLEANUP_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureRelay(url: string): Promise<AbstractRelay> {
|
||||||
|
// If relay is new and we have many relays, trigger cleanup
|
||||||
|
if (!this.relayIdleTracker.has(url) && this.relayIdleTracker.size > CLEANUP_THRESHOLD) {
|
||||||
|
this.cleanIdleRelays()
|
||||||
|
}
|
||||||
|
// Update last activity time
|
||||||
|
this.relayIdleTracker.set(url, Date.now())
|
||||||
|
return super.ensureRelay(url, { connectionTimeout: DEFAULT_CONNECTION_TIMEOUT })
|
||||||
|
}
|
||||||
|
|
||||||
|
private cleanIdleRelays() {
|
||||||
|
const idleRelays: string[] = []
|
||||||
|
this.relays.forEach((relay, url) => {
|
||||||
|
// If relay is disconnected or has active subscriptions, skip
|
||||||
|
if (!relay.connected || relay.openSubs.size > 0) return
|
||||||
|
|
||||||
|
const lastActivity = this.relayIdleTracker.get(url) ?? 0
|
||||||
|
// If relay active recently, skip
|
||||||
|
if (Date.now() - lastActivity < IDLE_TIMEOUT) return
|
||||||
|
|
||||||
|
idleRelays.push(url)
|
||||||
|
this.relayIdleTracker.delete(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (idleRelays.length > 0) {
|
||||||
|
console.log('[SmartPool] Closing idle relays:', idleRelays)
|
||||||
|
this.close(idleRelays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
|
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
|
||||||
import { formatPubkey, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey'
|
import { formatPubkey, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey'
|
||||||
import { filterOutBigRelays, getDefaultRelayUrls } from '@/lib/relay'
|
import { filterOutBigRelays, getDefaultRelayUrls } from '@/lib/relay'
|
||||||
|
import { SmartPool } from '@/lib/smart-pool'
|
||||||
import { getPubkeysFromPTags, getServersFromServerTags, tagNameEquals } from '@/lib/tag'
|
import { getPubkeysFromPTags, getServersFromServerTags, tagNameEquals } from '@/lib/tag'
|
||||||
import { mergeTimelines } from '@/lib/timeline'
|
import { mergeTimelines } from '@/lib/timeline'
|
||||||
import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url'
|
import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url'
|
||||||
|
|
@ -25,7 +26,6 @@ import {
|
||||||
matchFilters,
|
matchFilters,
|
||||||
Event as NEvent,
|
Event as NEvent,
|
||||||
nip19,
|
nip19,
|
||||||
SimplePool,
|
|
||||||
VerifiedEvent
|
VerifiedEvent
|
||||||
} from 'nostr-tools'
|
} from 'nostr-tools'
|
||||||
import { AbstractRelay } from 'nostr-tools/abstract-relay'
|
import { AbstractRelay } from 'nostr-tools/abstract-relay'
|
||||||
|
|
@ -40,7 +40,7 @@ class ClientService extends EventTarget {
|
||||||
signer?: ISigner
|
signer?: ISigner
|
||||||
pubkey?: string
|
pubkey?: string
|
||||||
currentRelays: string[] = []
|
currentRelays: string[] = []
|
||||||
private pool: SimplePool
|
private pool: SmartPool
|
||||||
private externalSeenOn = new Map<string, Set<string>>()
|
private externalSeenOn = new Map<string, Set<string>>()
|
||||||
|
|
||||||
private timelines: Record<
|
private timelines: Record<
|
||||||
|
|
@ -70,7 +70,7 @@ class ClientService extends EventTarget {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.pool = new SimplePool()
|
this.pool = new SmartPool()
|
||||||
this.pool.trackRelays = true
|
this.pool.trackRelays = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,7 +200,7 @@ class ClientService extends EventTarget {
|
||||||
uniqueRelayUrls.map(async (url) => {
|
uniqueRelayUrls.map(async (url) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const that = this
|
const that = this
|
||||||
const relay = await this.pool.ensureRelay(url, { connectionTimeout: 5_000 }).catch(() => {
|
const relay = await this.pool.ensureRelay(url).catch(() => {
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
if (!relay) {
|
if (!relay) {
|
||||||
|
|
@ -436,7 +436,7 @@ class ClientService extends EventTarget {
|
||||||
subPromises.push(startSub())
|
subPromises.push(startSub())
|
||||||
|
|
||||||
async function startSub() {
|
async function startSub() {
|
||||||
const relay = await that.pool.ensureRelay(url, { connectionTimeout: 5_000 }).catch(() => {
|
const relay = await that.pool.ensureRelay(url).catch(() => {
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
// cannot connect to relay
|
// cannot connect to relay
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue