Browse Source

bug-fix

stop using protected events with  "-" tags
imwald
Silberengel 3 weeks ago
parent
commit
ca532b07f4
  1. 2
      src/components/KindFilter/index.tsx
  2. 3
      src/components/PostEditor/PollEditor.tsx
  3. 16
      src/components/PostEditor/PostContent.tsx
  4. 15
      src/components/PostEditor/PostRelaySelector.tsx
  5. 2
      src/lib/discussion-thread-composer.ts
  6. 19
      src/lib/draft-event.ts
  7. 24
      src/services/relay-selection.service.ts

2
src/components/KindFilter/index.tsx

@ -179,7 +179,7 @@ export default function KindFilter({
}} }}
> >
<ListFilter className="size-3.5 shrink-0" /> <ListFilter className="size-3.5 shrink-0" />
<span className="ml-1 hidden min-[22rem]:inline">{t('Filter')}</span> <span className="ml-1 hidden min-[352px]:inline">{t('Filter')}</span>
{isDifferentFromSaved && ( {isDifferentFromSaved && (
<div className="absolute size-1.5 rounded-full bg-primary left-6 top-1.5 ring-1 ring-background" /> <div className="absolute size-1.5 rounded-full bg-primary left-6 top-1.5 ring-1 ring-background" />
)} )}

3
src/components/PostEditor/PollEditor.tsx

@ -27,8 +27,6 @@ export default function PollEditor({
pollCreateData.endsAt ? dayjs(pollCreateData.endsAt * 1000).format('YYYY-MM-DDTHH:mm') : '' pollCreateData.endsAt ? dayjs(pollCreateData.endsAt * 1000).format('YYYY-MM-DDTHH:mm') : ''
) )
const [additionalRelayUrls, setAdditionalRelayUrls] = useState<string[]>(pollCreateData.relays) const [additionalRelayUrls, setAdditionalRelayUrls] = useState<string[]>(pollCreateData.relays)
const [_isProtectedEvent, setIsProtectedEvent] = useState(false)
useEffect(() => { useEffect(() => {
setPollCreateData({ setPollCreateData({
isMultipleChoice, isMultipleChoice,
@ -115,7 +113,6 @@ export default function PollEditor({
<div className="space-y-2"> <div className="space-y-2">
<PostRelaySelector <PostRelaySelector
setAdditionalRelayUrls={setAdditionalRelayUrls} setAdditionalRelayUrls={setAdditionalRelayUrls}
setIsProtectedEvent={setIsProtectedEvent}
content={content} content={content}
/> />
</div> </div>

16
src/components/PostEditor/PostContent.tsx

@ -99,7 +99,7 @@ import { TDraftEvent } from '@/types'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Switch } from '@/components/ui/switch' import { Switch } from '@/components/ui/switch'
import { DISCUSSION_TOPICS } from '@/pages/primary/DiscussionsPage/discussionTopics' import { DISCUSSION_TOPICS } from '@/pages/primary/DiscussionsPage/discussionTopics'
import { getReplaceableCoordinateFromEvent, isProtectedEvent as isEventProtected, isReplaceableEvent, isReplyNoteEvent } from '@/lib/event' import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
import { Event, kinds } from 'nostr-tools' import { Event, kinds } from 'nostr-tools'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -283,7 +283,6 @@ export default function PostContent({
const [extractedMentions, setExtractedMentions] = useState<string[]>( const [extractedMentions, setExtractedMentions] = useState<string[]>(
initialPublicMessageTo ? [initialPublicMessageTo] : [] initialPublicMessageTo ? [initialPublicMessageTo] : []
) )
const [isProtectedEvent, setIsProtectedEvent] = useState(false)
const [additionalRelayUrls, setAdditionalRelayUrls] = useState<string[]>([]) const [additionalRelayUrls, setAdditionalRelayUrls] = useState<string[]>([])
/** When set, too many relays are checked vs the per-publish cap; publish stays disabled until unchecking. */ /** When set, too many relays are checked vs the per-publish cap; publish stays disabled until unchecking. */
const [relayCapBlockInfo, setRelayCapBlockInfo] = useState<{ const [relayCapBlockInfo, setRelayCapBlockInfo] = useState<{
@ -644,7 +643,6 @@ export default function PostContent({
contentOk && contentOk &&
(!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2) && (!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2) &&
(!isPublicMessage || extractedMentions.length > 0 || parentEvent?.kind === ExtendedKind.PUBLIC_MESSAGE) && (!isPublicMessage || extractedMentions.length > 0 || parentEvent?.kind === ExtendedKind.PUBLIC_MESSAGE) &&
(!isProtectedEvent || additionalRelayUrls.length > 0) &&
(!isHighlight || highlightData.sourceValue.trim() !== '') && (!isHighlight || highlightData.sourceValue.trim() !== '') &&
(!isCitationInternal || !!citationInternalCTag.trim()) && (!isCitationInternal || !!citationInternalCTag.trim()) &&
(!isCitationExternal || (!!citationExternalUrl.trim() && !!citationAccessedOn.trim())) && (!isCitationExternal || (!!citationExternalUrl.trim() && !!citationAccessedOn.trim())) &&
@ -665,7 +663,6 @@ export default function PostContent({
isPublicMessage, isPublicMessage,
extractedMentions, extractedMentions,
parentEvent, parentEvent,
isProtectedEvent,
additionalRelayUrls, additionalRelayUrls,
isHighlight, isHighlight,
highlightData, highlightData,
@ -810,13 +807,6 @@ export default function PostContent({
const addExpirationTag = storage.getDefaultExpirationEnabled() const addExpirationTag = storage.getDefaultExpirationEnabled()
const expirationMonths = storage.getDefaultExpirationMonths() const expirationMonths = storage.getDefaultExpirationMonths()
// Determine if we should use protected event tag
let shouldUseProtectedEvent = false
if (parentEvent) {
const isParentOP = !isReplyNoteEvent(parentEvent)
const parentHasProtectedTag = isEventProtected(parentEvent)
shouldUseProtectedEvent = isParentOP && parentHasProtectedTag
}
// Public messages - check BEFORE media notes to ensure PMs with media stay as PMs // Public messages - check BEFORE media notes to ensure PMs with media stay as PMs
if (isPublicMessage) { if (isPublicMessage) {
@ -874,7 +864,6 @@ export default function PostContent({
mentions, mentions,
{ {
addClientTag, addClientTag,
protectedEvent: shouldUseProtectedEvent,
isNsfw, isNsfw,
addExpirationTag: addExpirationTag && isChattingKind(ExtendedKind.VOICE_COMMENT), addExpirationTag: addExpirationTag && isChattingKind(ExtendedKind.VOICE_COMMENT),
expirationMonths, expirationMonths,
@ -1106,7 +1095,6 @@ export default function PostContent({
if (parentEvent && parentEvent.kind !== kinds.ShortTextNote) { if (parentEvent && parentEvent.kind !== kinds.ShortTextNote) {
return await createCommentDraftEvent(cleanedText, parentEvent, mentions, { return await createCommentDraftEvent(cleanedText, parentEvent, mentions, {
addClientTag, addClientTag,
protectedEvent: shouldUseProtectedEvent,
isNsfw, isNsfw,
addExpirationTag: addExpirationTag && isChattingKind(ExtendedKind.COMMENT), addExpirationTag: addExpirationTag && isChattingKind(ExtendedKind.COMMENT),
expirationMonths, expirationMonths,
@ -1129,7 +1117,6 @@ export default function PostContent({
return await createShortTextNoteDraftEvent(cleanedText, mentions, { return await createShortTextNoteDraftEvent(cleanedText, mentions, {
parentEvent, parentEvent,
addClientTag, addClientTag,
protectedEvent: shouldUseProtectedEvent,
isNsfw, isNsfw,
addExpirationTag: addExpirationTag && isChattingKind(kinds.ShortTextNote), addExpirationTag: addExpirationTag && isChattingKind(kinds.ShortTextNote),
expirationMonths, expirationMonths,
@ -3370,7 +3357,6 @@ export default function PostContent({
)} )}
> >
<PostRelaySelector <PostRelaySelector
setIsProtectedEvent={setIsProtectedEvent}
setAdditionalRelayUrls={setAdditionalRelayUrls} setAdditionalRelayUrls={setAdditionalRelayUrls}
onRelayPublishCapChange={handleRelayPublishCapChange} onRelayPublishCapChange={handleRelayPublishCapChange}
parentEvent={parentEvent} parentEvent={parentEvent}

15
src/components/PostEditor/PostRelaySelector.tsx

@ -39,7 +39,6 @@ function capAutoSelectedRelays(selectableRelaysOrder: string[], selectedWithCach
export default function PostRelaySelector({ export default function PostRelaySelector({
parentEvent: _parentEvent, parentEvent: _parentEvent,
openFrom, openFrom,
setIsProtectedEvent,
setAdditionalRelayUrls, setAdditionalRelayUrls,
onRelayPublishCapChange, onRelayPublishCapChange,
content: postContent = '', content: postContent = '',
@ -48,7 +47,6 @@ export default function PostRelaySelector({
}: { }: {
parentEvent?: NostrEvent parentEvent?: NostrEvent
openFrom?: string[] openFrom?: string[]
setIsProtectedEvent: Dispatch<SetStateAction<boolean>>
setAdditionalRelayUrls: Dispatch<SetStateAction<string[]>> setAdditionalRelayUrls: Dispatch<SetStateAction<string[]>>
/** Notifies the post form when the relay cap prevents honoring every checked relay (so the form can disable publish and show a banner). */ /** Notifies the post form when the relay cap prevents honoring every checked relay (so the form can disable publish and show a banner). */
onRelayPublishCapChange?: (preview: TPrePublishRelayCapPreview) => void onRelayPublishCapChange?: (preview: TPrePublishRelayCapPreview) => void
@ -81,6 +79,8 @@ export default function PostRelaySelector({
const [description, setDescription] = useState('') const [description, setDescription] = useState('')
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [hasManualSelection, setHasManualSelection] = useState(false) const [hasManualSelection, setHasManualSelection] = useState(false)
/** Auto-picked relays from {@link relaySelectionService}; used to detect manual relay-picker changes. */
const autoSelectedRelayUrlsRef = useRef<string[]>([])
const [previousSelectableCount, setPreviousSelectableCount] = useState(0) const [previousSelectableCount, setPreviousSelectableCount] = useState(0)
// Generation counter: incremented every time the effect fires; async callback checks whether // Generation counter: incremented every time the effect fires; async callback checks whether
// it's still the latest invocation before committing state, preventing stale races. // it's still the latest invocation before committing state, preventing stale races.
@ -201,6 +201,7 @@ export default function PostRelaySelector({
const cacheRelays = result.selectableRelays.filter(url => isLocalNetworkUrl(url)) const cacheRelays = result.selectableRelays.filter(url => isLocalNetworkUrl(url))
const selectedWithCache = Array.from(new Set([...result.selectedRelays, ...cacheRelays])) const selectedWithCache = Array.from(new Set([...result.selectedRelays, ...cacheRelays]))
const capped = capAutoSelectedRelays(result.selectableRelays, selectedWithCache) const capped = capAutoSelectedRelays(result.selectableRelays, selectedWithCache)
autoSelectedRelayUrlsRef.current = capped
setSelectedRelayUrls(capped) setSelectedRelayUrls(capped)
setDescription(describeRelaySelection(capped)) setDescription(describeRelaySelection(capped))
if (selectableRelaysChanged && hasManualSelection) { if (selectableRelaysChanged && hasManualSelection) {
@ -246,16 +247,8 @@ export default function PostRelaySelector({
// Update parent component with selected relays // Update parent component with selected relays
useEffect(() => { useEffect(() => {
// An event is "protected" if we have selected relays that aren't the default user write relays
const defaultUserWriteRelays = [...(relayList?.httpWrite ?? []), ...(relayList?.write || [])]
const normW = (u: string) => normalizeRelayUrlByScheme(u) || u
const defaultNorm = new Set(defaultUserWriteRelays.map(normW))
const isProtectedEvent =
selectedRelayUrls.length > 0 &&
!selectedRelayUrls.every((url) => defaultNorm.has(normW(url)))
setIsProtectedEvent(isProtectedEvent)
setAdditionalRelayUrls(selectedRelayUrls) setAdditionalRelayUrls(selectedRelayUrls)
}, [selectedRelayUrls, relayList, setIsProtectedEvent, setAdditionalRelayUrls]) }, [selectedRelayUrls, setAdditionalRelayUrls])
const handleRelayCheckedChange = useCallback((checked: boolean, url: string) => { const handleRelayCheckedChange = useCallback((checked: boolean, url: string) => {
setHasManualSelection(true) setHasManualSelection(true)

2
src/lib/discussion-thread-composer.ts

@ -146,7 +146,7 @@ export function collectDiscussionThreadTags(params: {
const { processedContent, topicForTags, title, dynamicTopics, isReadingGroup, author, subject, isNsfw } = params const { processedContent, topicForTags, title, dynamicTopics, isReadingGroup, author, subject, isNsfw } = params
const images = extractImagesFromContent(processedContent) const images = extractImagesFromContent(processedContent)
const hashtags = extractHashtagsFromContent(processedContent) const hashtags = extractHashtagsFromContent(processedContent)
const tags: string[][] = [['title', title.trim()], ['-']] const tags: string[][] = [['title', title.trim()]]
if (topicForTags !== 'all' && topicForTags !== 'general' && topicForTags !== 'groups') { if (topicForTags !== 'all' && topicForTags !== 'general' && topicForTags !== 'groups') {
const selectedDynamicTopic = dynamicTopics?.allTopics.find((dt) => dt.id === topicForTags) const selectedDynamicTopic = dynamicTopics?.allTopics.find((dt) => dt.id === topicForTags)

19
src/lib/draft-event.ts

@ -226,7 +226,6 @@ export async function createShortTextNoteDraftEvent(
options: { options: {
parentEvent?: Event parentEvent?: Event
addClientTag?: boolean addClientTag?: boolean
protectedEvent?: boolean
isNsfw?: boolean isNsfw?: boolean
addExpirationTag?: boolean addExpirationTag?: boolean
expirationMonths?: number expirationMonths?: number
@ -271,10 +270,6 @@ export async function createShortTextNoteDraftEvent(
tags.push(buildNsfwTag()) tags.push(buildNsfwTag())
} }
if (options.protectedEvent) {
tags.push(buildProtectedTag())
}
if (options.addExpirationTag && options.expirationMonths) { if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths)) tags.push(buildExpirationTag(options.expirationMonths))
} }
@ -309,7 +304,6 @@ export async function createCommentDraftEvent(
mentions: string[], mentions: string[],
options: { options: {
addClientTag?: boolean addClientTag?: boolean
protectedEvent?: boolean
isNsfw?: boolean isNsfw?: boolean
addExpirationTag?: boolean addExpirationTag?: boolean
expirationMonths?: number expirationMonths?: number
@ -389,10 +383,6 @@ export async function createCommentDraftEvent(
tags.push(buildNsfwTag()) tags.push(buildNsfwTag())
} }
if (options.protectedEvent) {
tags.push(buildProtectedTag())
}
if (options.addExpirationTag && options.expirationMonths) { if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths)) tags.push(buildExpirationTag(options.expirationMonths))
} }
@ -1620,10 +1610,6 @@ function buildNsfwTag() {
return ['content-warning', 'NSFW'] return ['content-warning', 'NSFW']
} }
function buildProtectedTag() {
return ['-']
}
function buildExpirationTag(months: number): string[] { function buildExpirationTag(months: number): string[] {
const expirationTime = dayjs().add(months, 'month').unix() const expirationTime = dayjs().add(months, 'month').unix()
return ['expiration', expirationTime.toString()] return ['expiration', expirationTime.toString()]
@ -1847,7 +1833,6 @@ export async function createVoiceCommentDraftEvent(
mentions: string[], mentions: string[],
options: { options: {
addClientTag?: boolean addClientTag?: boolean
protectedEvent?: boolean
isNsfw?: boolean isNsfw?: boolean
addExpirationTag?: boolean addExpirationTag?: boolean
expirationMonths?: number expirationMonths?: number
@ -1926,10 +1911,6 @@ export async function createVoiceCommentDraftEvent(
tags.push(buildNsfwTag()) tags.push(buildNsfwTag())
} }
if (options.protectedEvent) {
tags.push(buildProtectedTag())
}
if (options.addExpirationTag && options.expirationMonths) { if (options.addExpirationTag && options.expirationMonths) {
tags.push(buildExpirationTag(options.expirationMonths)) tags.push(buildExpirationTag(options.expirationMonths))
} }

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

@ -18,6 +18,7 @@ import logger from '@/lib/logger'
import indexedDb from '@/services/indexed-db.service' import indexedDb from '@/services/indexed-db.service'
import { getHttpRelayListFromEvent, getRelayListFromEvent } from '@/lib/event-metadata' import { getHttpRelayListFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
import { stripLocalNetworkRelaysFromRelayList } from '@/lib/relay-list-sanitize' import { stripLocalNetworkRelaysFromRelayList } from '@/lib/relay-list-sanitize'
import { isProtectedEvent } from '@/lib/event'
import { dedupeNormalizeRelayUrlsOrdered } from '@/lib/relay-url-priority' import { dedupeNormalizeRelayUrlsOrdered } from '@/lib/relay-url-priority'
import nip66Service from '@/services/nip66.service' import nip66Service from '@/services/nip66.service'
@ -71,12 +72,14 @@ class RelaySelectionService {
return normalizeRelayUrlByScheme(url) || url.trim() return normalizeRelayUrlByScheme(url) || url.trim()
} }
/** Kind 10002 + 10243 write/both outboxes for the logged-in user. */ /** Kind 10432 + 10243 + 10002 write outboxes (already merged in {@link RelaySelectionContext.userWriteRelays}). */
private userWriteOutboxRelays(context: RelaySelectionContext): string[] { private userWriteOutboxRelays(context: RelaySelectionContext): string[] {
return dedupeNormalizeRelayUrlsOrdered([ return dedupeNormalizeRelayUrlsOrdered(context.userWriteRelays)
...(context.userHttpWriteRelays ?? []), }
...context.userWriteRelays
]) /** True when the discussion thread (kind 11 parent) uses the `-` protected tag. */
private discussionContextIsProtected(parentEvent: Event): boolean {
return isProtectedEvent(parentEvent)
} }
private filterLocalRelaysFromOthers(relays: string[], isOwnRelays: boolean = false): string[] { private filterLocalRelaysFromOthers(relays: string[], isOwnRelays: boolean = false): string[] {
@ -669,14 +672,16 @@ class RelaySelectionService {
/** /**
* Get relays for discussion replies (kind 11 or kind 1111) * Get relays for discussion replies (kind 11 or kind 1111)
* Includes: relay hints from kind 11, wss://thecitadel.nostr1.com, user's outboxes, and local relays * Includes: relay hints from kind 11, wss://thecitadel.nostr1.com, user's outboxes, and local relays.
* Protected threads (`-` tag on kind 11): hints + citadel + cache only general outboxes reject protected events.
*/ */
private async getDiscussionReplyRelays(context: RelaySelectionContext): Promise<string[]> { private async getDiscussionReplyRelays(context: RelaySelectionContext): Promise<string[]> {
const { parentEvent, userPubkey, blockedRelays } = context const { parentEvent, userPubkey, blockedRelays } = context
if (!parentEvent) return [] if (!parentEvent) return []
const relayUrls = new Set<string>() const relayUrls = new Set<string>()
const userOutboxes = this.userWriteOutboxRelays(context) const threadIsProtected = this.discussionContextIsProtected(parentEvent)
const userOutboxes = threadIsProtected ? [] : this.userWriteOutboxRelays(context)
// Step 1: Get relay hints from the kind 11 event // Step 1: Get relay hints from the kind 11 event
let discussionEventId: string | null = null let discussionEventId: string | null = null
@ -710,14 +715,14 @@ class RelaySelectionService {
relayUrls.add(thecitadelUrl) relayUrls.add(thecitadelUrl)
} }
// Step 3: Add user's outboxes (NIP-65 + HTTP index write relays) // Step 3: User outboxes (skip for protected threads — most public relays reject `-` events)
if (!threadIsProtected) {
if (userOutboxes.length > 0) { if (userOutboxes.length > 0) {
userOutboxes.forEach((url) => { userOutboxes.forEach((url) => {
const normalized = this.normRelay(url) const normalized = this.normRelay(url)
if (normalized) relayUrls.add(normalized) if (normalized) relayUrls.add(normalized)
}) })
} else if (userPubkey) { } else if (userPubkey) {
// Fetch user's relay list if not provided
try { try {
const relayList = await this.getCachedRelayList(userPubkey) const relayList = await this.getCachedRelayList(userPubkey)
if (relayList) { if (relayList) {
@ -731,6 +736,7 @@ class RelaySelectionService {
logger.warn('Failed to fetch user relay list for discussion reply', { error, userPubkey }) logger.warn('Failed to fetch user relay list for discussion reply', { error, userPubkey })
} }
} }
}
// Step 4: Add local relays (cache relays from kind 10432) // Step 4: Add local relays (cache relays from kind 10432)
if (userPubkey) { if (userPubkey) {

Loading…
Cancel
Save