Browse Source

bug-fixes

imwald
Silberengel 4 weeks ago
parent
commit
aef8537c62
  1. 4
      package-lock.json
  2. 2
      package.json
  3. 41
      src/components/PaytoDialog/index.tsx
  4. 1
      src/components/PaytoLink/index.tsx
  5. 25
      src/components/ZapDialog/index.tsx
  6. 1
      src/constants.ts
  7. 20
      src/features/feed/relay-policy.test.ts
  8. 10
      src/features/feed/relay-policy.ts
  9. 14
      src/lib/pre-publish-relay-cap.ts
  10. 41
      src/lib/relay-publish-filter.test.ts
  11. 65
      src/lib/relay-publish-filter.ts
  12. 3
      src/lib/relay-url-priority.test.ts
  13. 24
      src/lib/relay-url-priority.ts
  14. 9
      src/services/client.service.ts
  15. 23
      src/services/relay-selection.service.ts

4
package-lock.json generated

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
{
"name": "imwald",
"version": "23.13.0",
"version": "23.13.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "imwald",
"version": "23.13.0",
"version": "23.13.1",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",

2
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "imwald",
"version": "23.13.0",
"version": "23.13.1",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true,
"type": "module",

41
src/components/PaytoDialog/index.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import TipPublicMessagePrompt from '@/components/ZapDialog/TipPublicMessagePrompt'
import {
Dialog,
DialogContent,
@ -8,6 +9,7 @@ import { @@ -8,6 +9,7 @@ import {
import { Button } from '@/components/ui/button'
import { Copy, ExternalLink, Wallet, Zap } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { useRef, useState } from 'react'
import { toast } from 'sonner'
import {
filterPaytoPaymentOpenHandlersForDevice,
@ -15,6 +17,7 @@ import { @@ -15,6 +17,7 @@ import {
getPaytoTypeInfo
} from '@/lib/payto'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import LightningInvoiceSection from './LightningInvoiceSection'
export default function PaytoDialog({
@ -22,15 +25,21 @@ export default function PaytoDialog({ @@ -22,15 +25,21 @@ export default function PaytoDialog({
onOpenChange,
type,
authority,
paytoUri
paytoUri,
recipientPubkey
}: {
open: boolean
onOpenChange: (open: boolean) => void
type: string
authority: string
paytoUri: string
/** When set, closing the dialog offers a kind-24 tip notice to this pubkey. */
recipientPubkey?: string
}) {
const { t } = useTranslation()
const { pubkey: selfPubkey } = useNostr()
const [tipNoticeOpen, setTipNoticeOpen] = useState(false)
const skipTipNoticeOnCloseRef = useRef(false)
const info = getPaytoTypeInfo(type)
const label = info?.label ?? type
const isLightning = type.toLowerCase() === 'lightning'
@ -41,11 +50,29 @@ export default function PaytoDialog({ @@ -41,11 +50,29 @@ export default function PaytoDialog({
const handleCopy = (text: string, copyLabel?: string) => {
navigator.clipboard.writeText(text)
toast.success(copyLabel ? t('Copied {{label}} address', { label: copyLabel }) : t('Copied to clipboard'))
onOpenChange(false)
handleDialogOpenChange(false)
}
const maybeOfferTipNoticeOnClose = () => {
if (!recipientPubkey) return
if (skipTipNoticeOnCloseRef.current) return
if (selfPubkey && recipientPubkey === selfPubkey) return
setTipNoticeOpen(true)
}
const handleDialogOpenChange = (next: boolean) => {
if (!next) {
maybeOfferTipNoticeOnClose()
skipTipNoticeOnCloseRef.current = false
} else {
skipTipNoticeOnCloseRef.current = false
}
onOpenChange(next)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<>
<Dialog open={open} onOpenChange={handleDialogOpenChange}>
<DialogContent
className={cn(
'left-[50%] top-[50%] flex w-[calc(100vw-1.25rem)] max-w-md translate-x-[-50%] translate-y-[-50%] flex-col gap-0',
@ -133,5 +160,13 @@ export default function PaytoDialog({ @@ -133,5 +160,13 @@ export default function PaytoDialog({
</div>
</DialogContent>
</Dialog>
{recipientPubkey ? (
<TipPublicMessagePrompt
open={tipNoticeOpen}
onOpenChange={setTipNoticeOpen}
recipientPubkey={recipientPubkey}
/>
) : null}
</>
)
}

1
src/components/PaytoLink/index.tsx

@ -151,6 +151,7 @@ export default function PaytoLink({ @@ -151,6 +151,7 @@ export default function PaytoLink({
type={type}
authority={authority}
paytoUri={raw}
recipientPubkey={pubkey}
/>
)}
</>

25
src/components/ZapDialog/index.tsx

@ -119,7 +119,6 @@ export default function ZapDialog({ @@ -119,7 +119,6 @@ export default function ZapDialog({
: t('Send a payment to this user')
const maybeOfferTipNoticeOnClose = () => {
if (paymentsOnly) return
if (skipTipNoticeOnCloseRef.current) return
if (selfPubkey && pubkey === selfPubkey) return
setTipNoticeOpen(true)
@ -198,13 +197,11 @@ export default function ZapDialog({ @@ -198,13 +197,11 @@ export default function ZapDialog({
}}
/>
</DrawerContent>
{!paymentsOnly && (
<TipPublicMessagePrompt
open={tipNoticeOpen}
onOpenChange={setTipNoticeOpen}
recipientPubkey={pubkey}
/>
)}
<TipPublicMessagePrompt
open={tipNoticeOpen}
onOpenChange={setTipNoticeOpen}
recipientPubkey={pubkey}
/>
</Drawer>
)
}
@ -237,13 +234,11 @@ export default function ZapDialog({ @@ -237,13 +234,11 @@ export default function ZapDialog({
/>
</DialogContent>
</Dialog>
{!paymentsOnly && (
<TipPublicMessagePrompt
open={tipNoticeOpen}
onOpenChange={setTipNoticeOpen}
recipientPubkey={pubkey}
/>
)}
<TipPublicMessagePrompt
open={tipNoticeOpen}
onOpenChange={setTipNoticeOpen}
recipientPubkey={pubkey}
/>
</>
)
}

1
src/constants.ts

@ -425,6 +425,7 @@ export const DOCUMENT_RELAY_URLS = [ @@ -425,6 +425,7 @@ export const DOCUMENT_RELAY_URLS = [
*/
export const READ_ONLY_RELAY_URLS = [
'wss://aggr.nostr.land',
'wss://nostr.land',
'wss://relay.nostr.watch',
'wss://relaypag.es',
'wss://relay.noswhere.com',

20
src/features/feed/relay-policy.test.ts

@ -66,4 +66,24 @@ describe('applyFeedRelayPolicy', () => { @@ -66,4 +66,24 @@ describe('applyFeedRelayPolicy', () => {
})
)
})
it('excludes profile/index mirrors for kind 1 writes', () => {
const result = applyFeedRelayPolicy(
[
{
source: 'viewer-write',
urls: ['wss://profiles.nostrver.se/', 'wss://relay.example/']
}
],
{ operation: 'write', eventKind: 1, applySocialKindBlockedFilter: false }
)
expect(result.urls).toEqual(['wss://relay.example/'])
expect(result.dropped).toContainEqual(
expect.objectContaining({
normalizedUrl: 'wss://profiles.nostrver.se/',
reason: 'profile-index-for-write'
})
)
})
})

10
src/features/feed/relay-policy.ts

@ -3,6 +3,7 @@ import { @@ -3,6 +3,7 @@ import {
SOCIAL_KIND_BLOCKED_RELAY_URLS,
relayFilterIncludesSocialKindBlockedKind
} from '@/constants'
import { relayAllowsPublishKind } from '@/lib/relay-publish-filter'
import { AGGR_NOSTR_LAND_WSS } from '@/lib/nostr-land-aggr'
import { getViewerRelayStackNostrLandAggrEligible } from '@/lib/nostr-land-relay-eligibility'
import {
@ -19,6 +20,7 @@ export type FeedRelayDropReason = @@ -19,6 +20,7 @@ export type FeedRelayDropReason =
| 'duplicate'
| 'user-blocked'
| 'read-only-for-write'
| 'profile-index-for-write'
| 'social-kind-blocked'
| 'extended-tag-blocked'
| 'third-party-local'
@ -195,6 +197,14 @@ export function applyFeedRelayPolicy( @@ -195,6 +197,14 @@ export function applyFeedRelayPolicy(
addDrop(dropped, normalized, layer.source, 'read-only-for-write')
continue
}
if (
(context.operation === 'write' || context.operation === 'publish-picker') &&
context.eventKind !== undefined &&
!relayAllowsPublishKind(normalized, context.eventKind)
) {
addDrop(dropped, normalized, layer.source, 'profile-index-for-write')
continue
}
if (
socialFilter &&
isSocialKindBlockedRelay(key) &&

14
src/lib/pre-publish-relay-cap.ts

@ -1,9 +1,6 @@ @@ -1,9 +1,6 @@
import {
isSocialKindBlockedKind,
MAX_PUBLISH_RELAYS,
READ_ONLY_RELAY_URLS,
SOCIAL_KIND_BLOCKED_RELAY_URLS
} from '@/constants'
import { isSocialKindBlockedKind, MAX_PUBLISH_RELAYS, SOCIAL_KIND_BLOCKED_RELAY_URLS } from '@/constants'
import { kinds } from 'nostr-tools'
import { filterRelaysForEventPublish } from '@/lib/relay-publish-filter'
import { dedupeNormalizeRelayUrlsOrdered } from '@/lib/relay-url-priority'
import { normalizeAnyRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url'
import type { NostrEvent } from 'nostr-tools'
@ -49,12 +46,11 @@ export function computePrePublishRelayCapPreview({ @@ -49,12 +46,11 @@ export function computePrePublishRelayCapPreview({
.map((u) => normalizeHttpRelayUrl(u) || u)
.filter((u): u is string => !!u)
let outbox = dedupeNormalizeRelayUrlsOrdered([...httpOut, ...wsOut])
const readOnlySet = new Set(READ_ONLY_RELAY_URLS.map((u) => normalizeAnyRelayUrl(u) || u))
const previewKind = kinds.ShortTextNote
const socialBlockedSet = new Set(SOCIAL_KIND_BLOCKED_RELAY_URLS.map((u) => normalizeUrl(u) || u))
outbox = dedupeNormalizeRelayUrlsOrdered(
outbox.filter((url) => {
filterRelaysForEventPublish(outbox, previewKind).filter((url) => {
const n = normalizeAnyRelayUrl(url) || url
if (readOnlySet.has(n)) return false
if (applySocialOutboxFilter && socialBlockedSet.has(n)) return false
return true
})

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

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
import { kinds } from 'nostr-tools'
import { describe, expect, it } from 'vitest'
import {
filterContextAuthorReadRelaysForPublish,
filterRelaysForEventPublish,
relayAllowsPublishKind
} from './relay-publish-filter'
describe('relay-publish-filter', () => {
it('blocks profile/index mirrors for kind 1 and 7', () => {
expect(relayAllowsPublishKind('wss://profiles.nostr1.com/', kinds.ShortTextNote)).toBe(false)
expect(relayAllowsPublishKind('wss://purplepag.es/', kinds.Reaction)).toBe(false)
expect(relayAllowsPublishKind('wss://indexer.coracle.social/', kinds.ShortTextNote)).toBe(false)
})
it('allows profile/index mirrors for kind 0 and 10002', () => {
expect(relayAllowsPublishKind('wss://profiles.nostrver.se/', kinds.Metadata)).toBe(true)
expect(relayAllowsPublishKind('wss://indexer.coracle.social/', kinds.RelayList)).toBe(true)
})
it('strips read-only aggregators and profile mirrors from publish lists', () => {
const out = filterRelaysForEventPublish(
[
'wss://nostr.land/',
'wss://profiles.nostr1.com/',
'wss://relay.primal.net/',
'wss://aggr.nostr.land/'
],
kinds.ShortTextNote
)
expect(out).toEqual(['wss://relay.primal.net/'])
})
it('strips profile mirrors from author read hints', () => {
const out = filterContextAuthorReadRelaysForPublish([
'wss://profiles.nostrver.se/',
'wss://relay.example.com/'
])
expect(out).toEqual(['wss://relay.example.com/'])
})
})

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

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
import {
AUTHOR_PROFILE_VIEW_REPLACEABLE_KINDS,
READ_ONLY_RELAY_URLS
} from '@/constants'
import { normalizeAnyRelayUrl } from '@/lib/url'
/**
* Profile mirrors and indexers that reject notes, reactions, and other social kinds.
* Distinct from {@link READ_ONLY_RELAY_URLS} (search/index aggregators) and
* {@link SOCIAL_KIND_BLOCKED_RELAY_URLS} (subset also listed here for kind 1 / 1111 / 11).
*/
export const PROFILE_INDEX_ONLY_RELAY_URLS = [
'wss://profiles.nostr1.com',
'wss://purplepag.es',
'wss://profiles.nostrver.se/',
'wss://indexer.coracle.social/'
] as const
const profileIndexOnlyKeySet = new Set(
PROFILE_INDEX_ONLY_RELAY_URLS.map((u) => (normalizeAnyRelayUrl(u) || u).toLowerCase()).filter(Boolean)
)
const readOnlyKeySet = new Set(
READ_ONLY_RELAY_URLS.map((u) => (normalizeAnyRelayUrl(u) || u).toLowerCase()).filter(Boolean)
)
const profileIndexPublishKindSet = new Set<number>(AUTHOR_PROFILE_VIEW_REPLACEABLE_KINDS)
function relayKey(url: string): string {
return (normalizeAnyRelayUrl(url) || url.trim()).toLowerCase()
}
export function isProfileIndexOnlyRelay(url: string): boolean {
const key = relayKey(url)
return key.length > 0 && profileIndexOnlyKeySet.has(key)
}
export function isReadOnlyRelayUrl(url: string): boolean {
const key = relayKey(url)
return key.length > 0 && readOnlyKeySet.has(key)
}
/** True when this relay may receive an EVENT for `eventKind` (profile/list replaceables only on profile mirrors). */
export function relayAllowsPublishKind(url: string, eventKind: number): boolean {
if (!isProfileIndexOnlyRelay(url)) return true
return profileIndexPublishKindSet.has(eventKind)
}
export function filterRelaysForEventPublish(urls: readonly string[], eventKind: number): string[] {
return urls.filter((u) => relayAllowsPublishKind(u, eventKind) && !isReadOnlyRelayUrl(u))
}
/**
* 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).
*/
export function filterContextAuthorReadRelaysForPublish(urls: readonly string[]): string[] {
return urls.filter((u) => {
const key = relayKey(u)
if (!key) return false
if (isReadOnlyRelayUrl(u)) return false
if (isProfileIndexOnlyRelay(u)) return false
return true
})
}

3
src/lib/relay-url-priority.test.ts

@ -9,12 +9,13 @@ import { stripMailboxLocalUrlsForRemoteViewers } from '@/lib/relay-list-sanitize @@ -9,12 +9,13 @@ import { stripMailboxLocalUrlsForRemoteViewers } from '@/lib/relay-list-sanitize
import { syncViewerRelayStackNostrLandAggrEligible } from '@/lib/nostr-land-relay-eligibility'
describe('filterContextAuthorReadRelaysForPublish', () => {
it('drops loopback, LAN, and .onion; keeps public relays', () => {
it('drops loopback, LAN, .onion, and profile/index mirrors; keeps public relays', () => {
const out = filterContextAuthorReadRelaysForPublish([
'ws://localhost:4869/',
'wss://127.0.0.1/',
'wss://192.168.0.5/',
'wss://abcdefghijklmnop.onion/',
'wss://profiles.nostrver.se/',
'wss://relay.example.com/'
])
expect(out).toEqual(['wss://relay.example.com/'])

24
src/lib/relay-url-priority.ts

@ -21,12 +21,14 @@ export function dedupeNormalizeRelayUrlsOrdered(urls: string[]): string[] { @@ -21,12 +21,14 @@ export function dedupeNormalizeRelayUrlsOrdered(urls: string[]): string[] {
return out
}
import { filterContextAuthorReadRelaysForPublish as stripNonInboxPublishHints } from '@/lib/relay-publish-filter'
/**
* NIP-65 **read** (inbox) hints from reply/mention context must never add LAN, loopback, or Tor-only
* endpoints to the publish list those are the author's private reachability, not yours.
* NIP-65 **read** (inbox) hints from reply/mention context must never add LAN, loopback, Tor-only,
* read-only aggregators, or profile/index mirrors to the publish list.
*/
export function filterContextAuthorReadRelaysForPublish(urls: string[]): string[] {
return dedupeNormalizeRelayUrlsOrdered(urls).filter((u) => {
const reachable = dedupeNormalizeRelayUrlsOrdered(urls).filter((u) => {
const n = normalizeAnyRelayUrl(u) || u.trim()
if (!n) return false
if (isLocalNetworkUrl(u) || isLocalNetworkUrl(n)) return false
@ -38,6 +40,7 @@ export function filterContextAuthorReadRelaysForPublish(urls: string[]): string[ @@ -38,6 +40,7 @@ export function filterContextAuthorReadRelaysForPublish(urls: string[]): string[
}
return true
})
return dedupeNormalizeRelayUrlsOrdered(stripNonInboxPublishHints(reachable))
}
/** LAN / local host relays first, then the rest; deduped. */
@ -141,7 +144,7 @@ function buildWriteRelayPriorityLayers(opts: { @@ -141,7 +144,7 @@ function buildWriteRelayPriorityLayers(opts: {
authorReadRelays?: string[]
favoriteRelays?: string[]
extraRelays?: string[]
/** When false, omit global FAST_WRITE and FAST_READ tails. Default true. */
/** When false, omit global FAST_WRITE tail. Default true. */
includeGlobalFastWriteReadTails?: boolean
}): string[][] {
const tier1 = relayUrlsLocalsFirst(opts.userWriteRelays)
@ -149,15 +152,15 @@ function buildWriteRelayPriorityLayers(opts: { @@ -149,15 +152,15 @@ function buildWriteRelayPriorityLayers(opts: {
const tier3 = dedupeNormalizeRelayUrlsOrdered(opts.favoriteRelays ?? [])
const tier4 = dedupeNormalizeRelayUrlsOrdered(opts.extraRelays ?? [])
if (opts.includeGlobalFastWriteReadTails === false) {
return [tier1, tier2, tier3, tier4, [], []]
return [tier1, tier2, tier3, tier4, []]
}
const tier5 = normFastWrite()
const tier6 = normFastRead()
return [tier1, tier2, tier3, tier4, tier5, tier6]
return [tier1, tier2, tier3, tier4, tier5]
}
/**
* Publish / write: user outboxes (locals first) target author inboxes favorites extras FAST_WRITE FAST_READ.
* Publish / write: user outboxes (locals first) target author inboxes favorites extras FAST_WRITE.
* Read aggregators ({@link FAST_READ_RELAY_URLS}) are intentionally omitted they reject social writes.
*/
export function buildPrioritizedWriteRelayUrls(opts: {
userWriteRelays: string[]
@ -168,7 +171,7 @@ export function buildPrioritizedWriteRelayUrls(opts: { @@ -168,7 +171,7 @@ export function buildPrioritizedWriteRelayUrls(opts: {
maxRelays?: number
/** When true, strip {@link SOCIAL_KIND_BLOCKED_RELAY_URLS} before capping (social kinds). */
applySocialKindBlockedFilter?: boolean
/** Default true: append FAST_WRITE then FAST_READ tiers. */
/** Default true: append FAST_WRITE tier. */
includeGlobalFastWriteReadTails?: boolean
}): string[] {
const max = opts.maxRelays ?? MAX_PUBLISH_RELAYS
@ -184,8 +187,7 @@ export function buildPrioritizedWriteRelayUrls(opts: { @@ -184,8 +187,7 @@ export function buildPrioritizedWriteRelayUrls(opts: {
{ source: 'author-read', urls: layers[1] ?? [] },
{ source: 'favorites', urls: layers[2] ?? [] },
{ source: 'explicit', urls: layers[3] ?? [] },
{ source: 'fast-write', urls: layers[4] ?? [] },
{ source: 'fast-read', urls: layers[5] ?? [] }
{ source: 'fast-write', urls: layers[4] ?? [] }
], {
operation: 'write',
blockedRelays: opts.blockedRelays,

9
src/services/client.service.ts

@ -129,6 +129,7 @@ import { hexPubkeysEqual, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/ @@ -129,6 +129,7 @@ import { hexPubkeysEqual, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/
import { collectNip05ValuesFromKind0 } from '@/lib/profile-metadata-search'
import { decodeProfileSearchQueryToPubkeyHex } from '@/lib/profile-search-query'
import { getPubkeysFromPTags, tagNameEquals } from '@/lib/tag'
import { filterRelaysForEventPublish } from '@/lib/relay-publish-filter'
import {
buildPrioritizedWriteRelayUrls,
dedupeNormalizeRelayUrlsOrdered,
@ -710,12 +711,10 @@ class ClientService extends EventTarget { @@ -710,12 +711,10 @@ class ClientService extends EventTarget {
* Normalize, dedupe, then cap at {@link MAX_PUBLISH_RELAYS}.
*/
private filterPublishingRelays(relays: string[], event: NEvent): string[] {
const readOnlySet = new Set(READ_ONLY_RELAY_URLS.map((u) => normalizeAnyRelayUrl(u) || u))
const socialKindBlockedSet = new Set(SOCIAL_KIND_BLOCKED_RELAY_URLS.map((u) => normalizeUrl(u) || u))
return dedupeNormalizeRelayUrlsOrdered(
relays.filter((url) => {
filterRelaysForEventPublish(relays, event.kind).filter((url) => {
const n = normalizeAnyRelayUrl(url) || url
if (readOnlySet.has(n)) return false
if (isSocialKindBlockedKind(event.kind) && socialKindBlockedSet.has(n)) return false
return true
})
@ -1589,11 +1588,9 @@ class ClientService extends EventTarget { @@ -1589,11 +1588,9 @@ class ClientService extends EventTarget {
: relayUrls
}
const readOnlySet = new Set(READ_ONLY_RELAY_URLS.map((u) => normalizeAnyRelayUrl(u) || u))
const socialKindBlockedSet = new Set(SOCIAL_KIND_BLOCKED_RELAY_URLS.map((u) => normalizeUrl(u) || u))
let filtered = mergedRelayUrls.filter((url) => {
let filtered = filterRelaysForEventPublish(mergedRelayUrls, event.kind).filter((url) => {
const n = normalizeAnyRelayUrl(url) || url
if (readOnlySet.has(n)) return false
if (isSocialKindBlockedKind(event.kind) && socialKindBlockedSet.has(n)) return false
return true
})

23
src/services/relay-selection.service.ts

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import { Event, kinds } from 'nostr-tools'
import { ExtendedKind, FAST_WRITE_RELAY_URLS, RANDOM_PUBLISH_RELAY_COUNT, READ_ONLY_RELAY_URLS } from '@/constants'
import { ExtendedKind, FAST_WRITE_RELAY_URLS, RANDOM_PUBLISH_RELAY_COUNT } from '@/constants'
import { filterRelaysForEventPublish } from '@/lib/relay-publish-filter'
import storage from '@/services/local-storage.service'
import { NOSTR_URI_FOR_REPLY_PUBKEYS_REGEX } from '@/lib/content-patterns'
import client from '@/services/client.service'
@ -176,7 +177,7 @@ class RelaySelectionService { @@ -176,7 +177,7 @@ class RelaySelectionService {
}
const deduplicatedRelays = order.map((o) => o.url)
const filtered = this.filterReadOnlyRelays(
const filtered = this.filterPublishPickerRelays(
this.filterBlockedRelays(deduplicatedRelays, context.blockedRelays)
)
const relayTypes: Record<string, RelaySourceType> = {}
@ -186,7 +187,7 @@ class RelaySelectionService { @@ -186,7 +187,7 @@ class RelaySelectionService {
return {
relays: filtered,
relayTypes,
randomRelayUrls: this.filterReadOnlyRelays(randomRelayUrls)
randomRelayUrls: this.filterPublishPickerRelays(randomRelayUrls)
}
}
@ -438,7 +439,7 @@ class RelaySelectionService { @@ -438,7 +439,7 @@ class RelaySelectionService {
selectedRelays = Array.from(new Set(selectedRelays))
}
return this.filterReadOnlyRelays(this.filterBlockedRelays(selectedRelays, context.blockedRelays))
return this.filterPublishPickerRelays(this.filterBlockedRelays(selectedRelays, context.blockedRelays))
}
/**
@ -808,17 +809,11 @@ class RelaySelectionService { @@ -808,17 +809,11 @@ class RelaySelectionService {
}
/**
* Strip relays that never accept writes ({@link READ_ONLY_RELAY_URLS}) so they do not appear in the publish picker.
* Same set as `ClientService` uses when filtering publish targets.
* Strip read-only aggregators and profile/index mirrors from the post/reaction publish picker
* (notes and reactions are not kind 0 / NIP-65 list traffic).
*/
private filterReadOnlyRelays(relays: string[]): string[] {
const readOnlySet = new Set(
READ_ONLY_RELAY_URLS.map((u) => normalizeAnyRelayUrl(u) || u).filter(Boolean)
)
return relays.filter((relay) => {
const n = normalizeAnyRelayUrl(relay) || relay
return !readOnlySet.has(n)
})
private filterPublishPickerRelays(relays: string[]): string[] {
return filterRelaysForEventPublish(relays, kinds.ShortTextNote)
}
/**

Loading…
Cancel
Save