Browse Source

bug-fix

imwald
Silberengel 3 weeks ago
parent
commit
14871246e8
  1. 16
      src/components/NoteList/index.tsx
  2. 8
      src/components/ReplyNoteList/index.tsx
  3. 91
      src/lib/superchat.test.ts
  4. 53
      src/lib/superchat.ts
  5. 3
      src/pages/primary/SpellsPage/index.tsx
  6. 3
      src/pages/primary/SpellsPage/useSpellsPageFeed.ts

16
src/components/NoteList/index.tsx

@ -798,7 +798,9 @@ const NoteList = forwardRef(
*/ */
alexandriaEmptyUrl = null, alexandriaEmptyUrl = null,
/** Notifications feed: show attest-superchat bar on incoming payment cards. */ /** Notifications feed: show attest-superchat bar on incoming payment cards. */
showPaymentAttestationAction = false showPaymentAttestationAction = false,
/** Notifications feed: show unattested kind 9734 / 9735 / 9740 / 9736 / 1814 addressed to this pubkey. */
incomingPaymentRecipientPubkey = null
}: { }: {
subRequests: TFeedSubRequest[] subRequests: TFeedSubRequest[]
showKinds: number[] showKinds: number[]
@ -861,6 +863,7 @@ const NoteList = forwardRef(
/** Optional Alexandria `/events` URL when this feed’s timeline is empty (search / tag browse). */ /** Optional Alexandria `/events` URL when this feed’s timeline is empty (search / tag browse). */
alexandriaEmptyUrl?: string | null alexandriaEmptyUrl?: string | null
showPaymentAttestationAction?: boolean showPaymentAttestationAction?: boolean
incomingPaymentRecipientPubkey?: string | null
}, },
ref ref
) => { ) => {
@ -1363,8 +1366,14 @@ const NoteList = forwardRef(
// Filter out expired events // Filter out expired events
if (shouldFilterEvent(evt)) return true if (shouldFilterEvent(evt)) return true
// Attested superchats only (9741), same as threads / profile walls. // Attested superchats only (9741), except incoming payments in notifications.
if (!shouldIncludePaymentInFeed(evt, feedAttestedSuperchatIds)) { if (
!shouldIncludePaymentInFeed(
evt,
feedAttestedSuperchatIds,
incomingPaymentRecipientPubkey
)
) {
return true return true
} }
@ -1395,6 +1404,7 @@ const NoteList = forwardRef(
pinnedEventIds, pinnedEventIds,
isEventDeleted, isEventDeleted,
feedAttestedSuperchatIds, feedAttestedSuperchatIds,
incomingPaymentRecipientPubkey,
extraShouldHideEvent, extraShouldHideEvent,
homeFeedActiveSeenOnAllowlist, homeFeedActiveSeenOnAllowlist,
homeFeedListMode homeFeedListMode

8
src/components/ReplyNoteList/index.tsx

@ -1,15 +1,9 @@
import { ExtendedKind } from '@/constants' import { ExtendedKind } from '@/constants'
import { isDiscussionDownvoteEmoji, isDiscussionUpvoteEmoji } from '@/lib/discussion-votes' import { isDiscussionDownvoteEmoji, isDiscussionUpvoteEmoji } from '@/lib/discussion-votes'
import {
canonicalizeRssArticleUrl,
getArticleUrlFromCommentITags
} from '@/lib/rss-article'
import { import {
getParentETag, getParentETag,
getReplaceableCoordinateFromEvent,
isMentioningMutedUsers, isMentioningMutedUsers,
isNip18RepostKind, isNip18RepostKind
isReplaceableEvent
} from '@/lib/event' } from '@/lib/event'
import logger from '@/lib/logger' import logger from '@/lib/logger'
import { import {

91
src/lib/superchat.test.ts

@ -9,6 +9,7 @@ import {
getSuperchatPaytoType, getSuperchatPaytoType,
getSuperchatReferenceFetchId, getSuperchatReferenceFetchId,
canUserAttestSuperchatPayment, canUserAttestSuperchatPayment,
isIncomingNotificationsPaymentEvent,
isProfileWallPaymentNotification, isProfileWallPaymentNotification,
isProfileWallZapReceipt, isProfileWallZapReceipt,
isNestedThreadReplyParentKind, isNestedThreadReplyParentKind,
@ -289,6 +290,96 @@ describe('shouldIncludePaymentInFeed', () => {
expect(shouldIncludePaymentInFeed(zap, new Set())).toBe(false) expect(shouldIncludePaymentInFeed(zap, new Set())).toBe(false)
expect(shouldIncludePaymentInFeed(note, attested)).toBe(true) expect(shouldIncludePaymentInFeed(note, attested)).toBe(true)
}) })
it('includes unattested incoming payments for the notifications recipient only', () => {
const zap = fakeEvent({
id: ZAP_ID,
kind: kinds.Zap,
tags: [
['P', SENDER],
['p', RECIPIENT],
['bolt11', 'lnbc210n1p0fake'],
[
'description',
JSON.stringify({
pubkey: SENDER,
content: 'Zap!',
tags: [['p', RECIPIENT], ['amount', '21000']]
})
]
]
})
const payment = fakeEvent({
id: PAYMENT_ID,
kind: ExtendedKind.PAYMENT_NOTIFICATION,
tags: [['p', RECIPIENT], ['amount', '100000']]
})
const moneroDisclosure = fakeEvent({
id: 'a'.repeat(64),
kind: ExtendedKind.MONERO_TIP_DISCLOSURE,
tags: [['p', RECIPIENT], ['amount', '0.01']]
})
const moneroReceipt = fakeEvent({
id: 'b'.repeat(64),
kind: ExtendedKind.MONERO_TIP_RECEIPT,
tags: [['p', SENDER], ['p', RECIPIENT]],
content: JSON.stringify({ txid: 'abc', message: 'tip' })
})
const zapRequest = fakeEvent({
id: 'c'.repeat(64),
kind: ExtendedKind.ZAP_REQUEST,
pubkey: SENDER,
tags: [['p', RECIPIENT], ['amount', '21000']]
})
const empty = new Set<string>()
expect(shouldIncludePaymentInFeed(zap, empty, RECIPIENT)).toBe(true)
expect(shouldIncludePaymentInFeed(payment, empty, RECIPIENT)).toBe(true)
expect(shouldIncludePaymentInFeed(moneroDisclosure, empty, RECIPIENT)).toBe(true)
expect(shouldIncludePaymentInFeed(moneroReceipt, empty, RECIPIENT)).toBe(true)
expect(shouldIncludePaymentInFeed(zapRequest, empty, RECIPIENT)).toBe(true)
expect(shouldIncludePaymentInFeed(zap, empty, SENDER)).toBe(false)
expect(shouldIncludePaymentInFeed(zap, empty)).toBe(false)
})
})
describe('isIncomingNotificationsPaymentEvent', () => {
it('matches all payment kinds addressed to the recipient', () => {
expect(
isIncomingNotificationsPaymentEvent(
fakeEvent({
kind: ExtendedKind.ZAP_REQUEST,
tags: [['p', RECIPIENT], ['amount', '21000']]
}),
RECIPIENT
)
).toBe(true)
expect(
isIncomingNotificationsPaymentEvent(
fakeEvent({
kind: ExtendedKind.MONERO_TIP_DISCLOSURE,
tags: [['p', RECIPIENT], ['amount', '0.01']]
}),
RECIPIENT
)
).toBe(true)
expect(
isIncomingNotificationsPaymentEvent(
fakeEvent({
kind: ExtendedKind.MONERO_TIP_RECEIPT,
tags: [['p', SENDER], ['p', RECIPIENT]],
content: '{}'
}),
RECIPIENT
)
).toBe(true)
expect(
isIncomingNotificationsPaymentEvent(
fakeEvent({ kind: kinds.ShortTextNote, tags: [['p', RECIPIENT]] }),
RECIPIENT
)
).toBe(false)
})
}) })
describe('getPaymentNotificationInfo', () => { describe('getPaymentNotificationInfo', () => {

53
src/lib/superchat.ts

@ -205,18 +205,47 @@ export function canUserAttestSuperchatPayment(
if (!isAttestableSuperchatPayment(event)) return false if (!isAttestableSuperchatPayment(event)) return false
const resolved = attestationRecipientPubkey ?? getSuperchatPaymentRecipientPubkey(event) const resolved = attestationRecipientPubkey ?? getSuperchatPaymentRecipientPubkey(event)
if (resolved && hexPubkeysEqual(resolved, userPubkey)) return true if (resolved && hexPubkeysEqual(resolved, userPubkey)) return true
const pTag = firstTagValue(event.tags, ['p']) return event.tags.some((t) => t[0] === 'p' && t[1] && hexPubkeysEqual(t[1], userPubkey))
return Boolean(pTag && hexPubkeysEqual(pTag, userPubkey))
} }
/** Incoming payment notification or zap receipt addressed to `userPubkey`. */ /** Payment / tip kinds that may appear in the notifications feed (9734–9736, 1814, 9740). */
export function isIncomingPaymentNotificationOrZapReceipt( export function isIncomingNotificationsPaymentKind(kind: number): boolean {
return (
kind === ExtendedKind.ZAP_REQUEST ||
kind === kinds.Zap ||
kind === ExtendedKind.ZAP_RECEIPT ||
kind === ExtendedKind.PAYMENT_NOTIFICATION ||
isMoneroTipKind(kind)
)
}
/**
* Incoming payment or tip addressed to `userPubkey` shown unattested in notifications only.
* Covers kind 9734 (zap request), 9735, 9740, 9736, and 1814.
*/
export function isIncomingNotificationsPaymentEvent(
event: Event, event: Event,
userPubkey: string, userPubkey: string,
attestationRecipientPubkey?: string | null attestationRecipientPubkey?: string | null
): boolean { ): boolean {
if (!isIncomingNotificationsPaymentKind(event.kind)) return false
if (event.kind === ExtendedKind.ZAP_REQUEST) {
return event.tags.some((t) => t[0] === 'p' && t[1] && hexPubkeysEqual(t[1], userPubkey))
}
if (isAttestableSuperchatPayment(event)) {
return canUserAttestSuperchatPayment(event, userPubkey, attestationRecipientPubkey) return canUserAttestSuperchatPayment(event, userPubkey, attestationRecipientPubkey)
} }
return false
}
/** @deprecated Use {@link isIncomingNotificationsPaymentEvent}. */
export function isIncomingPaymentNotificationOrZapReceipt(
event: Event,
userPubkey: string,
attestationRecipientPubkey?: string | null
): boolean {
return isIncomingNotificationsPaymentEvent(event, userPubkey, attestationRecipientPubkey)
}
/** Target `k` tag value for a kind 9741 attestation pointing at this event. */ /** Target `k` tag value for a kind 9741 attestation pointing at this event. */
export function getSuperchatAttestationTargetKindValue(event: Event): string | null { export function getSuperchatAttestationTargetKindValue(event: Event): string | null {
@ -324,13 +353,25 @@ export function buildGlobalAttestedSuperchatIdSet(attestations: Event[]): Set<st
/** /**
* Feeds: kind 9735 / 9740 / 9736 / 1814 only when attested (9741). * Feeds: kind 9735 / 9740 / 9736 / 1814 only when attested (9741).
* Same attestation rule as threads and profile walls. * Same attestation rule as threads and profile walls.
*
* When `incomingPaymentRecipientPubkey` is set (notifications feed), unattested kind
* 9734 / 9735 / 9740 / 9736 / 1814 addressed to that pubkey are included so the recipient
* can publish kind 9741 from the card (9734 is shown but not attestable).
*/ */
export function shouldIncludePaymentInFeed( export function shouldIncludePaymentInFeed(
event: Event, event: Event,
attestedIds: ReadonlySet<string> attestedIds: ReadonlySet<string>,
incomingPaymentRecipientPubkey?: string | null
): boolean { ): boolean {
if (!isSuperchatKind(event.kind)) return true if (!isSuperchatKind(event.kind)) return true
return isAttestedSuperchat(event, attestedIds) if (isAttestedSuperchat(event, attestedIds)) return true
if (
incomingPaymentRecipientPubkey &&
isIncomingNotificationsPaymentEvent(event, incomingPaymentRecipientPubkey)
) {
return true
}
return false
} }
export function replyFeedSuperchatsFirst(sortedNonSuperchatReplies: Event[], superchats: Event[]) { export function replyFeedSuperchatsFirst(sortedNonSuperchatReplies: Event[], superchats: Event[]) {

3
src/pages/primary/SpellsPage/index.tsx

@ -1102,6 +1102,9 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
: undefined : undefined
} }
showPaymentAttestationAction={selectedFauxSpell === 'notifications'} showPaymentAttestationAction={selectedFauxSpell === 'notifications'}
incomingPaymentRecipientPubkey={
selectedFauxSpell === 'notifications' ? notificationsFeedPubkey : null
}
/> />
</div> </div>
</> </>

3
src/pages/primary/SpellsPage/useSpellsPageFeed.ts

@ -17,6 +17,7 @@ import {
parseThreadWatchListRefs, parseThreadWatchListRefs,
threadWatchMatchesRefs threadWatchMatchesRefs
} from '@/lib/notification-thread-watch' } from '@/lib/notification-thread-watch'
import { isIncomingNotificationsPaymentEvent } from '@/lib/superchat'
import { import {
decodeFollowSetSpellId, decodeFollowSetSpellId,
getFollowSetDTag, getFollowSetDTag,
@ -600,6 +601,8 @@ export function useSpellsPageFeed(a: UseSpellsPageFeedArgs) {
return true return true
} }
if (isIncomingNotificationsPaymentEvent(evt, pk)) return false
if (isUserInEventMentions(evt, pk)) return false if (isUserInEventMentions(evt, pk)) return false
if ( if (

Loading…
Cancel
Save