Browse Source

bug-fixes

imwald
Silberengel 4 weeks ago
parent
commit
ef3cf1160b
  1. 28
      src/lib/index-relay-http.test.ts
  2. 45
      src/lib/index-relay-http.ts
  3. 14
      src/lib/relay-strikes.test.ts
  4. 3
      src/lib/relay-strikes.ts
  5. 2
      src/services/client-query.service.ts

28
src/lib/index-relay-http.test.ts

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
import {
IndexRelayTransportError,
clearDevIndexRelayUnavailableThisSession,
isDevIndexRelayUnavailableThisSession,
isIndexRelayTransportFailure
} from '@/lib/index-relay-http'
import { describe, expect, it, beforeEach } from 'vitest'
describe('isIndexRelayTransportFailure', () => {
it('treats IndexRelayTransportError as transport failure', () => {
expect(isIndexRelayTransportFailure(new IndexRelayTransportError())).toBe(true)
expect(isIndexRelayTransportFailure(new IndexRelayTransportError(new Error('HTTP 500')))).toBe(true)
})
it('treats network TypeError as transport failure', () => {
expect(isIndexRelayTransportFailure(new TypeError('Failed to fetch'))).toBe(true)
})
})
describe('dev index relay session skip', () => {
beforeEach(() => {
clearDevIndexRelayUnavailableThisSession()
})
it('starts available after clear', () => {
expect(isDevIndexRelayUnavailableThisSession()).toBe(false)
})
})

45
src/lib/index-relay-http.ts

