Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
c3cbed749c
  1. 14
      src/components/Note/index.tsx
  2. 5
      src/lib/nostr-relay-auth-patch.ts
  3. 27
      src/lib/relay-strikes.ts
  4. 37
      src/services/client-query.service.ts
  5. 13
      src/services/client.service.ts
  6. 49
      src/services/relay-operation-log.service.ts

14
src/components/Note/index.tsx

@ -318,6 +318,9 @@ export default function Note({ @@ -318,6 +318,9 @@ export default function Note({
hideMetadata?: boolean
className?: string
} = {}) => {
if (isNip18RepostKind(displayEvent.kind)) {
return <RepostEventContent className={className} event={displayEvent} />
}
const embeddedEvent = findTrailingStringifiedNostrEvent(displayEvent.content ?? '')
if (embeddedEvent) {
return (
@ -333,6 +336,9 @@ export default function Note({ @@ -333,6 +336,9 @@ export default function Note({
)
}
if (isStringifiedJsonContent(displayEvent.content)) {
if (isNip18RepostKind(displayEvent.kind)) {
return <RepostEventContent className={className} event={displayEvent} />
}
return (
<pre
className={cn(
@ -379,7 +385,11 @@ export default function Note({ @@ -379,7 +385,11 @@ export default function Note({
return (
<MarkdownArticle
className={className}
event={displayEvent}
event={
isNip18RepostKind(displayEvent.kind)
? { ...displayEvent, content: '' }
: displayEvent
}
hideMetadata={hideMetadata}
lazyMedia={!autoLoadMedia}
fullCalendarInvite={fullCalendarInvite}
@ -399,7 +409,7 @@ export default function Note({ @@ -399,7 +409,7 @@ export default function Note({
content = <NsfwNote show={() => setShowNsfw(true)} />
} else if (isNip25ReactionKind(event.kind)) {
content = null
} else if (isNip18RepostKind(event.kind)) {
} else if (isNip18RepostKind(displayEvent.kind)) {
content = <RepostEventContent className="mt-2" event={displayEvent} />
} else if (event.kind === ExtendedKind.POLL_RESPONSE) {
content = <NotificationEventCard className="mt-2" event={displayEvent} />

5
src/lib/nostr-relay-auth-patch.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import logger from '@/lib/logger'
import { notifyRelayNip42Accepted, notifyRelayNip42Rejected } from '@/lib/relay-auth-feedback'
import type { AbstractRelay } from 'nostr-tools/abstract-relay'
import type { EventTemplate, VerifiedEvent } from 'nostr-tools'
@ -65,9 +64,6 @@ export function patchPoolRelayAuthRaceAndFeedback(relay: object): void { @@ -65,9 +64,6 @@ export function patchPoolRelayAuthRaceAndFeedback(relay: object): void {
const r = asRelayInternals(this)
if (!r.connectionPromise && typeof message === 'string' && message.startsWith('["AUTH"')) {
abortPendingAuthForDeadSocket(r, message)
logger.debug('[RelayOp] Dropped AUTH (socket already closed; connect timeout vs signing race)', {
url: r.url
})
return Promise.resolve()
}
return origSend.call(this, message) as Promise<void>
@ -91,7 +87,6 @@ export function patchPoolRelayAuthRaceAndFeedback(relay: object): void { @@ -91,7 +87,6 @@ export function patchPoolRelayAuthRaceAndFeedback(relay: object): void {
msg.includes('relay connection closed before AUTH') ||
/relay connection closed/i.test(msg)
if (benignRace) {
logger.debug('[RelayOp] Relay AUTH aborted (benign race)', { url: r.url, detail: msg })
r.authPromise = undefined
return ''
}

27
src/lib/relay-strikes.ts

@ -65,8 +65,6 @@ function sessionKey(url: string): string { @@ -65,8 +65,6 @@ function sessionKey(url: string): string {
class RelaySessionStrikes {
private byKey = new Map<string, StrikeEntry>()
private cacheRelayKeys = new Set<string>()
/** Throttle debug spam when many parallel REQs hit the same dead relay (cache rows bypass strike debounce). */
private lastReadFailureDebugLogAt = new Map<string, number>()
setSessionCacheRelayKeysFromKind10432(ev: Event | null | undefined): void {
this.cacheRelayKeys.clear()
@ -136,13 +134,8 @@ class RelaySessionStrikes { @@ -136,13 +134,8 @@ class RelaySessionStrikes {
}
private applyRateLimitCooldownKey(key: string): void {
const now = Date.now()
const e = this.getEntry(key)
e.rateLimitUntil = Math.max(e.rateLimitUntil, now + RATE_LIMIT_COOLDOWN_MS)
logger.debug('[RelayStrikes] rate-limit cooldown', {
key,
untilMs: e.rateLimitUntil - now
})
e.rateLimitUntil = Math.max(e.rateLimitUntil, Date.now() + RATE_LIMIT_COOLDOWN_MS)
}
/** WS connect failure, HTTP transport failure, etc. */
@ -159,8 +152,7 @@ class RelaySessionStrikes { @@ -159,8 +152,7 @@ class RelaySessionStrikes {
// Cache relays from kind 10432 (e.g. localhost on another machine) always accrue failures.
if (now < e.rateLimitUntil && !this.cacheRelayKeys.has(key)) return
const cache = this.cacheRelayKeys.has(key)
if (!cache) {
if (!this.cacheRelayKeys.has(key)) {
if (now - e.readLastStrikeIncrementAt < STRIKE_INCREMENT_DEBOUNCE_MS) return
e.readLastStrikeIncrementAt = now
}
@ -168,13 +160,7 @@ class RelaySessionStrikes { @@ -168,13 +160,7 @@ class RelaySessionStrikes {
e.readFailures += 1
if (e.readFailures >= STRIKE_FAILURES_THRESHOLD) {
e.readStrikeSkipUntil = Math.max(e.readStrikeSkipUntil, now + STRIKE_COOLDOWN_MS)
logger.info('[RelayStrikes] read path strike skip', { key, readFailures: e.readFailures })
} else {
const lastDbg = this.lastReadFailureDebugLogAt.get(key) ?? 0
if (now - lastDbg >= STRIKE_INCREMENT_DEBOUNCE_MS) {
this.lastReadFailureDebugLogAt.set(key, now)
logger.debug('[RelayStrikes] read failure counted', { key, readFailures: e.readFailures, cache })
}
logger.warn('[RelayStrikes] read path strike skip', { key, readFailures: e.readFailures })
}
}
@ -186,7 +172,6 @@ class RelaySessionStrikes { @@ -186,7 +172,6 @@ class RelaySessionStrikes {
e.readFailures = 0
e.readStrikeSkipUntil = 0
e.readLastStrikeIncrementAt = 0
this.lastReadFailureDebugLogAt.delete(key)
}
recordPublishFailure(url: string): void {
@ -195,15 +180,14 @@ class RelaySessionStrikes { @@ -195,15 +180,14 @@ class RelaySessionStrikes {
const now = Date.now()
const e = this.getEntry(key)
if (now < e.rateLimitUntil && !this.cacheRelayKeys.has(key)) return
const cache = this.cacheRelayKeys.has(key)
if (!cache) {
if (!this.cacheRelayKeys.has(key)) {
if (now - e.publishLastStrikeIncrementAt < STRIKE_INCREMENT_DEBOUNCE_MS) return
e.publishLastStrikeIncrementAt = now
}
e.publishFailures += 1
if (e.publishFailures >= STRIKE_FAILURES_THRESHOLD) {
e.publishStrikeSkipUntil = Math.max(e.publishStrikeSkipUntil, now + STRIKE_COOLDOWN_MS)
logger.info('[RelayStrikes] publish path strike skip', { key, publishFailures: e.publishFailures })
logger.warn('[RelayStrikes] publish path strike skip', { key, publishFailures: e.publishFailures })
}
}
@ -248,7 +232,6 @@ class RelaySessionStrikes { @@ -248,7 +232,6 @@ class RelaySessionStrikes {
reset(): void {
this.byKey.clear()
this.cacheRelayKeys.clear()
this.lastReadFailureDebugLogAt.clear()
}
}

37
src/services/client-query.service.ts

@ -28,7 +28,7 @@ import { @@ -28,7 +28,7 @@ import {
import { applyRelayNip42AckTimeout } from '@/lib/relay-nip42-tuning'
import { isIndexRelayTransportFailure, queryIndexRelay } from '@/lib/index-relay-http'
import logger from '@/lib/logger'
import { isHttpRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url'
import { canonicalRelaySessionKey, isHttpRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url'
import { RelaySubscribeOpBatch, type RelayOpTerminalRow } from '@/services/relay-operation-log.service'
import { patchRelayNoticeForFetchFailures } from '@/services/relay-notice-fetch-failure'
import type { Filter, Event as NEvent } from 'nostr-tools'
@ -405,11 +405,14 @@ export class QueryService { @@ -405,11 +405,14 @@ export class QueryService {
/** One chunk → pass a single Filter (compat); several (e.g. kinds split) → full array for WS + HTTP. */
const effectiveFilter: Filter | Filter[] =
sanitizedFilters.length === 1 ? sanitizedFilters[0]! : sanitizedFilters
const eoseTimeout = options?.eoseTimeout ?? 500
const hasNip50Search = filtersHaveNip50Search(sanitizedFilters)
const globalTimeoutRaw = options?.globalTimeout ?? 10000
const useNip50QueryTimeoutFloor =
hasNip50Search && options?.relayOpSource === 'fetchEventsFromSingleRelay'
/** After all relays EOSE, wait this long before closing so slow `EVENT` tails are not cut off (NIP-50 is heavy). */
const eoseTimeout = useNip50QueryTimeoutFloor
? Math.max(options?.eoseTimeout ?? 500, 3_000)
: options?.eoseTimeout ?? 500
const globalTimeoutRaw = options?.globalTimeout ?? 10000
const globalTimeout = useNip50QueryTimeoutFloor
? Math.max(globalTimeoutRaw, NIP50_QUERY_GLOBAL_TIMEOUT_FLOOR_MS)
: globalTimeoutRaw
@ -733,7 +736,7 @@ export class QueryService { @@ -733,7 +736,7 @@ export class QueryService {
relayOpMeta?: {
source: string
logLevel?: 'info' | 'debug'
/** When true, suppress `[RelayOp] batch_begin` / `batch_end` (used by {@link QueryService.query}). */
/** When true (default on batches), suppress `[RelayOp] batch_begin` / `batch_end`. */
quiet?: boolean
onBatchEnd?: (rows: RelayOpTerminalRow[]) => void
}
@ -780,19 +783,27 @@ export class QueryService { @@ -780,19 +783,27 @@ export class QueryService {
grouped.get(key)!.push(...filters)
}
const searchableSet = new Set([
...SEARCHABLE_RELAY_URLS.map((u) => normalizeUrl(u) || u),
...nip66Service.getSearchableRelayUrls().map((u) => normalizeUrl(u) || u),
...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)
])
const searchableSet = new Set(
[
...SEARCHABLE_RELAY_URLS,
...nip66Service.getSearchableRelayUrls(),
...PROFILE_RELAY_URLS
]
.map((u) => canonicalRelaySessionKey(normalizeUrl(u) || String(u).trim()))
.filter((k): k is string => k.length > 0)
)
const hasNip50Search = filtersHaveNip50Search(filters)
/** Single-relay NIP-50 (e.g. search page / per-relay spell): caller targets one index relay — never strip `search`. */
const singleRelayNip50 = hasNip50Search && grouped.size === 1
const groupedRequests = Array.from(grouped.entries()).map(([url, f]) => {
const relaySupportsSearch = searchableSet.has(url) || nip66Service.isRelaySearchable(url)
const filtersForRelay = f.map((one) => filterForRelay(one, relaySupportsSearch))
const sessionKey = canonicalRelaySessionKey(url)
const relaySupportsSearch =
searchableSet.has(sessionKey) || nip66Service.isRelaySearchable(url)
const filtersForRelay = singleRelayNip50 ? f : f.map((one) => filterForRelay(one, relaySupportsSearch))
return { url, filters: filtersForRelay }
})
const hasNip50Search = filtersHaveNip50Search(filters)
const relaySubscriptionEoseTimeoutMs = hasNip50Search
? NIP50_RELAY_SUBSCRIPTION_EOSE_TIMEOUT_MS
: 10_000

13
src/services/client.service.ts

@ -2068,17 +2068,6 @@ class ClientService extends EventTarget { @@ -2068,17 +2068,6 @@ class ClientService extends EventTarget {
) {
const timelineBatchId = `tl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`
const timelineT0 = performance.now()
logger.debug('[RelayOp] timeline_wave_begin', {
timelineBatchId,
shardCount: subRequests.length,
relayCountsPerShard: subRequests.map((r) => r.urls.length),
shards: subRequests.map((s, shardIndex) => ({
shardIndex,
relayCount: s.urls.length,
relaysSample: [...new Set(s.urls.map((u) => normalizeUrl(u) || u))].slice(0, 40),
filter: compactFilterForRelayLog(s.filter as Filter)
}))
})
logger.debug('[relay-req] timeline_batch_start', {
timelineBatchId,
subRequestCount: subRequests.length,
@ -3243,6 +3232,8 @@ class ClientService extends EventTarget { @@ -3243,6 +3232,8 @@ class ClientService extends EventTarget {
globalTimeout: options?.globalTimeout ?? 25_000,
relayOpSource: 'fetchEventsFromSingleRelay' as const,
foreground: true as const,
/** NIP-50 must run to EOSE; implicit feed grace would close the REQ after the first hit. */
firstRelayResultGraceMs: false as const,
...(options?.signal ? { signal: options.signal } : {})
}

49
src/services/relay-operation-log.service.ts

@ -139,9 +139,9 @@ function groupTerminalsByOutcome(rows: RelayOpTerminalRow[]): Record<string, { c @@ -139,9 +139,9 @@ function groupTerminalsByOutcome(rows: RelayOpTerminalRow[]): Record<string, { c
* Tracks one logical subscribe/query wave: one `batch_begin` and one `batch_end` with per-relay outcomes.
*/
export type RelaySubscribeOpBatchOptions = {
/** `info` logs every REQ wave at INFO; default `debug` keeps subscribe noise behind jumble-debug / VITE_DEBUG. */
/** When `quiet` is false: `info` logs batch_end at INFO; `debug` logs batch_begin/batch_end at DEBUG. */
logLevel?: 'info' | 'debug'
/** When true, skip `[RelayOp] batch_begin` / `batch_end` lines (e.g. when {@link QueryService.query} logs `req_begin`/`req_end`). */
/** When true (default), skip `[RelayOp] batch_begin` / `batch_end`. Set false to opt into REQ wave logs. */
quiet?: boolean
/** Invoked once when this REQ wave finishes (same `rows` as `batch_end` / `terminals`). */
onBatchEnd?: (rows: RelayOpTerminalRow[]) => void
@ -198,7 +198,7 @@ export class RelaySubscribeOpBatch { @@ -198,7 +198,7 @@ export class RelaySubscribeOpBatch {
this.source = source
this.grouped = grouped
this.logLevel = options?.logLevel ?? 'debug'
this.quiet = options?.quiet ?? false
this.quiet = options?.quiet ?? true
this.onBatchEnd = options?.onBatchEnd
}
@ -340,14 +340,7 @@ export class RelayPublishOpBatch { @@ -340,14 +340,7 @@ export class RelayPublishOpBatch {
}
logBegin(): void {
logger.debug('[RelayOp] publish_batch_begin', {
batchId: this.batchId,
source: this.source,
eventId: this.eventId,
relayCount: this.relays.length,
relays: this.relays,
commands: this.relays.map((relay, cmdIndex) => ({ cmdIndex, relay, eventId: this.eventId }))
})
/* Intentionally quiet: publish outcomes surface in UI; failures are logged in logEnd. */
}
record(cmdIndex: number, relayUrl: string, ok: boolean, error?: string): void {
@ -365,7 +358,7 @@ export class RelayPublishOpBatch { @@ -365,7 +358,7 @@ export class RelayPublishOpBatch {
)
const ok = this.results.filter((r) => r.ok)
const fail = this.results.filter((r) => !r.ok)
const sorted = this.results.sort((a, b) => a.cmdIndex - b.cmdIndex)
this.results.sort((a, b) => a.cmdIndex - b.cmdIndex)
const readableSummary =
this.relays.length === 0
? 'No relays targeted (empty list or skipped by session rules).'
@ -381,31 +374,25 @@ export class RelayPublishOpBatch { @@ -381,31 +374,25 @@ export class RelayPublishOpBatch {
]
.filter(Boolean)
.join('\n')
logger.debug('[RelayOp] publish_batch_end', {
if (this.relays.length === 0 && status === 'no_targets') {
logger.warn('[RelayOp] publish_batch_end — no relay targets', {
batchId: this.batchId,
source: this.source,
eventId: this.eventId,
status
})
return
}
if (fail.length > 0) {
logger.warn(`[RelayOp] publish_batch_end — ${readableSummary}`, {
batchId: this.batchId,
source: this.source,
eventId: this.eventId,
status,
elapsedMs,
okCount: ok.length,
failCount: fail.length,
readableSummary,
byState: {
ok: {
count: ok.length,
relays: ok.map((r) => r.relayUrl),
hosts: ok.map((r) => relayHostForPublishLog(r.relayUrl)),
cmdIndices: ok.map((r) => r.cmdIndex)
},
fail: {
count: fail.length,
relays: fail.map((r) => r.relayUrl),
hosts: fail.map((r) => relayHostForPublishLog(r.relayUrl)),
cmdIndices: fail.map((r) => r.cmdIndex),
errors: fail.map((r) => r.error ?? '')
}
},
results: sorted
failCount: fail.length
})
}
}
}

Loading…
Cancel
Save