Browse Source

suppress some relay strikes

imwald
Silberengel 2 weeks ago
parent
commit
ae19f2d237
  1. 12
      src/lib/relay-publish-filter.test.ts
  2. 21
      src/lib/relay-publish-filter.ts
  3. 25
      src/lib/relay-strikes.test.ts
  4. 4
      src/lib/relay-strikes.ts
  5. 7
      src/services/client.service.ts

12
src/lib/relay-publish-filter.test.ts

@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest' @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'
import {
filterContextAuthorReadRelaysForPublish,
filterRelaysForEventPublish,
isRelayPublishPolicyRejection,
relayAllowsPublishKind
} from './relay-publish-filter'
@ -38,4 +39,15 @@ describe('relay-publish-filter', () => { @@ -38,4 +39,15 @@ describe('relay-publish-filter', () => {
])
expect(out).toEqual(['wss://relay.example.com/'])
})
it('detects relay kind-policy rejections (not infrastructure faults)', () => {
expect(
isRelayPublishPolicyRejection(
'only published longform articles accepted on this relay (kind 30023)'
)
).toBe(true)
expect(isRelayPublishPolicyRejection('this relay only accepts kind 1')).toBe(true)
expect(isRelayPublishPolicyRejection('Remote relay connection timeout')).toBe(false)
expect(isRelayPublishPolicyRejection('Publish timeout after 8000ms')).toBe(false)
})
})

21
src/lib/relay-publish-filter.ts

@ -50,6 +50,27 @@ export function filterRelaysForEventPublish(urls: readonly string[], eventKind: @@ -50,6 +50,27 @@ export function filterRelaysForEventPublish(urls: readonly string[], eventKind:
return urls.filter((u) => relayAllowsPublishKind(u, eventKind) && !isReadOnlyRelayUrl(u))
}
/**
* Relay refused the EVENT due to kind / content policy (not connectivity).
* These are expected when publishing to specialty relays do not session-strike the relay.
*/
export function isRelayPublishPolicyRejection(message: string): boolean {
const m = message.trim().toLowerCase()
if (!m) return false
if (/\bkind\s*[:\s]?\s*\d+\b/.test(m) && /(accept|accepted|only|not supported|unsupported|reject|refused|allow|allowed|permitted)/.test(m)) {
return true
}
if (/only .{0,120} (accept|accepted|allowed|permitted)/.test(m)) return true
if (/(does not|don't|do not) accept/.test(m)) return true
if (/not accepted on this relay/.test(m)) return true
if (/wrong kind/.test(m)) return true
if (/unsupported kind/.test(m)) return true
if (/kind not (supported|allowed|permitted)/.test(m)) return true
if (/event kind (blocked|not allowed|rejected)/.test(m)) return true
if (/this relay only accepts/.test(m)) return true
return false
}
/**
* Reply/mention author **read** hints used as publish targets: never LAN/Tor, read-only aggregators,
* or profile/index mirrors (those are not inboxes for notes or reactions).

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

@ -96,6 +96,31 @@ describe('relaySessionStrikes.clearKey', () => { @@ -96,6 +96,31 @@ describe('relaySessionStrikes.clearKey', () => {
})
})
describe('relaySessionStrikes publish failures', () => {
beforeEach(() => {
relaySessionStrikes.reset()
})
it('does not strike when relay rejects due to kind policy', () => {
const url = 'wss://essayist.decentnewsroom.com/'
for (let i = 0; i < 10; i++) {
relaySessionStrikes.recordPublishFailure(
url,
'only published longform articles accepted on this relay (kind 30023)'
)
}
expect(relaySessionStrikes.isPublishSkipped(url)).toBe(false)
})
it('strikes after repeated infrastructure publish failures', () => {
const url = 'wss://relay.example.com/'
relaySessionStrikes.recordPublishFailure(url, 'websocket closed')
const snap = relaySessionStrikes.getDebugSnapshot()
const entry = snap.entries.find((e) => e.key.includes('relay.example.com'))
expect(entry?.entry.publishFailures).toBe(1)
})
})
describe('isRelayStrikeEntryActive', () => {
it('is false for empty entry', () => {
expect(

4
src/lib/relay-strikes.ts

@ -7,6 +7,7 @@ import { @@ -7,6 +7,7 @@ import {
import type { Event } from 'nostr-tools'
import { getRelayListFromEvent } from '@/lib/event-metadata'
import logger from '@/lib/logger'
import { isRelayPublishPolicyRejection } from '@/lib/relay-publish-filter'
import { canonicalRelaySessionKey, httpIndexRelayBasesInUrlBatch, isLocalNetworkUrl } from '@/lib/url'
import type { RelayOpTerminalRow } from '@/services/relay-operation-log.service'
@ -288,7 +289,8 @@ class RelaySessionStrikes { @@ -288,7 +289,8 @@ class RelaySessionStrikes {
return true
}
recordPublishFailure(url: string): void {
recordPublishFailure(url: string, errorMessage?: string): void {
if (errorMessage && isRelayPublishPolicyRejection(errorMessage)) return
const key = sessionKey(url)
if (!key) return
const now = Date.now()

7
src/services/client.service.ts

@ -1965,13 +1965,13 @@ class ClientService extends EventTarget { @@ -1965,13 +1965,13 @@ class ClientService extends EventTarget {
logger.error(`[PublishEvent] Auth or publish failed`, { url, error: authError.message })
errors.push({ url, error: authError })
relayStatuses.push({ url, success: false, error: authError.message })
relaySessionStrikes.recordPublishFailure(url)
relaySessionStrikes.recordPublishFailure(url, authError.message)
})
} else {
logger.error(`[PublishEvent] Publish failed`, { url, error: error.message })
errors.push({ url, error })
relayStatuses.push({ url, success: false, error: error.message })
relaySessionStrikes.recordPublishFailure(url)
relaySessionStrikes.recordPublishFailure(url, error.message)
}
})
@ -2029,7 +2029,8 @@ class ClientService extends EventTarget { @@ -2029,7 +2029,8 @@ class ClientService extends EventTarget {
success: false,
error: error instanceof Error ? error.message : 'Connection failed'
})
relaySessionStrikes.recordPublishFailure(url)
const errMsg = error instanceof Error ? error.message : 'Connection failed'
relaySessionStrikes.recordPublishFailure(url, errMsg)
} finally {
clearTimeout(relayTimeout)
const currentFinished = ++finishedCount

Loading…
Cancel
Save