diff --git a/src/App.tsx b/src/App.tsx
index a265c9a..2c8de70 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -38,16 +38,16 @@ export default function App(): JSX.Element {
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/PageManager.tsx b/src/PageManager.tsx
index 966cd13..7c69c91 100644
--- a/src/PageManager.tsx
+++ b/src/PageManager.tsx
@@ -3,6 +3,7 @@ import { cn } from '@/lib/utils'
import NoteListPage from '@/pages/primary/NoteListPage'
import HomePage from '@/pages/secondary/HomePage'
import { CurrentRelaysProvider } from '@/providers/CurrentRelaysProvider'
+import { NotificationProvider } from '@/providers/NotificationProvider'
import { TPageRef } from '@/types'
import {
cloneElement,
@@ -25,7 +26,6 @@ import ProfilePage from './pages/primary/ProfilePage'
import RelayPage from './pages/primary/RelayPage'
import SearchPage from './pages/primary/SearchPage'
import DiscussionsPage from './pages/primary/DiscussionsPage'
-import { NotificationProvider } from './providers/NotificationProvider'
import { useScreenSize } from './providers/ScreenSizeProvider'
import { routes } from './routes'
import modalManager from './services/modal-manager.service'
diff --git a/src/components/NotificationList/NotificationItem/index.tsx b/src/components/NotificationList/NotificationItem/index.tsx
index 0bd8051..2a13484 100644
--- a/src/components/NotificationList/NotificationItem/index.tsx
+++ b/src/components/NotificationList/NotificationItem/index.tsx
@@ -26,13 +26,27 @@ export function NotificationItem({
const { hideContentMentioningMutedUsers } = useContentPolicy()
const { hideUntrustedNotifications, isUserTrusted } = useUserTrust()
const canShow = useMemo(() => {
- return notificationFilter(notification, {
+ const result = notificationFilter(notification, {
pubkey,
mutePubkeySet,
hideContentMentioningMutedUsers,
hideUntrustedNotifications,
isUserTrusted
})
+
+ if (notification.kind === 11) {
+ console.log('🔍 Discussion notification filter result:', {
+ id: notification.id,
+ kind: notification.kind,
+ canShow: result,
+ pubkey: notification.pubkey,
+ isMuted: mutePubkeySet.has(notification.pubkey),
+ hideUntrusted: hideUntrustedNotifications,
+ isTrusted: isUserTrusted(notification.pubkey)
+ })
+ }
+
+ return result
}, [
notification,
mutePubkeySet,
diff --git a/src/components/NotificationList/index.tsx b/src/components/NotificationList/index.tsx
index c169cfe..b43235c 100644
--- a/src/components/NotificationList/index.tsx
+++ b/src/components/NotificationList/index.tsx
@@ -56,7 +56,8 @@ const NotificationList = forwardRef((_, ref) => {
ExtendedKind.COMMENT,
ExtendedKind.VOICE_COMMENT,
ExtendedKind.POLL,
- ExtendedKind.PUBLIC_MESSAGE
+ ExtendedKind.PUBLIC_MESSAGE,
+ 11 // Discussion threads
]
case 'reactions':
return [kinds.Reaction, kinds.Repost, ExtendedKind.POLL_RESPONSE]
@@ -72,7 +73,8 @@ const NotificationList = forwardRef((_, ref) => {
ExtendedKind.POLL_RESPONSE,
ExtendedKind.VOICE_COMMENT,
ExtendedKind.POLL,
- ExtendedKind.PUBLIC_MESSAGE
+ ExtendedKind.PUBLIC_MESSAGE,
+ 11 // Discussion threads
]
}
}, [notificationType])
@@ -121,21 +123,48 @@ const NotificationList = forwardRef((_, ref) => {
setLastReadTime(getNotificationsSeenAt())
const relayList = await client.fetchRelayList(pubkey)
- const { closer, timelineKey } = await client.subscribeTimeline(
- [
- {
- urls: relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS,
- filter: {
- '#p': [pubkey],
- kinds: filterKinds,
- limit: LIMIT
- }
+ // Create separate subscriptions for different notification types
+ const subscriptions = []
+
+ // Subscription for mentions (events where user is in p-tags)
+ const mentionKinds = filterKinds.filter(kind => kind !== 11)
+ if (mentionKinds.length > 0) {
+ subscriptions.push({
+ urls: relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS,
+ filter: {
+ '#p': [pubkey],
+ kinds: mentionKinds,
+ limit: LIMIT
+ }
+ })
+ }
+
+ // Separate subscription for discussion notifications (kind 11) - no p-tag requirement
+ if (filterKinds.includes(11)) {
+ subscriptions.push({
+ urls: relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS,
+ filter: {
+ kinds: [11],
+ limit: LIMIT
}
- ],
+ })
+ }
+
+ const { closer, timelineKey } = await client.subscribeTimeline(
+ subscriptions,
{
onEvents: (events, eosed) => {
if (events.length > 0) {
- setNotifications(events.filter((event) => event.pubkey !== pubkey))
+ console.log('📋 NotificationList received events:', events.map(e => ({
+ id: e.id,
+ kind: e.kind,
+ pubkey: e.pubkey,
+ content: e.content.substring(0, 30) + '...'
+ })))
+
+ const filteredEvents = events.filter((event) => event.pubkey !== pubkey)
+ console.log('📋 After filtering own events:', filteredEvents.length, 'events')
+ setNotifications(filteredEvents)
}
if (eosed) {
setLoading(false)
@@ -144,6 +173,12 @@ const NotificationList = forwardRef((_, ref) => {
}
},
onNew: (event) => {
+ console.log('📋 NotificationList onNew event:', {
+ id: event.id,
+ kind: event.kind,
+ pubkey: event.pubkey,
+ content: event.content.substring(0, 30) + '...'
+ })
handleNewEvent(event)
}
}
diff --git a/src/providers/InterestListProvider.tsx b/src/providers/InterestListProvider.tsx
index 9d406c4..3341686 100644
--- a/src/providers/InterestListProvider.tsx
+++ b/src/providers/InterestListProvider.tsx
@@ -67,30 +67,42 @@ export function InterestListProvider({ children }: { children: React.ReactNode }
}
const subscribe = async (topic: string) => {
+ console.log('[InterestListProvider] subscribe called:', { topic, accountPubkey, changing })
if (!accountPubkey || changing) return
const normalizedTopic = normalizeTopic(topic)
if (subscribedTopics.has(normalizedTopic)) {
+ console.log('[InterestListProvider] Already subscribed to topic')
return
}
setChanging(true)
try {
+ console.log('[InterestListProvider] Fetching existing interest list event')
const interestListEvent = await client.fetchInterestListEvent(accountPubkey)
+ console.log('[InterestListProvider] Existing interest list event:', interestListEvent)
+
const currentTopics = interestListEvent
? interestListEvent.tags
.filter(tag => tag[0] === 't' && tag[1])
.map(tag => normalizeTopic(tag[1]))
: []
+ console.log('[InterestListProvider] Current topics:', currentTopics)
+
if (currentTopics.includes(normalizedTopic)) {
- // Already subscribed
+ console.log('[InterestListProvider] Already subscribed to topic (from event)')
return
}
const newTopics = [...currentTopics, normalizedTopic]
+ console.log('[InterestListProvider] Creating new interest list with topics:', newTopics)
+
const newInterestListEvent = await publishNewInterestListEvent(newTopics)
+ console.log('[InterestListProvider] Published new interest list event:', newInterestListEvent)
+
await updateInterestListEvent(newInterestListEvent)
+ console.log('[InterestListProvider] Updated interest list event in state')
toast.success(t('Subscribed to topic'))
} catch (error) {
diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx
index 0fdad10..275675d 100644
--- a/src/providers/NostrProvider/index.tsx
+++ b/src/providers/NostrProvider/index.tsx
@@ -31,7 +31,7 @@ import {
} from '@/types'
import { hexToBytes } from '@noble/hashes/utils'
import dayjs from 'dayjs'
-import { Event, kinds, VerifiedEvent } from 'nostr-tools'
+import { Event, kinds, VerifiedEvent, validateEvent } from 'nostr-tools'
import * as nip19 from 'nostr-tools/nip19'
import * as nip49 from 'nostr-tools/nip49'
import { createContext, useContext, useEffect, useState } from 'react'
@@ -237,7 +237,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
}
setRelayList(relayList)
- const events = await client.fetchEvents(relayList.write.concat(BIG_RELAY_URLS).slice(0, 4), [
+ const fetchRelays = relayList.write.concat(BIG_RELAY_URLS).slice(0, 4)
+ const events = await client.fetchEvents(fetchRelays, [
{
kinds: [
kinds.Metadata,
@@ -607,6 +608,14 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
if (!event) {
throw new Error('sign event failed')
}
+
+ // Validate the event before publishing
+ const isValid = validateEvent(event)
+ if (!isValid) {
+ console.error('Event validation failed:', event)
+ throw new Error('Event validation failed - invalid signature or format. Please try logging in again.')
+ }
+
return event as VerifiedEvent
}
@@ -617,6 +626,11 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
if (!account || !signer || account.signerType === 'npub') {
throw new Error('You need to login first')
}
+
+ // Validate account state before publishing
+ if (!account.pubkey || account.pubkey.length !== 64) {
+ throw new Error('Invalid account state - pubkey is missing or invalid')
+ }
const draft = JSON.parse(JSON.stringify(draftEvent)) as TDraftEvent
let event: VerifiedEvent
@@ -645,21 +659,25 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
try {
const publishResult = await client.publishEvent(relays, event)
- console.log('Publish result:', publishResult)
-
// Store relay status for display
if (publishResult.relayStatuses.length > 0) {
- // We'll pass this to the UI components that need it
(event as any).relayStatuses = publishResult.relayStatuses
- console.log('Attached relay statuses to event:', (event as any).relayStatuses)
}
return event
} catch (error) {
- // Even if publishing fails, try to extract relay statuses from the error
+ // Check for authentication-related errors
if (error instanceof AggregateError && (error as any).relayStatuses) {
(event as any).relayStatuses = (error as any).relayStatuses
- console.log('Attached relay statuses from error:', (event as any).relayStatuses)
+
+ // Check if any relay returned an "invalid key" error
+ const invalidKeyErrors = (error as any).relayStatuses.filter(
+ (status: any) => status.error && status.error.includes('invalid key')
+ )
+
+ if (invalidKeyErrors.length > 0) {
+ throw new Error('Authentication failed - invalid key. Please try logging out and logging in again.')
+ }
}
// Re-throw the error so the UI can handle it appropriately
diff --git a/src/providers/NotificationProvider.tsx b/src/providers/NotificationProvider.tsx
index a4b8039..96bbea6 100644
--- a/src/providers/NotificationProvider.tsx
+++ b/src/providers/NotificationProvider.tsx
@@ -11,7 +11,7 @@ import { useContentPolicy } from './ContentPolicyProvider'
import { useMuteList } from './MuteListProvider'
import { useNostr } from './NostrProvider'
import { useUserTrust } from './UserTrustProvider'
-import { useInterestList } from './InterestListProvider'
+// import { useInterestList } from './InterestListProvider' // No longer needed
type TNotificationContext = {
hasNewNotification: boolean
@@ -37,7 +37,7 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
const { hideUntrustedNotifications, isUserTrusted } = useUserTrust()
const { mutePubkeySet } = useMuteList()
const { hideContentMentioningMutedUsers } = useContentPolicy()
- const { getSubscribedTopics } = useInterestList()
+ // const { getSubscribedTopics } = useInterestList() // No longer needed since we subscribe to all discussions
const [newNotifications, setNewNotifications] = useState([])
const [readNotificationIdSet, setReadNotificationIdSet] = useState>(new Set())
const filteredNewNotifications = useMemo(() => {
@@ -109,45 +109,50 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
const relayList = await client.fetchRelayList(pubkey)
const notificationRelays = relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS
- // Subscribe to subscribed topics (kind 11 discussions)
- const subscribedTopics = getSubscribedTopics()
- if (subscribedTopics.length > 0) {
- let topicEosed = false
- const topicSubCloser = client.subscribe(
- notificationRelays,
- [
- {
- kinds: [11], // Discussion threads
- '#t': subscribedTopics,
- limit: 10
- }
- ],
+ // Subscribe to discussion notifications (kind 11)
+ // Subscribe to all discussions, not just subscribed topics
+ let discussionEosed = false
+ const discussionSubCloser = client.subscribe(
+ notificationRelays,
+ [
{
- oneose: (e) => {
- if (e) {
- topicEosed = e
- }
- },
- onevent: (evt) => {
- // Don't notify about our own threads
- if (evt.pubkey !== pubkey) {
- setNewNotifications((prev) => {
- if (!topicEosed) {
- return [evt, ...prev]
- }
- if (prev.length && compareEvents(prev[0], evt) >= 0) {
- return prev
- }
-
- client.emitNewEvent(evt)
+ kinds: [11], // Discussion threads
+ limit: 20
+ }
+ ],
+ {
+ oneose: (e) => {
+ if (e) {
+ discussionEosed = e
+ }
+ },
+ onevent: (evt) => {
+ // Don't notify about our own threads
+ if (evt.pubkey !== pubkey) {
+ console.log('📢 Discussion notification received:', {
+ id: evt.id,
+ pubkey: evt.pubkey,
+ kind: evt.kind,
+ content: evt.content.substring(0, 50) + '...',
+ topics: evt.tags.filter(tag => tag[0] === 't').map(tag => tag[1])
+ })
+
+ setNewNotifications((prev) => {
+ if (!discussionEosed) {
return [evt, ...prev]
- })
- }
+ }
+ if (prev.length && compareEvents(prev[0], evt) >= 0) {
+ return prev
+ }
+
+ client.emitNewEvent(evt)
+ return [evt, ...prev]
+ })
}
}
- )
- topicSubCloserRef.current = topicSubCloser
- }
+ }
+ )
+ topicSubCloserRef.current = discussionSubCloser
// Regular notifications subscription
const subCloser = client.subscribe(
@@ -180,7 +185,6 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
},
onevent: (evt) => {
if (evt.pubkey !== pubkey) {
-
setNewNotifications((prev) => {
if (!eosed) {
return [evt, ...prev]
@@ -243,7 +247,7 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
topicSubCloserRef.current = null
}
}
- }, [pubkey, getSubscribedTopics])
+ }, [pubkey])
useEffect(() => {
const newNotificationCount = filteredNewNotifications.length
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index cb720b3..b2a47ad 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -389,7 +389,11 @@ class ClientService extends EventTarget {
await this.throttleRequest(url)
const relay = await this.pool.ensureRelay(url)
- await relay.auth((authEvt: EventTemplate) => that.signer!.signEvent(authEvt))
+ await relay.auth((authEvt: EventTemplate) => {
+ // Ensure the auth event has the correct pubkey
+ const authEventWithPubkey = { ...authEvt, pubkey: that.pubkey }
+ return that.signer!.signEvent(authEventWithPubkey)
+ })
await relay.publish(event)
this.trackEventSeenOn(event.id, relay)
this.recordSuccess(url)
diff --git a/src/services/indexed-db.service.ts b/src/services/indexed-db.service.ts
index adb961f..72c6a3e 100644
--- a/src/services/indexed-db.service.ts
+++ b/src/services/indexed-db.service.ts
@@ -43,7 +43,7 @@ class IndexedDbService {
init(): Promise {
if (!this.initPromise) {
this.initPromise = new Promise((resolve, reject) => {
- const request = window.indexedDB.open('jumble', 8)
+ const request = window.indexedDB.open('jumble', 9)
request.onerror = (event) => {
reject(event)