From 78caabeafc8af564fe11501f5bd42da5c7155e2b Mon Sep 17 00:00:00 2001 From: codytseng Date: Thu, 13 Mar 2025 12:03:38 +0800 Subject: [PATCH] fix: ensure events are sent to mentioned users' read relays --- src/components/NoteStats/LikeButton.tsx | 6 +-- src/components/NoteStats/RepostButton.tsx | 4 +- .../PostEditor/NormalPostContent.tsx | 39 ++-------------- .../PostEditor/PicturePostContent.tsx | 6 +-- .../index.tsx | 0 .../utils.ts | 0 src/providers/NostrProvider/index.tsx | 45 +++++++++++++++---- 7 files changed, 45 insertions(+), 55 deletions(-) rename src/components/{TextareaWithMentions.tsx => TextareaWithMentions}/index.tsx (100%) rename src/components/{TextareaWithMentions.tsx => TextareaWithMentions}/utils.ts (100%) diff --git a/src/components/NoteStats/LikeButton.tsx b/src/components/NoteStats/LikeButton.tsx index e2fc0c40..99276746 100644 --- a/src/components/NoteStats/LikeButton.tsx +++ b/src/components/NoteStats/LikeButton.tsx @@ -2,7 +2,6 @@ import { createReactionDraftEvent } from '@/lib/draft-event' import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import { useNoteStats } from '@/providers/NoteStatsProvider' -import client from '@/services/client.service' import { Heart, Loader } from 'lucide-react' import { Event } from 'nostr-tools' import { useMemo, useState } from 'react' @@ -37,11 +36,8 @@ export default function LikeButton({ event }: { event: Event }) { if (stats?.likes?.has(pubkey)) return } - const targetRelayList = await client.fetchRelayList(event.pubkey) const reaction = createReactionDraftEvent(event) - const evt = await publish(reaction, { - additionalRelayUrls: targetRelayList.read.slice(0, 4) - }) + const evt = await publish(reaction) updateNoteStatsByEvents([evt]) } catch (error) { console.error('like failed', error) diff --git a/src/components/NoteStats/RepostButton.tsx b/src/components/NoteStats/RepostButton.tsx index eed3e369..2f9b6349 100644 --- a/src/components/NoteStats/RepostButton.tsx +++ b/src/components/NoteStats/RepostButton.tsx @@ -12,7 +12,6 @@ import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import { useNoteStats } from '@/providers/NoteStatsProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' -import client from '@/services/client.service' import { Loader, PencilLine, Repeat } from 'lucide-react' import { Event } from 'nostr-tools' import { useMemo, useState } from 'react' @@ -53,9 +52,8 @@ export default function RepostButton({ event }: { event: Event }) { if (stats?.reposts?.has(pubkey)) return } - const targetRelayList = await client.fetchRelayList(event.pubkey) const repost = createRepostDraftEvent(event) - const evt = await publish(repost, { additionalRelayUrls: targetRelayList.read.slice(0, 5) }) + const evt = await publish(repost) updateNoteStatsByEvents([evt]) } catch (error) { console.error('repost failed', error) diff --git a/src/components/PostEditor/NormalPostContent.tsx b/src/components/PostEditor/NormalPostContent.tsx index 290a55a9..6a4d0df9 100644 --- a/src/components/PostEditor/NormalPostContent.tsx +++ b/src/components/PostEditor/NormalPostContent.tsx @@ -1,20 +1,17 @@ import { Button } from '@/components/ui/button' import { useToast } from '@/hooks/use-toast' import { createCommentDraftEvent, createShortTextNoteDraftEvent } from '@/lib/draft-event' -import { getRootEventTag } from '@/lib/event.ts' -import { generateEventIdFromETag } from '@/lib/tag.ts' import { useNostr } from '@/providers/NostrProvider' -import client from '@/services/client.service' import postContentCache from '@/services/post-content-cache.service' import { ChevronDown, ImageUp, LoaderCircle } from 'lucide-react' import { Event, kinds } from 'nostr-tools' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import TextareaWithMentions from '../TextareaWithMentions.tsx' +import TextareaWithMentions from '../TextareaWithMentions' import Mentions from './Mentions' -import PostOptions from './PostOptions.tsx' +import PostOptions from './PostOptions' import Preview from './Preview' -import SendOnlyToSwitch from './SendOnlyToSwitch.tsx' +import SendOnlyToSwitch from './SendOnlyToSwitch' import Uploader from './Uploader' export default function NormalPostContent({ @@ -69,31 +66,6 @@ export default function NormalPostContent({ setPosting(true) try { - const additionalRelayUrls: string[] = [] - if (parentEvent && !specifiedRelayUrls) { - const rootEventTag = getRootEventTag(parentEvent) - if (rootEventTag) { - const [, , , , rootAuthor] = rootEventTag - if (rootAuthor) { - if (rootAuthor !== parentEvent.pubkey) { - const rootAuthorRelayList = await client.fetchRelayList(rootAuthor) - additionalRelayUrls.push(...rootAuthorRelayList.read.slice(0, 4)) - } - } else { - const rootEventId = generateEventIdFromETag(rootEventTag) - if (rootEventId) { - const rootEvent = await client.fetchEvent(rootEventId) - - if (rootEvent && rootEvent.pubkey !== parentEvent.pubkey) { - const rootAuthorRelayList = await client.fetchRelayList(rootEvent.pubkey) - additionalRelayUrls.push(...rootAuthorRelayList.read.slice(0, 4)) - } - } - } - } - const relayList = await client.fetchRelayList(parentEvent.pubkey) - additionalRelayUrls.push(...relayList.read.slice(0, 4)) - } const draftEvent = parentEvent && parentEvent.kind !== kinds.ShortTextNote ? await createCommentDraftEvent(content, parentEvent, pictureInfos, mentions, { @@ -105,10 +77,7 @@ export default function NormalPostContent({ addClientTag, protectedEvent: !!specifiedRelayUrls }) - await publish(draftEvent, { - additionalRelayUrls, - specifiedRelayUrls - }) + await publish(draftEvent, { specifiedRelayUrls }) setContent('') close() } catch (error) { diff --git a/src/components/PostEditor/PicturePostContent.tsx b/src/components/PostEditor/PicturePostContent.tsx index 24930c53..37529587 100644 --- a/src/components/PostEditor/PicturePostContent.tsx +++ b/src/components/PostEditor/PicturePostContent.tsx @@ -8,10 +8,10 @@ import { ChevronDown, Loader, LoaderCircle, Plus, X } from 'lucide-react' import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Image from '../Image' -import TextareaWithMentions from '../TextareaWithMentions.tsx' +import TextareaWithMentions from '../TextareaWithMentions' import Mentions from './Mentions' -import PostOptions from './PostOptions.tsx' -import SendOnlyToSwitch from './SendOnlyToSwitch.tsx' +import PostOptions from './PostOptions' +import SendOnlyToSwitch from './SendOnlyToSwitch' import Uploader from './Uploader' export default function PicturePostContent({ close }: { close: () => void }) { diff --git a/src/components/TextareaWithMentions.tsx/index.tsx b/src/components/TextareaWithMentions/index.tsx similarity index 100% rename from src/components/TextareaWithMentions.tsx/index.tsx rename to src/components/TextareaWithMentions/index.tsx diff --git a/src/components/TextareaWithMentions.tsx/utils.ts b/src/components/TextareaWithMentions/utils.ts similarity index 100% rename from src/components/TextareaWithMentions.tsx/utils.ts rename to src/components/TextareaWithMentions/utils.ts diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 18804ef5..1f51ba07 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -1,11 +1,12 @@ import LoginDialog from '@/components/LoginDialog' -import { BIG_RELAY_URLS } from '@/constants' +import { BIG_RELAY_URLS, COMMENT_EVENT_KIND, PICTURE_EVENT_KIND } from '@/constants' import { useToast } from '@/hooks' import { getLatestEvent, getProfileFromProfileEvent, getRelayListFromRelayListEvent } from '@/lib/event' +import { isValidPubkey } from '@/lib/pubkey' import client from '@/services/client.service' import indexedDb from '@/services/indexed-db.service' import storage from '@/services/local-storage.service' @@ -40,10 +41,7 @@ type TNostrContext = { /** * Default publish the event to current relays, user's write relays and additional relays */ - publish: ( - draftEvent: TDraftEvent, - options?: { additionalRelayUrls?: string[]; specifiedRelayUrls?: string[] } - ) => Promise + publish: (draftEvent: TDraftEvent, options?: { specifiedRelayUrls?: string[] }) => Promise signHttpAuth: (url: string, method: string) => Promise signEvent: (draftEvent: TDraftEvent) => Promise nip04Encrypt: (pubkey: string, plainText: string) => Promise @@ -372,11 +370,40 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { const publish = async ( draftEvent: TDraftEvent, - { - additionalRelayUrls, - specifiedRelayUrls - }: { additionalRelayUrls?: string[]; specifiedRelayUrls?: string[] } = {} + { specifiedRelayUrls }: { specifiedRelayUrls?: string[] } = {} ) => { + const additionalRelayUrls: string[] = [] + if ( + !specifiedRelayUrls?.length && + [ + kinds.ShortTextNote, + kinds.Reaction, + kinds.Repost, + COMMENT_EVENT_KIND, + PICTURE_EVENT_KIND + ].includes(draftEvent.kind) + ) { + const mentions: string[] = [] + draftEvent.tags.forEach(([tagName, tagValue]) => { + if ( + ['p', 'P'].includes(tagName) && + !!tagValue && + isValidPubkey(tagValue) && + !mentions.includes(tagValue) + ) { + mentions.push(tagValue) + } + }) + if (mentions.length > 0) { + const relayLists = await Promise.all( + mentions.map((pubkey) => client.fetchRelayList(pubkey)) + ) + relayLists.forEach((relayList) => { + additionalRelayUrls.push(...relayList.read.slice(0, 4)) + }) + } + } + const event = await signEvent(draftEvent) const relays = specifiedRelayUrls?.length ? specifiedRelayUrls