Browse Source

make blocked relays more persistent

imwald
Silberengel 2 weeks ago
parent
commit
d12e599d5b
  1. 2
      src/components/FavoriteRelaysSetting/BlockedRelayItem.tsx
  2. 2
      src/components/FavoriteRelaysSetting/RelayItem.tsx
  3. 4
      src/lib/viewer-blocked-relays.ts
  4. 3
      src/providers/FavoriteRelaysProvider.tsx
  5. 5
      src/services/client-query.service.ts
  6. 44
      src/services/client.service.ts
  7. 4
      src/services/relay-info.service.ts

2
src/components/FavoriteRelaysSetting/BlockedRelayItem.tsx

@ -33,7 +33,7 @@ export default function BlockedRelayItem({ relay }: { relay: string }) { @@ -33,7 +33,7 @@ export default function BlockedRelayItem({ relay }: { relay: string }) {
onClick={() => push(toRelay(relay))}
>
<div className="flex items-center gap-2 flex-1">
<RelayIcon url={relay} />
<RelayIcon url={relay} skipRelayInfoFetch />
<div className="flex-1 w-0 truncate font-semibold">{relay}</div>
</div>
<Button

2
src/components/FavoriteRelaysSetting/RelayItem.tsx

@ -36,7 +36,7 @@ export default function RelayItem({ relay, isBlocked = false }: { relay: string; @@ -36,7 +36,7 @@ export default function RelayItem({ relay, isBlocked = false }: { relay: string;
<GripVertical className="size-4 text-muted-foreground" />
</div>
<div className="flex gap-2 items-center flex-1 min-w-0">
<RelayIcon url={relay} />
<RelayIcon url={relay} skipRelayInfoFetch={isBlocked} />
<div className="flex items-center gap-2 flex-1 min-w-0">
<div className="flex-1 truncate font-semibold">{relay}</div>
{isBlocked && (

4
src/lib/viewer-blocked-relays.ts

@ -25,6 +25,10 @@ export function getViewerBlockedRelayUrls(): readonly string[] { @@ -25,6 +25,10 @@ export function getViewerBlockedRelayUrls(): readonly string[] {
return viewerBlockedRelayUrls
}
export function isViewerRelayBlocked(url: string): boolean {
return isRelayBlockedByUser(url, viewerBlockedRelayUrls)
}
/** Drop user-blocked relays (hostname-aware) before any REQ / query / WebSocket connect. */
export function filterViewerBlockedRelaysForFetch(urls: readonly string[]): string[] {
if (!viewerBlockedRelayUrls.length) return [...urls]

3
src/providers/FavoriteRelaysProvider.tsx

@ -7,7 +7,7 @@ import { getRelaySetFromEvent } from '@/lib/event-metadata' @@ -7,7 +7,7 @@ import { getRelaySetFromEvent } from '@/lib/event-metadata'
import { randomString } from '@/lib/random'
import { isWebsocketUrl, normalizeAnyRelayUrl, normalizeUrl } from '@/lib/url'
import { setViewerBlockedRelayUrls } from '@/lib/viewer-blocked-relays'
import { queryService } from '@/services/client.service'
import client, { queryService } from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
import { TRelaySet } from '@/types'
import { Event, kinds } from 'nostr-tools'
@ -159,6 +159,7 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode @@ -159,6 +159,7 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
useEffect(() => {
setViewerBlockedRelayUrls(blockedRelays)
client.closeViewerBlockedRelayConnections()
}, [blockedRelays])
useEffect(() => {

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

@ -41,6 +41,7 @@ import type { Filter, Event as NEvent } from 'nostr-tools' @@ -41,6 +41,7 @@ import type { Filter, Event as NEvent } from 'nostr-tools'
import { SimplePool, EventTemplate, VerifiedEvent, nip19 } from 'nostr-tools'
import type { AbstractRelay } from 'nostr-tools/abstract-relay'
import { sanitizeRelayUrlsForFetch, isRelayConnectionAllowedForViewer, grantRelayConnectionOperationScope } from '@/lib/read-only-relay-personal'
import { filterViewerBlockedRelaysForFetch } from '@/lib/viewer-blocked-relays'
import { closeRelayPoolSocketsIfIdle } from '@/lib/relay-pool-idle'
import { publicReadRelayFallbackUrls } from '@/lib/viewer-relay-defaults'
import nip66Service from './nip66.service'
@ -516,7 +517,9 @@ export class QueryService { @@ -516,7 +517,9 @@ export class QueryService {
? FIRST_RELAY_RESULT_GRACE_MS
: null
const httpRelayBases = httpIndexBasesForRelayQuery(urls, options?.httpIndexRelayBases ?? [])
const httpRelayBases = filterViewerBlockedRelaysForFetch(
httpIndexBasesForRelayQuery(urls, options?.httpIndexRelayBases ?? [])
)
.filter((u) => !relaySessionStrikes.isReadHttpSkipped(u))
.filter((u) => isRelayConnectionAllowedForViewer(u))
const httpKeys = new Set(httpRelayBases.map((u) => canonicalRelaySessionKey(u)))

44
src/services/client.service.ts

@ -69,6 +69,9 @@ import { @@ -69,6 +69,9 @@ import {
viewerUsesGlobalRelayDefaults
} from '@/lib/viewer-relay-defaults'
import {
filterViewerBlockedRelaysForFetch,
getViewerBlockedRelayUrls,
isRelayBlockedByUser,
parseBlockedRelayUrlsFromEvent,
setViewerBlockedRelayUrls
} from '@/lib/viewer-blocked-relays'
@ -720,11 +723,6 @@ class ClientService extends EventTarget { @@ -720,11 +723,6 @@ class ClientService extends EventTarget {
/** IndexedDB-first: personal lists (incl. cache + HTTP) before policy or network so locals stay allowed. */
const storageUrls = await this.collectViewerPersonalRelayUrlsFromStorage(pk)
this.viewerHttpIndexRelayBases = storageUrls.httpIndexBases
setViewerPersonalRelayKeys(buildPersonalRelayKeySet(storageUrls.all), { viewerActive: true })
syncViewerRelayStackNostrLandAggrEligible(storageUrls.all)
relaySessionStrikes.setSessionCacheRelayKeysFromKind10432(storageUrls.cacheRelayEvent)
this.closeMetadataPolicyDisallowedRelayConnections()
try {
const blockedEvt = await indexedDb.getReplaceableEvent(pk, ExtendedKind.BLOCKED_RELAYS)
@ -733,6 +731,13 @@ class ClientService extends EventTarget { @@ -733,6 +731,13 @@ class ClientService extends EventTarget {
setViewerBlockedRelayUrls([])
}
this.viewerHttpIndexRelayBases = filterViewerBlockedRelaysForFetch(storageUrls.httpIndexBases)
setViewerPersonalRelayKeys(buildPersonalRelayKeySet(storageUrls.all), { viewerActive: true })
syncViewerRelayStackNostrLandAggrEligible(storageUrls.all)
relaySessionStrikes.setSessionCacheRelayKeysFromKind10432(storageUrls.cacheRelayEvent)
this.closeMetadataPolicyDisallowedRelayConnections()
this.closeViewerBlockedRelayConnections()
const urls = [...storageUrls.all]
try {
urls.push(...(await this.fetchFavoriteRelays(pk)))
@ -742,6 +747,7 @@ class ClientService extends EventTarget { @@ -742,6 +747,7 @@ class ClientService extends EventTarget {
setViewerPersonalRelayKeys(buildPersonalRelayKeySet(urls), { viewerActive: true })
syncViewerRelayStackNostrLandAggrEligible(urls)
this.closeMetadataPolicyDisallowedRelayConnections()
this.closeViewerBlockedRelayConnections()
}
/** NIP-65 / 10243 / 10432 / favorites (10012) from IndexedDB only — no network. */
@ -802,8 +808,9 @@ class ClientService extends EventTarget { @@ -802,8 +808,9 @@ class ClientService extends EventTarget {
.map((u) => normalizeHttpRelayUrl(u) || u)
.filter(Boolean)
if (fresh.length > 0) {
this.viewerHttpIndexRelayBases = fresh
return fresh
const filtered = filterViewerBlockedRelaysForFetch(fresh)
this.viewerHttpIndexRelayBases = filtered
return filtered
}
} catch {
/* keep session cache */
@ -835,6 +842,20 @@ class ClientService extends EventTarget { @@ -835,6 +842,20 @@ class ClientService extends EventTarget {
}
}
/** Close pooled sockets to relays on the viewer block list (hostname-aware, wss/https). */
closeViewerBlockedRelayConnections(): void {
const blocked = getViewerBlockedRelayUrls()
if (!blocked.length) return
try {
const toClose = [...this.pool.listConnectionStatus().keys()].filter((url) =>
isRelayBlockedByUser(url, blocked)
)
if (toClose.length > 0) this.pool.close(toClose)
} catch {
// ignore
}
}
/** NIP-66: fetch relay discovery events (30166) in background to supplement search/NIP support. */
private async fetchNip66RelayDiscovery(): Promise<void> {
if (isMetadataRelaysOnlyPolicyActive()) return
@ -3190,12 +3211,9 @@ class ClientService extends EventTarget { @@ -3190,12 +3211,9 @@ class ClientService extends EventTarget {
let eosedAt: number | null = null
let eventIds = new Set<string>()
const httpTimelinePollBases = httpIndexBasesForRelayQuery(
originalDedupedRelays,
this.viewerHttpIndexRelayBases
).filter(
(u) => relayAuthoritativeTimeline || !relaySessionStrikes.isReadHttpSkipped(u)
)
const httpTimelinePollBases = filterViewerBlockedRelaysForFetch(
httpIndexBasesForRelayQuery(originalDedupedRelays, this.viewerHttpIndexRelayBases)
).filter((u) => relayAuthoritativeTimeline || !relaySessionStrikes.isReadHttpSkipped(u))
let httpPollIntervalId: ReturnType<typeof setInterval> | null = null
let httpPollCursorUnix = 0
const clearHttpTimelinePoll = () => {

4
src/services/relay-info.service.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { isViewerRelayBlocked } from '@/lib/viewer-blocked-relays'
import {
devProxyCorsProblematicHttpsIndexRelayBase,
devProxyLoopbackHttpRelayBase,
@ -142,6 +143,9 @@ class RelayInfoService { @@ -142,6 +143,9 @@ class RelayInfoService {
}
private async _getRelayInfo(url: string) {
if (isViewerRelayBlocked(url)) {
return undefined
}
const exist = this.relayInfoMap.get(url)
if (exist && !this.isStale(exist)) {
return exist

Loading…
Cancel
Save