feat: Enhance Nostr chat functionality and debugging
- Improve console logging in ChatComponent and useNostrChat for better tracking of message sending, user authentication, and key management. - Update user authentication checks to ensure valid Nostr keypairs are available before sending messages. - Refactor message handling logic to streamline subscription and processing of incoming messages, enhancing overall chat experience.
This commit is contained in:
parent
8e94216c02
commit
390f77539e
3 changed files with 108 additions and 194 deletions
|
|
@ -547,8 +547,9 @@ const sendMessage = async () => {
|
||||||
console.log('🔍 ChatComponent: Nostr key status:', keyStatus)
|
console.log('🔍 ChatComponent: Nostr key status:', keyStatus)
|
||||||
|
|
||||||
// Check if user is logged in
|
// 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: Current user:', nostrChat.currentUser.value)
|
||||||
|
console.log('🔍 ChatComponent: Has Nostr keys:', nostrChat.hasNostrKeys.value)
|
||||||
|
|
||||||
await sendNostrMessage(selectedPeer.value.pubkey, messageInput.value)
|
await sendNostrMessage(selectedPeer.value.pubkey, messageInput.value)
|
||||||
messageInput.value = ''
|
messageInput.value = ''
|
||||||
|
|
|
||||||
|
|
@ -87,66 +87,45 @@ export function useNostrChat() {
|
||||||
|
|
||||||
// Computed - use relay hub's connection status and auth system
|
// Computed - use relay hub's connection status and auth system
|
||||||
const isConnected = computed(() => relayHub.isConnected.value)
|
const isConnected = computed(() => relayHub.isConnected.value)
|
||||||
const isLoggedIn = computed(() => auth.isAuthenticated.value)
|
|
||||||
|
|
||||||
// Get current user from auth system
|
// Get current user from auth system
|
||||||
const currentUser = computed(() => {
|
const currentUser = computed(() => {
|
||||||
const user = auth.currentUser.value
|
const user = auth.currentUser.value
|
||||||
if (!user || !user.pubkey) {
|
if (!user) {
|
||||||
console.log('🔍 useNostrChat: No user or no pubkey available:', user)
|
console.log('🔍 useNostrChat: No user available from auth system')
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔍 useNostrChat: User data available:', {
|
console.log('🔍 useNostrChat: Full user data from LNBits:', user)
|
||||||
id: user.id,
|
console.log('🔍 useNostrChat: User pubkey:', user.pubkey)
|
||||||
username: user.username,
|
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,
|
pubkey: user.pubkey,
|
||||||
hasExtensions: user.extensions,
|
prvkey: user.prvkey
|
||||||
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
|
// Check if user is authenticated (has LNBits login)
|
||||||
if (storedKeys && storedKeys.prvkey && storedKeys.pubkey) {
|
const isAuthenticated = computed(() => {
|
||||||
console.log('🔍 useNostrChat: Using stored Nostr keys')
|
return auth.currentUser.value !== null
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if user has complete Nostr keypair
|
// Check if user has complete Nostr keypair
|
||||||
|
|
@ -162,17 +141,11 @@ export function useNostrChat() {
|
||||||
return { hasUser: false, hasPubkey: false, hasPrvkey: false, message: 'No user logged in' }
|
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 {
|
return {
|
||||||
hasUser: true,
|
hasUser: true,
|
||||||
hasPubkey: true,
|
hasPubkey: !!user.pubkey,
|
||||||
hasPrvkey: false,
|
hasPrvkey: !!user.prvkey,
|
||||||
message: 'User has Nostr public key but private key not available',
|
message: user.pubkey && user.prvkey ? 'User has complete Nostr keypair' : 'User missing Nostr keys',
|
||||||
pubkey: user.pubkey
|
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) => {
|
const subscribeToPeer = async (peerPubkey: string) => {
|
||||||
if (!currentUser.value) {
|
if (!currentUser.value) {
|
||||||
console.warn('No user logged in - cannot subscribe to peer messages')
|
console.warn('Cannot subscribe to peer: no user logged in')
|
||||||
return null
|
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
|
// Check if we have a pool and are connected
|
||||||
|
|
@ -382,51 +360,32 @@ export function useNostrChat() {
|
||||||
throw new Error('Failed to initialize Nostr pool')
|
throw new Error('Failed to initialize Nostr pool')
|
||||||
}
|
}
|
||||||
|
|
||||||
const myPubkey = currentUser.value.pubkey
|
try {
|
||||||
|
// Subscribe to direct messages (kind 4) from this peer
|
||||||
// First, load historical messages
|
const filter = {
|
||||||
await loadHistoricalMessages(peerPubkey, myPubkey)
|
|
||||||
|
|
||||||
// Then subscribe to new messages
|
|
||||||
const relayConfigs = getRelays()
|
|
||||||
console.log('Subscribing to new messages for peer:', peerPubkey, 'with filters:', [
|
|
||||||
{
|
|
||||||
kinds: [4],
|
kinds: [4],
|
||||||
authors: [peerPubkey],
|
'#p': [currentUser.value.pubkey], // Messages where we are the recipient
|
||||||
'#p': [myPubkey]
|
authors: [peerPubkey] // Messages from this specific peer
|
||||||
},
|
|
||||||
{
|
|
||||||
kinds: [4],
|
|
||||||
authors: [myPubkey],
|
|
||||||
'#p': [peerPubkey]
|
|
||||||
}
|
}
|
||||||
])
|
|
||||||
|
|
||||||
const unsubscribe = relayHub.subscribe({
|
console.log('Subscribing to peer with filter:', filter)
|
||||||
id: `peer-${peerPubkey}-${Date.now()}`,
|
|
||||||
filters: [
|
// Use the relay hub to subscribe
|
||||||
{
|
const unsubscribe = relayHub.subscribe({
|
||||||
kinds: [4],
|
id: `peer-${peerPubkey}`,
|
||||||
authors: [peerPubkey],
|
filters: [filter],
|
||||||
'#p': [myPubkey]
|
onEvent: (event) => {
|
||||||
},
|
handleIncomingMessage(event, peerPubkey)
|
||||||
{
|
|
||||||
kinds: [4],
|
|
||||||
authors: [myPubkey],
|
|
||||||
'#p': [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)
|
// Subscribe to a peer for notifications only (without loading full message history)
|
||||||
|
|
@ -545,52 +504,38 @@ export function useNostrChat() {
|
||||||
|
|
||||||
// Handle incoming message
|
// Handle incoming message
|
||||||
const handleIncomingMessage = async (event: any, peerPubkey: string) => {
|
const handleIncomingMessage = async (event: any, peerPubkey: string) => {
|
||||||
console.log('=== HANDLE INCOMING MESSAGE START ===')
|
if (!currentUser.value || !currentUser.value.prvkey) {
|
||||||
console.log('Event ID:', event.id, 'Peer:', peerPubkey)
|
console.warn('Cannot decrypt message: no private key available')
|
||||||
|
|
||||||
if (processedMessageIds.value.has(event.id)) {
|
|
||||||
console.log('Message already processed, skipping:', event.id)
|
|
||||||
return
|
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 {
|
try {
|
||||||
// Decrypt the message
|
|
||||||
// For NIP-04 direct messages, always use peerPubkey as the second argument
|
// For NIP-04 direct messages, always use peerPubkey as the second argument
|
||||||
// This is the public key of the other party in the conversation
|
// 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:', {
|
console.log('Decrypting message:', {
|
||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
isSentByMe,
|
isSentByMe,
|
||||||
eventPubkey: event.pubkey,
|
eventPubkey: event.pubkey,
|
||||||
myPubkey: currentUser.value!.pubkey,
|
myPubkey: currentUser.value.pubkey,
|
||||||
peerPubkey,
|
peerPubkey,
|
||||||
contentLength: event.content.length
|
contentLength: event.content.length
|
||||||
})
|
})
|
||||||
|
|
||||||
const decryptedContent = await nip04.decrypt(
|
const decryptedContent = await nip04.decrypt(
|
||||||
currentUser.value!.prvkey,
|
currentUser.value.prvkey,
|
||||||
peerPubkey, // Always use peerPubkey for shared secret derivation
|
peerPubkey, // Always use peerPubkey for shared secret derivation
|
||||||
event.content
|
event.content
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('Successfully decrypted message:', {
|
console.log('Message decrypted successfully:', {
|
||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
contentLength: decryptedContent.length,
|
contentLength: decryptedContent.length,
|
||||||
contentPreview: decryptedContent.substring(0, 50) + '...'
|
isSentByMe
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create chat message
|
||||||
const message: ChatMessage = {
|
const message: ChatMessage = {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
content: decryptedContent,
|
content: decryptedContent,
|
||||||
|
|
@ -599,74 +544,29 @@ export function useNostrChat() {
|
||||||
pubkey: event.pubkey
|
pubkey: event.pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add message to the appropriate conversation
|
// Add to messages
|
||||||
// Always use peerPubkey as the conversation key for both sent and received messages
|
if (!messages.value.has(peerPubkey)) {
|
||||||
const conversationKey = peerPubkey
|
messages.value.set(peerPubkey, [])
|
||||||
|
|
||||||
if (!messages.value.has(conversationKey)) {
|
|
||||||
messages.value.set(conversationKey, [])
|
|
||||||
}
|
}
|
||||||
|
messages.value.get(peerPubkey)!.push(message)
|
||||||
|
|
||||||
messages.value.get(conversationKey)!.push(message)
|
// Mark as unread if not sent by us
|
||||||
|
|
||||||
// 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)
|
|
||||||
if (!isSentByMe) {
|
if (!isSentByMe) {
|
||||||
const unreadData = getUnreadData(peerPubkey)
|
// For now, just update the unread count
|
||||||
|
// TODO: Implement proper unread message tracking
|
||||||
console.log(`Processing unread message logic for ${peerPubkey}:`, {
|
const currentCount = unreadCounts.value.get(peerPubkey) || 0
|
||||||
messageId: event.id,
|
updateUnreadCount(peerPubkey, currentCount + 1)
|
||||||
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.`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update latest message timestamp
|
||||||
|
updateLatestMessageTimestamp(peerPubkey, event.created_at)
|
||||||
|
|
||||||
// Trigger callback if set
|
// Trigger callback if set
|
||||||
if (onMessageAdded.value) {
|
if (onMessageAdded.value) {
|
||||||
onMessageAdded.value(conversationKey)
|
onMessageAdded.value(peerPubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Messages for conversation:', messages.value.get(conversationKey)?.map(m => ({
|
console.log('Message processed and added to chat:', message)
|
||||||
id: m.id,
|
|
||||||
sent: m.sent,
|
|
||||||
content: m.content.substring(0, 30) + '...',
|
|
||||||
timestamp: m.created_at
|
|
||||||
})))
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to decrypt message:', error)
|
console.error('Failed to decrypt message:', error)
|
||||||
}
|
}
|
||||||
|
|
@ -674,12 +574,24 @@ export function useNostrChat() {
|
||||||
|
|
||||||
// Send message to a peer
|
// Send message to a peer
|
||||||
const sendMessage = async (peerPubkey: string, content: string) => {
|
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) {
|
if (!currentUser.value) {
|
||||||
|
console.log('🔍 sendMessage: currentUser.value is null/undefined')
|
||||||
throw new Error('No user logged in - please authenticate first')
|
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
|
// Check if we have the required Nostr keypair
|
||||||
if (!currentUser.value.prvkey) {
|
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.')
|
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
|
// State
|
||||||
isConnected: readonly(isConnected),
|
isConnected: readonly(isConnected),
|
||||||
messages: readonly(messages),
|
messages: readonly(messages),
|
||||||
isLoggedIn: readonly(isLoggedIn),
|
isLoggedIn: readonly(isAuthenticated),
|
||||||
peers: readonly(peers),
|
peers: readonly(peers),
|
||||||
|
|
||||||
// Reactive computed properties
|
// Reactive computed properties
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ interface User {
|
||||||
username?: string
|
username?: string
|
||||||
email?: string
|
email?: string
|
||||||
pubkey?: string
|
pubkey?: string
|
||||||
|
prvkey?: string // Nostr private key for user
|
||||||
external_id?: string
|
external_id?: string
|
||||||
extensions: string[]
|
extensions: string[]
|
||||||
wallets: Wallet[]
|
wallets: Wallet[]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue