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 }) {
onClick={() => push(toRelay(relay))} onClick={() => push(toRelay(relay))}
> >
<div className="flex items-center gap-2 flex-1"> <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 className="flex-1 w-0 truncate font-semibold">{relay}</div>
</div> </div>
<Button <Button

2
src/components/FavoriteRelaysSetting/RelayItem.tsx

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

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

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

3
src/providers/FavoriteRelaysProvider.tsx

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

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

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

44
src/services/client.service.ts

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

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

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

Loading…
Cancel
Save