Browse Source

make relay service more efficient

imwald
Silberengel 2 months ago
parent
commit
744c1c68c8
  1. 6
      Dockerfile
  2. 143
      src/services/client.service.ts

6
Dockerfile

@ -34,8 +34,10 @@ RUN printf "server {\n\
}\n\ }\n\
\n\ \n\
location / {\n\ location / {\n\
# For scrapers, serve index.html (they'll see static meta tags)\n\ # For scrapers, always serve index.html so they see static og/twitter meta tags\n\
# Note: To get dynamic meta tags, you need SSR or a meta tag service\n\ if (\$is_scraper = 1) {\n\
rewrite ^ /index.html last;\n\
}\n\
try_files \$uri \$uri/ /index.html;\n\ try_files \$uri \$uri/ /index.html;\n\
}\n\ }\n\
\n\ \n\

143
src/services/client.service.ts

@ -65,10 +65,6 @@ class ClientService extends EventTarget {
tokenize: 'forward' tokenize: 'forward'
}) })
/** Min delay between starting subscriptions to the same relay to avoid hammering one relay with many REQs */
private static readonly PER_RELAY_SUB_STAGGER_MS = 80
private lastSubStartByRelay = new Map<string, number>()
constructor() { constructor() {
super() super()
this.pool = new SimplePool() this.pool = new SimplePool()
@ -650,110 +646,35 @@ class ClientService extends EventTarget {
// 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 _knownIds = new Set<string>() const _knownIds = new Set<string>()
let startedCount = 0
let eosedCount = 0
let eosed = false
let closedCount = 0
const closeReasons: string[] = []
const subPromises: Promise<{ close: () => void }>[] = []
const staggerMs = ClientService.PER_RELAY_SUB_STAGGER_MS
relays.forEach((url) => {
let hasAuthed = false
subPromises.push(startSub())
async function startSub() {
const relayKey = normalizeUrl(url) || url
const now = Date.now()
const last = that.lastSubStartByRelay.get(relayKey) ?? 0
const wait = staggerMs - (now - last)
if (wait > 0) {
await new Promise<void>((r) => setTimeout(r, wait))
}
that.lastSubStartByRelay.set(relayKey, Date.now())
startedCount++
const relay = await that.pool.ensureRelay(url, { connectionTimeout: 5000 }).catch(() => {
return undefined
})
// cannot connect to relay
if (!relay) {
if (!eosed) {
eosedCount++
eosed = eosedCount >= startedCount
oneose?.(eosed)
}
return {
close: () => {}
}
}
return relay.subscribe(filters, { // One request per (relay, filter) so pool groups by relay and sends one REQ per relay with all filters
receivedEvent: (relay, id) => { const requests = relays.flatMap((url) =>
that.trackEventSeenOn(id, relay) filters.map((f) => ({ url: normalizeUrl(url) || url, filter: f }))
}, )
alreadyHaveEvent: (id: string) => {
const have = _knownIds.has(id)
if (have) {
return true
}
_knownIds.add(id)
return false
},
onevent: (evt: NEvent) => {
onevent?.(evt)
},
oneose: () => {
// make sure eosed is not called multiple times
if (eosed) return
eosedCount++
eosed = eosedCount >= startedCount
oneose?.(eosed)
},
onclose: (reason: string) => {
// auth-required
if (reason.startsWith('auth-required') && !hasAuthed) {
// already logged in
if (that.signer) {
relay
.auth(async (authEvt: EventTemplate) => {
const evt = await that.signer!.signEvent(authEvt)
if (!evt) {
throw new Error('sign event failed')
}
return evt as VerifiedEvent
})
.then(() => {
hasAuthed = true
if (!eosed) {
subPromises.push(startSub())
}
})
.catch(() => {
// ignore
})
return
}
// open login dialog
if (startLogin) {
startLogin()
return
}
}
// close the subscription const poolCloser = this.pool.subscribeMap(requests, {
closedCount++ onevent: (evt: NEvent) => onevent?.(evt),
closeReasons.push(reason) oneose: () => oneose?.(true),
onclose?.(url, reason) onclose: (reasons: string[]) => {
if (closedCount >= startedCount) { relays.forEach((url, i) => onclose?.(url, reasons[i] ?? ''))
onAllClose?.(closeReasons) onAllClose?.(reasons)
} },
return onauth: async (authEvt: EventTemplate) => {
}, if (that.signer) {
eoseTimeout: 10_000 // 10s const evt = await that.signer.signEvent(authEvt)
}) if (!evt) throw new Error('sign event failed')
} return evt as VerifiedEvent
}
startLogin?.()
throw new Error('Login required')
},
alreadyHaveEvent: (id: string) => {
const have = _knownIds.has(id)
if (have) return true
_knownIds.add(id)
return false
},
eoseTimeout: 10_000
}) })
const handleNewEventFromInternal = (data: Event) => { const handleNewEventFromInternal = (data: Event) => {
@ -774,15 +695,7 @@ class ClientService extends EventTarget {
return { return {
close: () => { close: () => {
this.removeEventListener('newEvent', handleNewEventFromInternal) this.removeEventListener('newEvent', handleNewEventFromInternal)
subPromises.forEach((subPromise) => { poolCloser.close()
subPromise
.then((sub) => {
sub.close()
})
.catch(() => {
// Silent fail
})
})
} }
} }
} }

Loading…
Cancel
Save