@ -79,6 +79,7 @@ function warnIndexRelayHttpThrottled(endpoint: string, message: string, meta: Re @@ -79,6 +79,7 @@ function warnIndexRelayHttpThrottled(endpoint: string, message: string, meta: Re
/** True when the relay cannot be reached (down, DNS, browser blocked, etc.). Not HTTP 4xx/5xx from a live server. */
export function isIndexRelayTransportFailure(err: unknown): boolean {
if (err instanceof IndexRelayTransportError) return true
if (err == null || typeof err !== 'object') return false
const e = err as Error & { name?: string; cause?: unknown }
if (e.name === 'AbortError') return false
@ -107,6 +108,39 @@ function isDevViteIndexRelayProxyPath(endpoint: string): boolean { @@ -107,6 +108,39 @@ function isDevViteIndexRelayProxyPath(endpoint: string): boolean {
)
}
/**
* When the Vite `/dev-index-relay` proxy returns 5xx, skip further dev index HTTP fetches for this tab
* (same pattern as optional `/sites/` and translate proxies). Cleared on a successful filter response.
*/
let devIndexRelayUnavailableThisSession = false
let devIndexRelaySkipLogged = false
export function isDevIndexRelayUnavailableThisSession(): boolean {
return devIndexRelayUnavailableThisSession
}
export function clearDevIndexRelayUnavailableThisSession(): void {
devIndexRelayUnavailableThisSession = false
devIndexRelaySkipLogged = false
}
function markDevIndexRelayUnavailableFromHttpStatus(status: number, endpoint: string): void {
if (!isDevViteIndexRelayProxyPath(endpoint)) return
if (status < 500 || status > 599) return
if (devIndexRelayUnavailableThisSession) return
devIndexRelayUnavailableThisSession = true
if (!devIndexRelaySkipLogged) {
devIndexRelaySkipLogged = true
logger.debug(
`[IndexRelayHttp] Dev index relay returned HTTP ${status}; skipping further dev-index-relay fetches this session (other relays continue).`
)
}
}
function shouldSkipDevIndexRelayFetch(endpoint: string): boolean {
return import.meta.env.DEV && devIndexRelayUnavailableThisSession && isDevViteIndexRelayProxyPath(endpoint)
}
function maybeLogDevIndexRelayUnreachableHint(): void {
if (import.meta.env.PROD || typeof window === 'undefined') return
const now = Date.now()
@ -191,6 +225,9 @@ export async function queryIndexRelay( @@ -191,6 +225,9 @@ export async function queryIndexRelay(
const filters = Array.isArray(filter) ? filter : [filter]
const out: NEvent[] = []
const seen = new Set<string>()
if (shouldSkipDevIndexRelayFetch(endpoint)) {
return out
}
for (const f of filters) {
const body = nostrFilterToIndexRelayBody(filterForIndexRelay(f))
try {
@ -213,6 +250,7 @@ export async function queryIndexRelay( @@ -213,6 +250,7 @@ export async function queryIndexRelay(
/* ignore */
}
if (res.status >= 500 && res.status <= 599) {
markDevIndexRelayUnavailableFromHttpStatus(res.status, endpoint)
maybeLogDevIndexRelayHttpErrorHint(res.status, detail || undefined)
} else {
logger.debug('[IndexRelayHttp] filter HTTP response', {
@ -228,10 +266,12 @@ export async function queryIndexRelay( @@ -228,10 +266,12 @@ export async function queryIndexRelay(
})
}
if (res.status >= 500) {
markDevIndexRelayUnavailableFromHttpStatus(res.status, endpoint)
throw new IndexRelayTransportError(new Error(`HTTP ${res.status}`))
}
continue
}
clearDevIndexRelayUnavailableThisSession()
const json = (await res.json()) as { data?: unknown }
const data = json.data
if (!Array.isArray(data)) continue
@ -245,11 +285,12 @@ export async function queryIndexRelay( @@ -245,11 +285,12 @@ export async function queryIndexRelay(
}
} catch (e) {
if ((e as Error).name === 'AbortError') throw e
if (e instanceof IndexRelayTransportError) throw e
if (isIndexRelayTransportFailure(e)) {
handleFilterTransportFailure(endpoint, e)
} else {
warnIndexRelayHttpThrottled(endpoint, '[IndexRelayHttp] filter request error', { endpoint, error: e })
throw new IndexRelayTransportError(e)
}
warnIndexRelayHttpThrottled(endpoint, '[IndexRelayHttp] filter request error', { endpoint, error: e })
}
}
return out

14
src/lib/relay-strikes.test.ts

@ -43,6 +43,20 @@ describe('relaySessionStrikes.observeSubscribeBatch', () => { @@ -43,6 +43,20 @@ describe('relaySessionStrikes.observeSubscribeBatch', () => {
})
})
describe('relaySessionStrikes HTTP read failures', () => {
beforeEach(() => {
relaySessionStrikes.reset()
})
it('session-skips after five parallel HTTP failures (no debounce)', () => {
const url = 'https://index.example.com/'
for (let i = 0; i < 5; i++) {
relaySessionStrikes.recordReadFailure(url, 'http')
}
expect(relaySessionStrikes.isReadHttpSkipped(url)).toBe(true)
})
})
describe('relaySessionStrikes.clearKey', () => {
beforeEach(() => {
relaySessionStrikes.reset()

3
src/lib/relay-strikes.ts

@ -174,7 +174,8 @@ class RelaySessionStrikes { @@ -174,7 +174,8 @@ class RelaySessionStrikes {
if (now < e.rateLimitUntil && !this.cacheRelayKeys.has(key)) return
if (!this.cacheRelayKeys.has(key)) {
if (now - e.readLastStrikeIncrementAt < STRIKE_INCREMENT_DEBOUNCE_MS) return
// HTTP index failures often arrive in parallel; count each so session skip engages quickly.
if (_source !== 'http' && now - e.readLastStrikeIncrementAt < STRIKE_INCREMENT_DEBOUNCE_MS) return
e.readLastStrikeIncrementAt = now
}

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

@ -475,7 +475,7 @@ export class QueryService { @@ -475,7 +475,7 @@ export class QueryService {
urls
.filter((u) => isHttpRelayUrl(u))
.map((u) => normalizeHttpRelayUrl(u) || u)
.filter(Boolean)
.filter((u): u is string => Boolean(u) && !relaySessionStrikes.isReadHttpSkipped(u))
)
)
const wsQueryUrls = urls.filter((u) => !isHttpRelayUrl(u))

Loading…
Cancel
Save