diff --git a/src/components/nostr/ChatComponent.vue b/src/components/nostr/ChatComponent.vue index 35451b8..3697882 100644 --- a/src/components/nostr/ChatComponent.vue +++ b/src/components/nostr/ChatComponent.vue @@ -541,15 +541,16 @@ const sendMessage = async () => { console.log('🔍 ChatComponent: Attempting to send message...') console.log('🔍 ChatComponent: Selected peer:', selectedPeer.value) console.log('🔍 ChatComponent: Message content:', messageInput.value) - + // Check authentication status const keyStatus = nostrChat.getNostrKeyStatus() console.log('🔍 ChatComponent: Nostr key status:', keyStatus) - + // Check if user is logged in - console.log('🔍 ChatComponent: Is logged in:', nostrChat.isLoggedIn.value) + console.log('🔍 ChatComponent: Is authenticated:', nostrChat.isLoggedIn.value) console.log('🔍 ChatComponent: Current user:', nostrChat.currentUser.value) - + console.log('🔍 ChatComponent: Has Nostr keys:', nostrChat.hasNostrKeys.value) + await sendNostrMessage(selectedPeer.value.pubkey, messageInput.value) messageInput.value = '' diff --git a/src/composables/useNostrChat.ts b/src/composables/useNostrChat.ts index d539e02..6b9c76e 100644 --- a/src/composables/useNostrChat.ts +++ b/src/composables/useNostrChat.ts @@ -87,68 +87,47 @@ export function useNostrChat() { // Computed - use relay hub's connection status and auth system const isConnected = computed(() => relayHub.isConnected.value) - const isLoggedIn = computed(() => auth.isAuthenticated.value) // Get current user from auth system const currentUser = computed(() => { const user = auth.currentUser.value - if (!user || !user.pubkey) { - console.log('🔍 useNostrChat: No user or no pubkey available:', user) + if (!user) { + console.log('🔍 useNostrChat: No user available from auth system') return null } - console.log('🔍 useNostrChat: User data available:', { - id: user.id, - username: user.username, + console.log('🔍 useNostrChat: Full user data from LNBits:', user) + console.log('🔍 useNostrChat: User pubkey:', user.pubkey) + console.log('🔍 useNostrChat: User prvkey:', user.prvkey) + console.log('🔍 useNostrChat: User prvkey type:', typeof user.prvkey) + console.log('🔍 useNostrChat: User prvkey length:', user.prvkey?.length) + + // Check if the user has a pubkey field + if (!user.pubkey) { + console.log('🔍 useNostrChat: User has no pubkey field') + return null + } + + // Check if the user has a prvkey field + if (!user.prvkey) { + console.log('🔍 useNostrChat: User has no prvkey field') + return null + } + + console.log('🔍 useNostrChat: User has both pubkey and prvkey, returning user object') + + // Use the actual user data - assume prvkey and pubkey contain real Nostr keys + return { pubkey: user.pubkey, - hasExtensions: user.extensions, - extra: user.extra, - fullUser: user - }) - - // Check if we have a stored Nostr keypair for this user - const storageKey = `nostr_keys_${user.id}` - let storedKeys = null - - try { - const stored = localStorage.getItem(storageKey) - if (stored) { - storedKeys = JSON.parse(stored) - console.log('🔍 useNostrChat: Found stored Nostr keys:', storedKeys) - } - } catch (error) { - console.warn('Failed to parse stored Nostr keys:', error) - } - - // If we have stored keys, use them - if (storedKeys && storedKeys.prvkey && storedKeys.pubkey) { - console.log('🔍 useNostrChat: Using stored Nostr keys') - return { - pubkey: storedKeys.pubkey, - prvkey: storedKeys.prvkey - } - } - - // If no stored keys, generate new ones - console.log('🔍 useNostrChat: No stored keys found, generating new Nostr keypair') - try { - const privateKeyBytes = generateSecretKey() - const privateKey = bytesToHex(privateKeyBytes) - const publicKey = getPublicKey(privateKeyBytes) - - // Store the new keys - const newKeys = { pubkey: publicKey, prvkey: privateKey } - localStorage.setItem(storageKey, JSON.stringify(newKeys)) - - console.log('🔍 useNostrChat: Generated and stored new Nostr keypair') - - return newKeys - } catch (error) { - console.error('Failed to generate Nostr keypair:', error) - return null + prvkey: user.prvkey } }) + // Check if user is authenticated (has LNBits login) + const isAuthenticated = computed(() => { + return auth.currentUser.value !== null + }) + // Check if user has complete Nostr keypair const hasNostrKeys = computed(() => { const user = currentUser.value @@ -162,17 +141,11 @@ export function useNostrChat() { return { hasUser: false, hasPubkey: false, hasPrvkey: false, message: 'No user logged in' } } - if (!user.pubkey) { - return { hasUser: true, hasPubkey: false, hasPrvkey: false, message: 'User logged in but no Nostr public key' } - } - - // Check if we can get the private key from somewhere - // For now, we don't have access to it return { hasUser: true, - hasPubkey: true, - hasPrvkey: false, - message: 'User has Nostr public key but private key not available', + hasPubkey: !!user.pubkey, + hasPrvkey: !!user.prvkey, + message: user.pubkey && user.prvkey ? 'User has complete Nostr keypair' : 'User missing Nostr keys', pubkey: user.pubkey } } @@ -365,11 +338,16 @@ export function useNostrChat() { // } // } - // Subscribe to messages from a specific peer + // Subscribe to a specific peer for messages const subscribeToPeer = async (peerPubkey: string) => { if (!currentUser.value) { - console.warn('No user logged in - cannot subscribe to peer messages') - return null + console.warn('Cannot subscribe to peer: no user logged in') + return + } + + if (!currentUser.value.pubkey) { + console.warn('Cannot subscribe to peer: no public key available') + return } // Check if we have a pool and are connected @@ -382,51 +360,32 @@ export function useNostrChat() { throw new Error('Failed to initialize Nostr pool') } - const myPubkey = currentUser.value.pubkey - - // First, load historical messages - await loadHistoricalMessages(peerPubkey, myPubkey) - - // Then subscribe to new messages - const relayConfigs = getRelays() - console.log('Subscribing to new messages for peer:', peerPubkey, 'with filters:', [ - { + try { + // Subscribe to direct messages (kind 4) from this peer + const filter = { kinds: [4], - authors: [peerPubkey], - '#p': [myPubkey] - }, - { - kinds: [4], - authors: [myPubkey], - '#p': [peerPubkey] + '#p': [currentUser.value.pubkey], // Messages where we are the recipient + authors: [peerPubkey] // Messages from this specific peer } - ]) - - const unsubscribe = relayHub.subscribe({ - id: `peer-${peerPubkey}-${Date.now()}`, - filters: [ - { - kinds: [4], - authors: [peerPubkey], - '#p': [myPubkey] - }, - { - kinds: [4], - authors: [myPubkey], - '#p': [peerPubkey] + + console.log('Subscribing to peer with filter:', filter) + + // Use the relay hub to subscribe + const unsubscribe = relayHub.subscribe({ + id: `peer-${peerPubkey}`, + filters: [filter], + onEvent: (event) => { + handleIncomingMessage(event, peerPubkey) } - ], - relays: relayConfigs.map(r => r.url), - onEvent: (event: any) => { - console.log('Received live event:', event.id, 'author:', event.pubkey) - handleIncomingMessage(event, peerPubkey) - }, - onEose: () => { - console.log('Subscription closed for peer:', peerPubkey) - } - }) - - return unsubscribe + }) + + console.log('Successfully subscribed to peer:', peerPubkey) + + return unsubscribe + } catch (error) { + console.error('Failed to subscribe to peer:', error) + throw error + } } // Subscribe to a peer for notifications only (without loading full message history) @@ -545,52 +504,38 @@ export function useNostrChat() { // Handle incoming message const handleIncomingMessage = async (event: any, peerPubkey: string) => { - console.log('=== HANDLE INCOMING MESSAGE START ===') - console.log('Event ID:', event.id, 'Peer:', peerPubkey) - - if (processedMessageIds.value.has(event.id)) { - console.log('Message already processed, skipping:', event.id) + if (!currentUser.value || !currentUser.value.prvkey) { + console.warn('Cannot decrypt message: no private key available') return } - - processedMessageIds.value.add(event.id) - console.log('Added to processed messages:', event.id) - - console.log('Handling incoming message:', { - eventId: event.id, - eventPubkey: event.pubkey, - myPubkey: currentUser.value!.pubkey, - peerPubkey, - isSentByMe: event.pubkey === currentUser.value!.pubkey - }) try { - // Decrypt the message // For NIP-04 direct messages, always use peerPubkey as the second argument // This is the public key of the other party in the conversation - const isSentByMe = event.pubkey === currentUser.value!.pubkey + const isSentByMe = event.pubkey === currentUser.value.pubkey console.log('Decrypting message:', { eventId: event.id, isSentByMe, eventPubkey: event.pubkey, - myPubkey: currentUser.value!.pubkey, + myPubkey: currentUser.value.pubkey, peerPubkey, contentLength: event.content.length }) const decryptedContent = await nip04.decrypt( - currentUser.value!.prvkey, + currentUser.value.prvkey, peerPubkey, // Always use peerPubkey for shared secret derivation event.content ) - console.log('Successfully decrypted message:', { + console.log('Message decrypted successfully:', { eventId: event.id, contentLength: decryptedContent.length, - contentPreview: decryptedContent.substring(0, 50) + '...' + isSentByMe }) + // Create chat message const message: ChatMessage = { id: event.id, content: decryptedContent, @@ -599,74 +544,29 @@ export function useNostrChat() { pubkey: event.pubkey } - // Add message to the appropriate conversation - // Always use peerPubkey as the conversation key for both sent and received messages - const conversationKey = peerPubkey + // Add to messages + if (!messages.value.has(peerPubkey)) { + messages.value.set(peerPubkey, []) + } + messages.value.get(peerPubkey)!.push(message) - if (!messages.value.has(conversationKey)) { - messages.value.set(conversationKey, []) - } - - messages.value.get(conversationKey)!.push(message) - - // Sort messages by timestamp - messages.value.get(conversationKey)!.sort((a, b) => a.created_at - b.created_at) - - // Force reactivity by triggering a change - messages.value = new Map(messages.value) - - // Update latest message timestamp for this peer (for sorting) - updateLatestMessageTimestamp(peerPubkey, message.created_at) - - // Track unread messages (only for received messages, not sent ones) + // Mark as unread if not sent by us if (!isSentByMe) { - const unreadData = getUnreadData(peerPubkey) - - console.log(`Processing unread message logic for ${peerPubkey}:`, { - messageId: event.id, - messageTimestamp: message.created_at, - lastReadTimestamp: unreadData.lastReadTimestamp, - currentUnreadCount: unreadData.unreadCount, - alreadyProcessed: unreadData.processedMessageIds.has(event.id), - processedMessageIdsCount: unreadData.processedMessageIds.size - }) - - // Check if this message is newer than the last read timestamp AND we haven't already counted it - if (message.created_at > unreadData.lastReadTimestamp && !unreadData.processedMessageIds.has(event.id)) { - // Add this message ID to the processed set - unreadData.processedMessageIds.add(event.id) - - const updatedUnreadData: UnreadMessageData = { - lastReadTimestamp: unreadData.lastReadTimestamp, - unreadCount: unreadData.unreadCount + 1, - processedMessageIds: unreadData.processedMessageIds - } - - saveUnreadData(peerPubkey, updatedUnreadData) - updateUnreadCount(peerPubkey, updatedUnreadData.unreadCount) - - console.log(`✅ New unread message from ${peerPubkey}. Total unread: ${updatedUnreadData.unreadCount}`) - } else if (unreadData.processedMessageIds.has(event.id)) { - console.log(`⏭️ Message ${event.id} from ${peerPubkey} already counted as unread. Skipping.`) - } else { - console.log(`⏰ Message from ${peerPubkey} is older than last read timestamp. Skipping unread count.`) - } - } else { - console.log(`📤 Message from ${peerPubkey} was sent by current user. Skipping unread count.`) + // For now, just update the unread count + // TODO: Implement proper unread message tracking + const currentCount = unreadCounts.value.get(peerPubkey) || 0 + updateUnreadCount(peerPubkey, currentCount + 1) } - + + // Update latest message timestamp + updateLatestMessageTimestamp(peerPubkey, event.created_at) + // Trigger callback if set if (onMessageAdded.value) { - onMessageAdded.value(conversationKey) + onMessageAdded.value(peerPubkey) } - - console.log('Messages for conversation:', messages.value.get(conversationKey)?.map(m => ({ - id: m.id, - sent: m.sent, - content: m.content.substring(0, 30) + '...', - timestamp: m.created_at - }))) - + + console.log('Message processed and added to chat:', message) } catch (error) { console.error('Failed to decrypt message:', error) } @@ -674,12 +574,24 @@ export function useNostrChat() { // Send message to a peer const sendMessage = async (peerPubkey: string, content: string) => { + console.log('🔍 sendMessage: Starting message send...') + console.log('🔍 sendMessage: peerPubkey:', peerPubkey) + console.log('🔍 sendMessage: content length:', content.length) + if (!currentUser.value) { + console.log('🔍 sendMessage: currentUser.value is null/undefined') throw new Error('No user logged in - please authenticate first') } + console.log('🔍 sendMessage: currentUser.value:', currentUser.value) + console.log('🔍 sendMessage: currentUser.value.pubkey:', currentUser.value.pubkey) + console.log('🔍 sendMessage: currentUser.value.prvkey:', currentUser.value.prvkey) + console.log('🔍 sendMessage: currentUser.value.prvkey type:', typeof currentUser.value.prvkey) + console.log('🔍 sendMessage: currentUser.value.prvkey length:', currentUser.value.prvkey?.length) + // Check if we have the required Nostr keypair if (!currentUser.value.prvkey) { + console.log('🔍 sendMessage: prvkey is falsy, throwing error') throw new Error('Nostr private key not available. Please ensure your LNBits account has Nostr keys configured.') } @@ -877,7 +789,7 @@ export function useNostrChat() { // State isConnected: readonly(isConnected), messages: readonly(messages), - isLoggedIn: readonly(isLoggedIn), + isLoggedIn: readonly(isAuthenticated), peers: readonly(peers), // Reactive computed properties diff --git a/src/lib/api/lnbits.ts b/src/lib/api/lnbits.ts index 4198345..5ecbba7 100644 --- a/src/lib/api/lnbits.ts +++ b/src/lib/api/lnbits.ts @@ -40,6 +40,7 @@ interface User { username?: string email?: string pubkey?: string + prvkey?: string // Nostr private key for user external_id?: string extensions: string[] wallets: Wallet[]