From 8a019db34a6cb4a78666c6203fab91f136bb7893 Mon Sep 17 00:00:00 2001 From: padreug Date: Sat, 6 Sep 2025 20:12:41 +0200 Subject: [PATCH 1/2] Enhance Chat and Market Services with improved error handling and logging Chat Service: - Added detailed logging for API calls and responses, including warnings for missing authentication tokens and invalid response formats. - Implemented a retry mechanism for message subscription setup on connection errors. - Merged peers instead of clearing existing ones when loading from the API. Market Service: - Updated authentication checks to prioritize global auth state, improving user experience during order placement. - Enhanced error messages for missing Nostr keys to guide users in configuring their profiles. These changes improve the robustness and user-friendliness of the chat and market functionalities. --- src/modules/chat/services/chat-service.ts | 78 ++++++++++++++----- .../market/services/nostrmarketService.ts | 28 +++++-- src/modules/market/views/CheckoutPage.vue | 15 ++-- 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/src/modules/chat/services/chat-service.ts b/src/modules/chat/services/chat-service.ts index 0aa52de..49666a4 100644 --- a/src/modules/chat/services/chat-service.ts +++ b/src/modules/chat/services/chat-service.ts @@ -316,10 +316,13 @@ export class ChatService extends BaseService { try { const authToken = getAuthToken() if (!authToken) { + console.warn('💬 No authentication token found for loading peers from API') throw new Error('No authentication token found') } const API_BASE_URL = config.api.baseUrl || 'http://localhost:5006' + console.log('💬 Loading peers from API:', `${API_BASE_URL}/api/v1/auth/nostr/pubkeys`) + const response = await fetch(`${API_BASE_URL}/api/v1/auth/nostr/pubkeys`, { headers: { 'Authorization': `Bearer ${authToken}`, @@ -328,15 +331,26 @@ export class ChatService extends BaseService { }) if (!response.ok) { - throw new Error(`Failed to load peers: ${response.status}`) + const errorText = await response.text() + console.error('💬 API response error:', response.status, errorText) + throw new Error(`Failed to load peers: ${response.status} - ${errorText}`) } const data = await response.json() + console.log('💬 API returned', data?.length || 0, 'peers') - // Clear existing peers and load from API - this.peers.value.clear() + if (!Array.isArray(data)) { + console.warn('💬 Invalid API response format - expected array, got:', typeof data) + return + } + // Don't clear existing peers - merge instead data.forEach((peer: any) => { + if (!peer.pubkey) { + console.warn('💬 Skipping peer without pubkey:', peer) + return + } + const chatPeer: ChatPeer = { pubkey: peer.pubkey, name: peer.username || `User ${peer.pubkey.slice(0, 8)}`, @@ -349,25 +363,31 @@ export class ChatService extends BaseService { // Save to storage this.savePeersToStorage() - console.log(`Loaded ${data.length} peers from API`) + console.log(`✅ Loaded ${data.length} peers from API, total peers now: ${this.peers.value.size}`) } catch (error) { - console.error('Failed to load peers from API:', error) - throw error + console.error('❌ Failed to load peers from API:', error) + // Don't re-throw - peers from storage are still available } } private loadPeersFromStorage(): void { // Skip loading peers in constructor as StorageService may not be available yet // This will be called later during initialization when dependencies are ready - if (!this.isInitialized.value) { + if (!this.isInitialized.value || !this.storageService) { + this.debug('Skipping peer loading from storage - not initialized or storage unavailable') return } - const peersArray = this.storageService.getUserData('chat-peers', []) as ChatPeer[] - peersArray.forEach(peer => { - this.peers.value.set(peer.pubkey, peer) - }) + try { + const peersArray = this.storageService.getUserData('chat-peers', []) as ChatPeer[] + console.log('💬 Loading', peersArray.length, 'peers from storage') + peersArray.forEach(peer => { + this.peers.value.set(peer.pubkey, peer) + }) + } catch (error) { + console.warn('💬 Failed to load peers from storage:', error) + } } private savePeersToStorage(): void { @@ -465,22 +485,36 @@ export class ChatService extends BaseService { if (!this.relayHub || !this.authService?.user?.value?.pubkey) { console.warn('💬 Cannot setup message subscription: missing services') + // Retry after a short delay + setTimeout(() => this.setupMessageSubscription(), 2000) return } - if (!this.relayHub.isConnected) { + if (!this.relayHub.isConnected.value) { console.warn('💬 RelayHub not connected, waiting for connection...') - // Listen for connection event - this.relayHub.on('connected', () => { + // Listen for connection event - but also set up retry + const connectHandler = () => { console.log('💬 RelayHub connected, setting up message subscription...') + this.relayHub.off('connected', connectHandler) // Remove listener to prevent multiple calls this.setupMessageSubscription() - }) + } + this.relayHub.on('connected', connectHandler) + + // Also set up a fallback retry mechanism + setTimeout(() => { + if (!this.subscriptionUnsubscriber && this.relayHub.isConnected.value) { + console.log('💬 Retry mechanism triggered - setting up subscription') + this.setupMessageSubscription() + } + }, 5000) return } const userPubkey = this.authService.user.value.pubkey // Subscribe to encrypted direct messages (kind 4) addressed to this user + console.log('💬 Setting up chat message subscription for user:', userPubkey.slice(0, 8)) + this.subscriptionUnsubscriber = this.relayHub.subscribe({ id: 'chat-messages', filters: [ @@ -495,17 +529,25 @@ export class ChatService extends BaseService { return } + console.log('💬 Received encrypted message from:', event.pubkey.slice(0, 8)) await this.processIncomingMessage(event) }, onEose: () => { - console.log('Chat message subscription EOSE received') + console.log('💬 Chat message subscription EOSE received - subscription active') } }) - console.log('Chat message subscription set up successfully') + console.log('✅ Chat message subscription set up successfully') } catch (error) { - console.error('Failed to setup message subscription:', error) + console.error('❌ Failed to setup message subscription:', error) + // Retry after a delay on error + setTimeout(() => { + if (!this.subscriptionUnsubscriber) { + console.log('💬 Retrying message subscription setup after error...') + this.setupMessageSubscription() + } + }, 3000) } } diff --git a/src/modules/market/services/nostrmarketService.ts b/src/modules/market/services/nostrmarketService.ts index df2b4da..c210d87 100644 --- a/src/modules/market/services/nostrmarketService.ts +++ b/src/modules/market/services/nostrmarketService.ts @@ -1,6 +1,7 @@ import { finalizeEvent, type EventTemplate, nip04 } from 'nostr-tools' import { BaseService } from '@/core/base/BaseService' import type { Stall, Product, Order } from '@/stores/market' +import { auth } from '@/composables/useAuth' export interface NostrmarketStall { id: string @@ -106,15 +107,32 @@ export class NostrmarketService extends BaseService { } private getAuth() { - if (!this.authService?.isAuthenticated?.value || !this.authService?.user?.value?.prvkey) { - throw new Error('User not authenticated or private key not available') + // Check global auth first + if (!auth.isAuthenticated.value) { + throw new Error('User not authenticated') } + + // Try to get keys from global auth first, fallback to injected auth service + const globalUser = auth.currentUser.value + const serviceUser = this.authService?.user?.value - const pubkey = this.authService.user.value.pubkey - const prvkey = this.authService.user.value.prvkey + const pubkey = globalUser?.pubkey || serviceUser?.pubkey + const prvkey = globalUser?.prvkey || serviceUser?.prvkey if (!pubkey || !prvkey) { - throw new Error('Public key or private key is missing') + this.debug('Auth check failed:', { + globalUser: { + exists: !!globalUser, + hasPubkey: !!globalUser?.pubkey, + hasPrvkey: !!globalUser?.prvkey + }, + serviceUser: { + exists: !!serviceUser, + hasPubkey: !!serviceUser?.pubkey, + hasPrvkey: !!serviceUser?.prvkey + } + }) + throw new Error('Nostr keys not available. Please ensure your Nostr identity is configured in your profile.') } // Validate that we have proper hex strings diff --git a/src/modules/market/views/CheckoutPage.vue b/src/modules/market/views/CheckoutPage.vue index e37a712..694e816 100644 --- a/src/modules/market/views/CheckoutPage.vue +++ b/src/modules/market/views/CheckoutPage.vue @@ -272,6 +272,7 @@ import { ref, computed, onMounted } from 'vue' import { useRoute } from 'vue-router' import { useMarketStore } from '@/stores/market' import { injectService, SERVICE_TOKENS } from '@/core/di-container' +import { auth } from '@/composables/useAuth' import { Card, CardHeader, @@ -411,21 +412,23 @@ const placeOrder = async () => { } // Debug logging to understand auth state console.log('Auth check:', { - isAuthenticated: authService.isAuthenticated.value, - user: authService.user.value, + isAuthenticated: auth.isAuthenticated.value, + user: auth.currentUser.value, + userPubkey: auth.currentUser.value?.pubkey, + authServiceUser: authService.user.value, hasPubkey: !!authService.user.value?.pubkey, nostrPubkey: authService.user.value?.pubkey }) - // Get pubkey from auth service - const userPubkey = authService.user.value?.pubkey + // Try to get pubkey from main auth first, fallback to auth service + const userPubkey = auth.currentUser.value?.pubkey || authService.user.value?.pubkey - if (!authService.isAuthenticated.value) { + if (!auth.isAuthenticated.value) { throw new Error('You must be logged in to place an order') } if (!userPubkey) { - throw new Error('No Nostr public key found. Please ensure your Nostr identity is configured.') + throw new Error('Nostr identity required: Please configure your Nostr public key in your profile settings to place orders.') } // Create the order using the market store's order placement functionality From 7cfeaee21e201ed93feef238abc26646218410e4 Mon Sep 17 00:00:00 2001 From: padreug Date: Sat, 6 Sep 2025 22:56:50 +0200 Subject: [PATCH 2/2] Refactor Chat and Market Services for enhanced user experience and error handling Chat Service: - Improved error handling for message subscription setup and API responses. - Enhanced logging for better tracking of authentication issues. Market Service: - Streamlined authentication checks to improve order placement flow. - Updated user guidance for missing Nostr keys. These updates aim to bolster the reliability and usability of the chat and market functionalities. --- src/modules/chat/services/chat-service.ts | 139 ++++++++++++++++++---- 1 file changed, 115 insertions(+), 24 deletions(-) diff --git a/src/modules/chat/services/chat-service.ts b/src/modules/chat/services/chat-service.ts index 49666a4..351a97a 100644 --- a/src/modules/chat/services/chat-service.ts +++ b/src/modules/chat/services/chat-service.ts @@ -38,8 +38,22 @@ export class ChatService extends BaseService { * Service-specific initialization (called by BaseService) */ protected async onInitialize(): Promise { - // Check if we have user pubkey - if (!this.authService?.user?.value?.pubkey) { + // Import global auth to ensure we're checking the right instance + const { auth } = await import('@/composables/useAuth') + + // Check both injected auth service and global auth + const hasAuthService = this.authService?.user?.value?.pubkey + const hasGlobalAuth = auth.currentUser.value?.pubkey + + this.debug('Auth check during initialization:', { + hasAuthService: !!hasAuthService, + hasGlobalAuth: !!hasGlobalAuth, + authServicePubkey: hasAuthService ? this.authService.user.value.pubkey.slice(0, 8) + '...' : null, + globalAuthPubkey: hasGlobalAuth ? auth.currentUser.value.pubkey.slice(0, 8) + '...' : null + }) + + // Check if we have user pubkey from either source + if (!hasAuthService && !hasGlobalAuth) { this.debug('User not authenticated yet, deferring full initialization') // Listen for auth events to complete initialization when user logs in @@ -52,9 +66,30 @@ export class ChatService extends BaseService { await this.completeInitialization() }) + // Also set up a periodic check in case the event was missed + const checkAuth = async () => { + // Check both sources again + const hasAuth = this.authService?.user?.value?.pubkey || auth.currentUser.value?.pubkey + + if (hasAuth) { + this.debug('Auth detected via periodic check, completing chat initialization...') + unsubscribe() + await this.waitForDependencies() + await this.completeInitialization() + return + } + + // Check again in 1 second + setTimeout(checkAuth, 1000) + } + + // Start periodic check after a short delay + setTimeout(checkAuth, 2000) + return } + this.debug('User is authenticated, proceeding with initialization') await this.completeInitialization() } @@ -62,6 +97,14 @@ export class ChatService extends BaseService { * Complete the initialization once all dependencies are available */ private async completeInitialization(): Promise { + // Prevent duplicate initialization + if (this.isFullyInitialized) { + this.debug('Chat service already fully initialized, skipping') + return + } + + this.debug('Starting complete chat service initialization...') + // Load peers from storage first this.loadPeersFromStorage() @@ -76,13 +119,18 @@ export class ChatService extends BaseService { // Register with visibility service this.registerWithVisibilityService() - this.debug('Chat service fully initialized and ready!') + // Mark as fully initialized + this.isFullyInitialized = true + + console.log('✅ Chat service fully initialized and ready!') } + private isFullyInitialized = false + // Initialize message handling (subscription + history loading) async initializeMessageHandling(): Promise { // Set up real-time subscription - this.setupMessageSubscription() + await this.setupMessageSubscription() // Load message history for known peers await this.loadMessageHistory() @@ -198,37 +246,60 @@ export class ChatService extends BaseService { // Refresh peers from API async refreshPeers(): Promise { + // Import global auth to check authentication + const { auth } = await import('@/composables/useAuth') + const hasAuth = this.authService?.user?.value?.pubkey || auth.currentUser.value?.pubkey + + // If chat service hasn't been fully initialized yet, try to complete initialization first + if (!this.isFullyInitialized && hasAuth) { + console.log('💬 Refresh peers triggered full initialization') + await this.completeInitialization() + } + return this.loadPeersFromAPI() } // Check if services are available for messaging - private checkServicesAvailable(): { relayHub: any; authService: any } | null { + private async checkServicesAvailable(): Promise<{ relayHub: any; authService: any; globalAuth?: any } | null> { // Dependencies are already injected by BaseService + const { auth } = await import('@/composables/useAuth') - if (!this.relayHub || !this.authService?.user?.value?.prvkey) { + const hasAuthService = this.authService?.user?.value?.prvkey + const hasGlobalAuth = auth.currentUser.value?.prvkey + + if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) { return null } - if (!this.relayHub.isConnected) { + if (!this.relayHub.isConnected.value) { return null } - return { relayHub: this.relayHub, authService: this.authService } + return { + relayHub: this.relayHub, + authService: this.authService, + globalAuth: auth + } } // Send a message async sendMessage(peerPubkey: string, content: string): Promise { try { - const services = this.checkServicesAvailable() + const services = await this.checkServicesAvailable() if (!services) { throw new Error('Chat services not ready. Please wait for connection to establish.') } - const { relayHub, authService } = services + const { relayHub, authService, globalAuth } = services - const userPrivkey = authService.user.value.prvkey - const userPubkey = authService.user.value.pubkey + // Use auth service if available, otherwise fall back to global auth + const userPrivkey = authService?.user?.value?.prvkey || globalAuth.currentUser.value?.prvkey + const userPubkey = authService?.user?.value?.pubkey || globalAuth.currentUser.value?.pubkey + + if (!userPrivkey || !userPubkey) { + throw new Error('User authentication not available') + } // Encrypt the message using NIP-04 const encryptedContent = await nip04.encrypt(userPrivkey, peerPubkey, content) @@ -398,15 +469,20 @@ export class ChatService extends BaseService { // Load message history for known peers private async loadMessageHistory(): Promise { try { - // Dependencies are already injected by BaseService + // Import global auth to ensure we're checking the right instance + const { auth } = await import('@/composables/useAuth') - if (!this.relayHub || !this.authService?.user?.value?.pubkey) { + // Check both auth sources + const hasAuthService = this.authService?.user?.value?.pubkey + const hasGlobalAuth = auth.currentUser.value?.pubkey + + if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) { console.warn('Cannot load message history: missing services') return } - const userPubkey = this.authService.user.value.pubkey - const userPrivkey = this.authService.user.value.prvkey + const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value.pubkey + const userPrivkey = hasAuthService ? this.authService.user.value.prvkey : auth.currentUser.value.prvkey const peerPubkeys = Array.from(this.peers.value.keys()) if (peerPubkeys.length === 0) { @@ -479,11 +555,24 @@ export class ChatService extends BaseService { } // Setup subscription for incoming messages - private setupMessageSubscription(): void { + private async setupMessageSubscription(): Promise { try { - // Dependencies are already injected by BaseService + // Import global auth to ensure we're checking the right instance + const { auth } = await import('@/composables/useAuth') - if (!this.relayHub || !this.authService?.user?.value?.pubkey) { + // Check both auth sources + const hasAuthService = this.authService?.user?.value?.pubkey + const hasGlobalAuth = auth.currentUser.value?.pubkey + const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey + + console.log('💬 Setting up message subscription, auth check:', { + hasRelayHub: !!this.relayHub, + hasAuthService: !!hasAuthService, + hasGlobalAuth: !!hasGlobalAuth, + userPubkey: userPubkey?.slice(0, 8) + '...' || 'none' + }) + + if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) { console.warn('💬 Cannot setup message subscription: missing services') // Retry after a short delay setTimeout(() => this.setupMessageSubscription(), 2000) @@ -510,8 +599,6 @@ export class ChatService extends BaseService { return } - const userPubkey = this.authService.user.value.pubkey - // Subscribe to encrypted direct messages (kind 4) addressed to this user console.log('💬 Setting up chat message subscription for user:', userPubkey.slice(0, 8)) @@ -617,8 +704,11 @@ export class ChatService extends BaseService { */ private async processIncomingMessage(event: any): Promise { try { - const userPubkey = this.authService?.user?.value?.pubkey - const userPrivkey = this.authService?.user?.value?.prvkey + // Import global auth to ensure we're checking the right instance + const { auth } = await import('@/composables/useAuth') + + const userPubkey = this.authService?.user?.value?.pubkey || auth.currentUser.value?.pubkey + const userPrivkey = this.authService?.user?.value?.prvkey || auth.currentUser.value?.prvkey if (!userPubkey || !userPrivkey) { console.warn('Cannot process message: user not authenticated') @@ -679,7 +769,8 @@ export class ChatService extends BaseService { * Load recent messages for a specific peer */ private async loadRecentMessagesForPeer(peerPubkey: string): Promise { - const userPubkey = this.authService?.user?.value?.pubkey + const { auth } = await import('@/composables/useAuth') + const userPubkey = this.authService?.user?.value?.pubkey || auth.currentUser.value?.pubkey if (!userPubkey || !this.relayHub) return try {